Saltearse al contenido

Acciones

Las acciones Livewire son métodos en su componente que pueden ser activados por interacciones frontales como pulsar un botón o enviar un formulario. Proporcionan al desarrollador la experiencia de poder llamar a un método PHP directamente desde el navegador, permitiéndole centrarse en la lógica de su aplicación sin tener que escribir código repetitivo que conecte el frontend y el backend de su aplicación.

Exploremos un ejemplo básico de llamada a una acción de guardar en un componente CrearPublicacion:

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Post;
class CrearPublicacion extends Component
{
public $titulo = '';
public $contenido = '';
public function guardar()
{
Post::create([
'titulo' => $this->titulo,
'contenido' => $this->contenido,
]);
return redirect()->to('/publicaciones');
}
public function render()
{
return view('livewire.crear-publicacion');
}
}
<form wire:submit="guardar">
<input type="text" wire:model="titulo" />
<textarea wire:model="contenido"></textarea>
<button type="submit">Guardar</button>
</form>

En el ejemplo anterior, cuando un usuario envía el formulario haciendo clic en «Guardar», wire:submit intercepta el evento submit y llama a la acción guardar() en el servidor.

En esencia, las acciones son una forma de asignar fácilmente las interacciones del usuario a la funcionalidad del lado del servidor sin la molestia de enviar y gestionar manualmente las solicitudes AJAX.

Refrescando un componente

A veces es posible que desee activar una simple «actualización» de su componente. Por ejemplo, si tiene un componente que comprueba el estado de algo en la base de datos, puede que quiera mostrar un botón a sus usuarios que les permita refrescar los resultados mostrados.

Puede hacer esto usando la simple acción $refresh de Livewire en cualquier lugar donde normalmente haría referencia al método de su propio componente:

<button type="button" wire:click="$refresh">...</button>

Cuando la acción $refresh es disparada, Livewire hará un viaje alrededor del servidor y re-presentará su componente sin llamar a ningún método.

Es importante tener en cuenta que cualquier actualización de datos pendiente en su componente (por ejemplo, enlaces wire:model) se aplicará en el servidor cuando se actualice el componente.

Internamente, Livewire utiliza el nombre «commit» para referirse a cualquier momento en que un componente Livewire se actualiza en el servidor. Si prefiere esta terminología, puede utilizar el ayudante $commit en lugar de $refresh. Ambos son idénticos.

<button type="button" wire:click="$commit">...</button>

También puede activar una actualización del componente utilizando AlpineJS en su componente Livewire:

<button type="button" x-on:click="$wire.$refresh()">...</button>

Obtenga más información leyendo la documentación para utilizar Alpine dentro de Livewire.

Confirmando una Acción

Cuando se permite a los usuarios realizar acciones peligrosas-como borrar una entrada de la base de datos-puedes querer mostrarles una alerta de confirmación para verificar que desean realizar esa acción.

Livewire hace esto fácil proporcionando una directiva simple llamada wire:confirm:

<button
type="button"
wire:click="eliminar"
wire:confirm="¿Seguro que quieres borrar este post?"
>
Eliminar Publicación
</button>

Cuando se añade wire:confirm a un elemento que contiene una acción Livewire, cuando un usuario intenta activar esa acción, se le presentará un diálogo de confirmación con el mensaje proporcionado. Puede pulsar «Aceptar» para confirmar la acción, o pulsar «Cancelar» o la tecla Escape.

Para más información, visite la página de documentación de wire:confirm.

Escuchadores de eventos (Event Listeners)

Livewire soporta una variedad de escuchadores de eventos, permitiéndole responder a varios tipos de interacciones del usuario:

ListenerDescripción
wire:clickSe activa cuando se hace clic en un elemento
wire:submit Se activa cuando se envía un formulario
wire:keydown Se activa cuando se pulsa una tecla
wire:keyup Se activa cuando se suelta una tecla
wire:mouseenter Se activa cuando el ratón entra en un elemento
wire:* El texto que siga a wire: se utilizará como nombre del evento de escucha

Debido a que el nombre del evento después de wire: puede ser cualquier cosa, Livewire soporta cualquier evento del navegador que puedas necesitar escuchar. Por ejemplo, para escuchar transitionend, puedes usar wire:transitionend.

Listener para teclas específicas

Puede utilizar uno de los cómodos alias de Livewire para limitar los listeners de eventos de pulsación de teclas a una tecla o combinación de teclas específica.

Por ejemplo, para realizar una búsqueda cuando un usuario pulsa Intro después de escribir en un cuadro de búsqueda, puede utilizar wire:keydown.enter:

<input wire:model="query" wire:keydown.enter="buscarPublicaciones" />

Puedes encadenar más alias de teclas después del primero para escuchar combinaciones de teclas. Por ejemplo, si desea escuchar la tecla Enter sólo mientras se pulsa la tecla Shift, puede escribir lo siguiente:

<input wire:keydown.shift.enter="..." />

A continuación encontrará una lista de todos los modificadores de teclas disponibles:

ModificadorTecla
.shiftShift
.enterEnter
.spaceEspacio
.ctrlCtrl
.cmdCmd
.metaCmd en Mac, tecla Windows en Windows
.altAlt
.upFlecha arriba
.downFlecha abajo
.leftFlecha izquierda
.rightFlecha derecha
.escapeEscape
.tabTabulador
.caps-lockBloqueo de mayúsculas
.equalIgual, =
.periodPunto, .
.slashBarra oblicua, /

Modificadores de eventos

Livewire también incluye modificadores útiles para hacer triviales las tareas comunes de gestión de eventos.

Por ejemplo, si necesitas llamar a event.preventDefault() desde dentro de un receptor de eventos, puedes añadir el sufijo .preven al nombre del evento:

<input wire:keydown.prevent="..." />

Aquí tienes una lista completa de todos los modificadores de oyentes de eventos disponibles y sus funciones:

ModificadorClave
.preventEquivale a llamar a .preventDefault()
.stopEquivale a llamar a .stopPropagation()
.windowEscucha eventos en el objeto window
.outsideSólo escucha los clics «fuera» del elemento
.documentEscucha los eventos del objeto document
.onceGarantiza que sólo se llame al receptor una vez
.debounceRebota el manejador 250ms por defecto
.debounce.100msDesactiva el manejador durante un tiempo determinado
.throttleAcelera el handler para que sea llamado cada 250ms como mínimo
.throttle.100msAcelerar el manejador a una duración personalizada
.selfSólo llama al receptor si el evento se originó en este elemento, no en los hijos
.camelConvierte el nombre del evento a camel case (wire:custom-event -> «customEvent»)
.dotConvierte el nombre del evento a la notación por puntos (wire:custom-event -> «custom.event»)
.passivewire:touchstart.passive no bloquea el funcionamiento del scroll
.captureEscucha el evento en la fase de «captura

Debido a que wire: utiliza la directiva x-on de Alpine bajo el capó, estos modificadores están disponibles para usted por Alpine. Para más contexto sobre cuándo deberías usar estos modificadores, consulta la documentación de Alpine Events.

Manejo de eventos de terceros

Livewire también soporta la escucha de eventos personalizados disparados por librerías de terceros.

Por ejemplo, imaginemos que estás utilizando el editor de texto enriquecido Trix en tu proyecto y quieres escuchar el evento trix-change para capturar el contenido del editor. Puedes conseguirlo utilizando la directiva wire:trix-change:

<form wire:submit="guardar">
<!-- ... -->
<trix-editor
wire:trix-change="enviarContenidoPublicacion($event.target.value)"
></trix-editor>
<!-- ... -->
</form>

En este ejemplo, la acción enviarContenidoPublicacion es llamada cada vez que se dispara el evento trix-change, actualizando la propiedad contenido en el componente Livewire con el valor actual del editor Trix.

Escucha de eventos personalizados enviados

Si su aplicación envía eventos personalizados desde Alpine, también puede escucharlos usando Livewire:

<div wire:custom-event="...">
<!-- Profundamente anidado dentro de este componente: -->
<button x-on:click="$dispatch('custom-event')">...</button>
</div>

Cuando se pulsa el botón en el ejemplo anterior, el evento custom-event se envía y burbujea hasta la raíz del componente Livewire donde wire:custom-event lo captura e invoca una acción determinada.

Si quieres escuchar un evento enviado a algún otro lugar de tu aplicación, tendrás que esperar a que el evento burbujee hasta el objeto window y escucharlo allí. Afortunadamente, Livewire hace esto fácil permitiéndote añadir un simple modificador .window a cualquier receptor de eventos:

<form wire:submit="guardar">
<input wire:model="titulo" />
<textarea wire:model="contenido"></textarea>
<button type="submit">Guardar</button>
</form>

Cuando un usuario hace clic en «Guardar», se envía una petición de red al servidor para llamar a la acción guardar() en el componente Livewire.

Pero, imaginemos que un usuario está rellenando este formulario con una conexión a Internet lenta. El usuario hace clic en «Guardar» e inicialmente no ocurre nada porque la petición de red tarda más de lo habitual. Puede que se pregunte si el envío ha fallado e intente hacer clic de nuevo en el botón «Guardar» mientras se está gestionando la primera solicitud.

En este caso, habría dos solicitudes para la misma acción siendo procesadas al mismo tiempo.

Para evitar este escenario, Livewire desactiva automáticamente el botón de envío y todas las entradas del formulario dentro del elemento <form> mientras se procesa una acción wire:submit. Esto asegura que un formulario no se envíe accidentalmente dos veces.

Para reducir aún más la confusión de los usuarios con conexiones lentas, a menudo es útil mostrar algún indicador de carga, como un sutil cambio de color de fondo o una animación SVG.

Livewire proporciona una directiva wire:loading que hace trivial mostrar y ocultar indicadores de carga en cualquier parte de una página. He aquí un breve ejemplo del uso de wire:loading para mostrar un mensaje de carga debajo del botón «Guardar»:

<form wire:submit="guardar">
<textarea wire:model="contenido"></textarea>
<button type="submit">Guardar</button>
<span wire:loading>Guardando...</span>
</form>

wire:loading es una potente función con una variedad de características más potentes. Consulta la documentación completa sobre carga para obtener más información.

Paso de Parámetros

Livewire le permite pasar parámetros desde su plantilla Blade a las acciones de su componente, dándole la oportunidad de proporcionar a una acción datos adicionales o estado desde el frontend cuando la acción es llamada.

Por ejemplo, imaginemos que tiene un componente MostrarPublicaciones que permite a los usuarios eliminar una entrada. Puede pasar el ID de la entrada como parámetro a la acción eliminar() en su componente Livewire. Entonces, la acción puede obtener la entrada relevante y borrarla de la base de datos:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
class MostrarPublicaciones extends Component
{
public function eliminar($id)
{
$post = Post::findOrFail($id);
$this->authorize('delete', $post);
$post->delete();
}
public function render()
{
return view('livewire.mostrar-publicaciones', [
'publicaciones' => Auth::user()->posts,
]);
}
}
<div>
@foreach ($publicaciones as $publicacion)
<div wire:key="{{ $publicacion->id }}">
<h1>{{ $publicacion->titulo }}</h1>
<span>{{ $publicacion->contenido }}</span>
<button wire:click="eliminar({{ $publicacion->id }})">Eliminar</button>
</div>
@endforeach
</div>

Para una entrada con un ID de 2, el botón «Eliminar» de la plantilla Blade anterior se mostrará en el navegador como:

<button wire:click="eliminar(2)">Eliminar</button>

Cuando se pulse este botón, se llamará al método eliminar() y se pasará $id con el valor «2».

Como comodidad añadida, puede resolver automáticamente los modelos Eloquent por un ID de modelo correspondiente que se proporciona a una acción como parámetro. Esto es muy similar a la vinculación de modelos de ruta(route model binding). Para empezar, escriba un parámetro de acción con una clase de modelo y el modelo apropiado se recuperará automáticamente de la base de datos y se pasará a la acción en lugar del ID:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
class MostrarPublicaciones extends Component
{
public function eliminar(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
}
public function render()
{
return view('livewire.mostrar-publicaciones', [
'publicaciones' => Auth::user()->publicaciones,
]);
}
}

Injección de dependencias

Puedes aprovechar el sistema de inyección de dependencias de Laravel introduciendo parámetros en la firma de tu acción. Livewire y Laravel resolverán automáticamente las dependencias de la acción desde el contenedor:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Repositories\PostRepository;
class MostrarPublicaciones extends Component
{
public function eliminar(PostRepository $publicaciones, $publicacionID)
{
$publicaciones->eliminarPost($publicacionID);
}
public function render()
{
return view('livewire.mostrar-publicaciones', [
'publicaciones' => Auth::user()->publicaciones,
]);
}
}
<div>
@foreach ($publicaciones as $publicacion)
<div wire:key="{{ $publicacion->id }}">
<h1>{{ $publicacion->titulo }}</h1>
<span>{{ $publicacion->contenido }}</span>
<button wire:click="eliminar({{ $publicacion->id }})">Eliminar</button>
</div>
@endforeach
</div>

En este ejemplo, el método eliminar() recibe una instancia de PostRepository resuelta a través del contenedor de servicios de Laravel antes de recibir el parámetro $publicacionId proporcionado.

Llamando a Acciones desde Alpine

Livewire se integra perfectamente con Alpine. De hecho, bajo el capó, cada componente Livewire es también un componente Alpine. Esto significa que puede aprovechar Alpine al máximo dentro de sus componentes para agregar interactividad del lado del cliente potenciada por JavaScript.

Para hacer este emparejamiento aún más poderoso, Livewire expone un objeto mágico $wire a Alpine que puede ser tratado como una representación JavaScript de su componente PHP. Además de acceder y mutar propiedades públicas a través de $wire, puede llamar a acciones. Cuando una acción es invocada en el objeto $wire, el método PHP correspondiente será invocado en su componente Livewire backend:

<button x-on:click="$wire.save()">Guardar Publicación</button>

O, para ilustrar un ejemplo más complejo, podría utilizar la utilidad x-intersect de Alpine para disparar una acción incrementViewCount() Livewire cuando un elemento dado es visible en la pag

<div x-intersect="$wire.incrementViewCount()">...</div>

Pasando parámetros

Cualquier parámetro que pase al método $wire también será pasado al método de la clase PHP. Por ejemplo, considere la siguiente acción Livewire:

public function agregarTarea($tarea)
{
$this->tareas[] = $tarea;
}

Dentro de la plantilla Blade de su componente, puede invocar esta acción a través de Alpine, proporcionando el parámetro que debe darse a la acción:

<div x-data="{ tarea: '' }">
<input type="text" x-model="tarea">
<button x-on:click="$wire.agregarTarea(tarea)">Agregar Tarea</button>
</div>

Si un usuario ha escrito «Sacar la basura» en la entrada de texto y pulsa el botón «Añadir Todo», el método agregarTarea() se activará con el parámetro $tarea siendo «Sacar la basura».

Recepción de valores de retorno

Para obtener aún más potencia, las acciones $wire invocadas devuelven una promesa mientras se procesa la solicitud de red. Cuando se recibe la respuesta del servidor, la promesa se resuelve con el valor devuelto por la acción backend.

Por ejemplo, considere un componente Livewire que tiene la siguiente acción:

use App\Models\Publicacion;
public function obtenerNumeroPublicaciones()
{
return Publicacion::count();
}

Utilizando $wire, se puede invocar la acción y resolver su valor devuelto:

<span x-init="$el.innerHTML = await $wire.obtenerNumeroPublicaciones()"></span>

En este ejemplo, si el método obtenerNumeroPublicaciones() devuelve «10», la etiqueta <span> también contendrá «10».

El conocimiento de Alpine no es necesario cuando se utiliza Livewire; sin embargo, es una herramienta extremadamente poderosa y conocer Alpine aumentará su experiencia y productividad en Livewire.

Funciones JavaScript «híbridas» de #Livewire

A veces hay acciones en su componente que no necesitan comunicarse con el servidor y pueden ser escritas más eficientemente usando sólo JavaScript.

En estos casos, en lugar de escribir las acciones dentro de su plantilla Blade u otro archivo, la acción de su componente puede devolver la función JavaScript como una cadena. Si la acción está marcada con el atributo #[Js], se podrá llamar desde el frontend de su aplicación:

Por ejemplo:

<?php
namespace App\Livewire;
use Livewire\Attributes\Js;
use Livewire\Component;
use App\Models\Publicacion;
class BuscarPublicacion extends Component
{
public $query = '';
#[Js]
public function resetQuery()
{
return <<<'JS'
$wire.query = '';
JS;
}
public function render()
{
return view('livewire.buscar-publicacion', [
'publicaciones' => Post::whereTitle($this->query)->get(),
]);
}
}
<div>
<input wire:model.live="query">
<button wire:click="resetQuery">«Restablecer Búsqueda</button>
@foreach ($publicaciones as $publicacion)
<!-- ... -->
@endforeach
</div>

En el ejemplo anterior, al pulsar el botón «Restablecer búsqueda», la entrada de texto se borrará sin enviar ninguna petición al servidor.

Evaluación de expresiones puntuales de JavaScript

Además de designar métodos completos para ser evaluados en JavaScript, puede utilizar el método js() para evaluar expresiones individuales más pequeñas.

Esto suele ser útil para realizar algún tipo de seguimiento del lado del cliente después de realizar una acción del lado del servidor.

Por ejemplo, este es un ejemplo de un componente CrearPublicacion que activa un cuadro de diálogo de alerta del lado del cliente después de que la publicación se guarda en la base de datos:

<?php
namespace App\Livewire;
use Livewire\Component;
class CrearPublicacion extends Component
{
public $titulo = '';
public function save()
{
// ...
$this->js("alert('¡Publicación guardada!')");
}
}

La expresión JavaScript alert('¡Publicación guardada!') se ejecutará ahora en el cliente después de que la publicación se haya guardado en la base de datos del servidor.

Al igual que los métodos #[Js], puede acceder al objeto $wire del componente actual dentro de la expresión.

## Acciones mágicas (Magic actions) Livewire proporciona un conjunto de acciones «mágicas» que le permiten realizar tareas comunes en sus componentes sin definir métodos personalizados. Estas acciones mágicas pueden utilizarse dentro de escuchadores de eventos definidos en sus plantillas Blade.

$parent

La variable mágica $parent permite acceder a las propiedades del componente padre y llamar a las acciones del componente padre desde un componente hijo:

<button wire:click="$parent.eliminarPublicacion({{ $publicacion->id }})">Eliminar</button>

En el ejemplo anterior, si un componente padre tiene una acción eliminarPublicacion(), un hijo puede llamarla directamente desde su plantilla Blade utilizando $parent.eliminarPublicacion().

$set

La acción mágica $set le permite actualizar una propiedad en su componente Livewire directamente desde la plantilla Blade. Para utilizar $set, proporcione la propiedad que desea actualizar y el nuevo valor como argumentos:

<button wire:click="$set('query', '')">Restablecer Búsqueda</button>

En este ejemplo, cuando se hace clic en el botón, se envía una petición de red que establece la propiedad $query del componente en ''.

$refresh

La acción $refresh activa una nueva renderización del componente Livewire. Esto puede ser útil cuando se actualiza la vista del componente sin cambiar ningún valor de propiedad:

<button wire:click="$refresh">Refrescar</button>

Al hacer clic en el botón, el componente volverá a renderizarse, lo que le permitirá ver los últimos cambios en la vista.

$toggle

La acción $toggle se utiliza para cambiar el valor de una propiedad booleana en su componente Livewire:

<button wire:click="$toggle('orden')">
Ordenar {{ $orden ? 'Descendiente' : 'Ascendiente' }}
</button>

En este ejemplo, cuando se pulse el botón, la propiedad $orden del componente alternará entre true y false.

$dispatch

La acción $dispatch permite enviar un evento Livewire directamente en el navegador. A continuación se muestra un ejemplo de un botón que, al hacer clic en él, enviará el evento eliminar-publicacion:

<button type="submit" wire:click="$dispatch('eliminar-publicacion')">Eliminar Publicación</button>

$event

La acción $event puede utilizarse dentro de escuchadores(listeners) de eventos como wire:click. Esta acción te da acceso al evento JavaScript real que se desencadenó, lo que te permite hacer referencia al elemento desencadenante y otra información relevante:

<input type="text" wire:keydown.enter="buscar($event.target.value)">

Cuando se pulsa la tecla intro mientras un usuario está escribiendo en la entrada anterior, el contenido de la entrada se pasará como parámetro a la acción buscar().

Uso de acciones mágicas (magic actions) desde Alpine

También puede invocar acciones mágicas desde Alpine utilizando el objeto $wire. Por ejemplo, puede utilizar el objeto $wire para invocar la acción mágica $refresh:

<button x-on:click="$wire.$refresh()">Refrescar</button>

Evitando re-renderizados

A veces puede haber una acción en su componente sin efectos secundarios que cambiaría la plantilla Blade renderizada cuando se invoca la acción. Si es así, puede omitir la parte de render del ciclo de vida de Livewire añadiendo el atributo #[Renderless] sobre el método de acción.

Para demostrarlo, en el componente MostrarPublicaciones que se muestra a continuación, el «recuento de vistas» se registra cuando el usuario se ha desplazado hasta la parte inferior de la entrada:

<?php
namespace App\Livewire;
use Livewire\Attributes\Renderless;
use Livewire\Component;
use App\Models\Publicacion;
class MostrarPublicacion extends Component
{
public Publicacion $publicacion;
public function mount(Publicacion $publicacion)
{
$this->publicacion = $publicacion;
}
#[Renderless]
public function incrementarVisita()
{
$this->publicacion->incrementarVisita();
}
public function render()
{
return view('livewire.mostrar-publicacion');
}
}
<div>
<h1>{{ $publicacion->titulo }}</h1>
<p>{{ $publicacion->contenido }}</p>
<div x-intersect="$wire.incrementarVisita()"></div>
</div>

El ejemplo anterior utiliza x-intersect, una utilidad de Alpine que llama a la expresión cuando el elemento entra en la ventana gráfica (normalmente se utiliza para detectar cuando un usuario se desplaza a un elemento más abajo en la página).

Como puede ver, cuando un usuario se desplaza hasta la parte inferior de la entrada, se invoca a incrementarVisita(). Como se ha añadido #[Renderless] a la acción, la vista se registra, pero la plantilla no se vuelve a renderizar y ninguna parte de la página se ve afectada.

Si prefiere no utilizar atributos de método o necesita omitir condicionalmente la renderización, puede invocar el método skipRender() en la acción de su componente:

<?php
namespace App\Livewire;
use Livewire\Attributes\Renderless;
use Livewire\Component;
use App\Models\Publicacion;
class MostrarPublicacion extends Component
{
public Publicacion $publicacion;
public function mount(Publicacion $publicacion)
{
$this->publicacion = $publicacion;
}
public function incrementarVisita()
{
$this->publicacion->incrementarVisita();
$this->skipRender();
}
public function render()
{
return view('livewire.mostrar-publicacion');
}
}

Cuestiones de seguridad

Recuerda que cualquier método público en tu componente Livewire puede ser llamado desde el lado del cliente, incluso sin un manejador wire:click asociado que lo invoque. En estos casos, los usuarios pueden activar la acción desde las DevTools del navegador.

A continuación se muestran tres ejemplos de vulnerabilidades fáciles de pasar por alto en componentes Livewire. Cada uno mostrará el componente vulnerable primero y el componente seguro después. Como ejercicio, intente detectar las vulnerabilidades en el primer ejemplo antes de ver la solución.

Si tiene dificultades para detectar las vulnerabilidades y eso le preocupa sobre su capacidad para mantener sus propias aplicaciones seguras, recuerde que todas estas vulnerabilidades se aplican a aplicaciones web estándar que utilizan peticiones y controladores. Si utilizas un método de componente como un proxy para un método de controlador, y sus parámetros como un proxy para la entrada de solicitud, deberías ser capaz de aplicar tu conocimiento existente de seguridad de aplicaciones a tu código Livewire.

Autorizar siempre los parámetros de acción

Al igual que la entrada de solicitud del controlador, es imprescindible autorizar los parámetros de acción, ya que son entradas arbitrarias del usuario.

A continuación se muestra un componente ShowPosts donde los usuarios pueden ver todos sus mensajes en una página. Pueden eliminar cualquier publicación que deseen utilizando uno de los botones «Eliminar» de la publicación.

Esta es una versión vulnerable del componente:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Publicacion;
class MostrarPublicaciones extends Component
{
public function eliminar($id)
{
$publicacion = Publicacion::find($id);
$publicacion->delete();
}
public function render()
{
return view('livewire.mostrar-publicaciones', [
'publicaciones' => Auth::user()->publicaciones,
]);
}
}
<div>
@foreach ($publicaciones as $publicacion)
<div wire:key="{{ $publicacion->id }}">
<h1>{{ $publicacion->titulo }}</h1>
<span>{{ $publicacion->contenido }}</span>
<button wire:click="eliminar({{ $publicacion->id }})">Eliminar</button>
</div>
@endforeach
</div>

Recuerde que un usuario malicioso puede llamar a eliminar() directamente desde una consola JavaScript, pasando cualquier parámetro que desee a la acción. Esto significa que un usuario viendo uno de sus posts puede borrar el post de otro usuario pasando el ID del post que no es suyo a eliminar().

Para protegernos contra esto, necesitamos autorizar que el usuario es el propietario de la entrada que va a ser borrada:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Publicacion;
class MostrarPublicaciones extends Component
{
public function eliminar($id)
{
$publicacion = Publicacion::find($id);
$this->authorize('eliminar', $publicacion);
$publicacion->delete();
}
public function render()
{
return view('livewire.mostrar-publicaciones', [
'publicaciones' => Auth::user()->publicaciones,
]);
}
}

Autorizar siempre del lado del servidor

Al igual que los controladores estándar de Laravel, las acciones de Livewire pueden ser invocadas por cualquier usuario, incluso si no existe un affordance para invocar la acción en la UI.

Considere el siguiente componente BuscarPublicaciones donde cualquier usuario puede ver todos los mensajes en la aplicación, pero sólo los administradores pueden eliminar un mensaje:

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Publicacion;
class BuscarPublicaciones extends Component
{
public function eliminarPublicacion($id)
{
$publicacion = Publicacion::find($id);
$publicacion->delete();
}
public function render()
{
return view('livewire.buscar-publicaciones', [
'publicaciones' => Publicacion::all(),
]);
}
}
<div>
@foreach ($publicaciones as $publicacion)
<div wire:key="{{ $publicacion->id }}">
<h1>{{ $publicacion->title }}</h1>
<span>{{ $publicacion->content }}</span>
@if (Auth::user()->isAdmin())
<button wire:click="eliminarPublicacion({{ $publicacion->id }})">Eliminar Publicacion</button>
@endif
</div>
@endforeach
</div>

Como puedes ver, sólo los administradores pueden ver el botón Eliminar Publicacion<; sin embargo, cualquier usuario puede llamar a eliminarPublicacion() en el componente desde las DevTools del navegador.

Para parchear esta vulnerabilidad, necesitamos autorizar la acción en el servidor de la siguiente manera:

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Publicacion;
class BuscarPublicaciones extends Component
{
public function eliminarPublicacion($id)
{
if (! Auth::user()->isAdmin) {
abort(403);
}
$publicacion = Publicacion::find($id);
$publicacion->delete();
}
public function render()
{
return view('livewire.buscar-publicaciones', [
'publicaciones' => Publicacion::all(),
]);
}
}

Con este cambio, sólo los administradores pueden eliminar una entrada de este componente.

Mantenga los métodos peligrosos protegidos o privados

Cada método público dentro de tu componente Livewire es invocable desde el cliente. Incluso los métodos a los que no has hecho referencia dentro de un manejador wire:click. Para evitar que un usuario llame a un método que no está pensado para ser llamado desde el lado del cliente, debe marcarlos como protected o private. Al hacerlo, restringes la visibilidad de ese método sensible a la clase del componente y sus subclases, asegurando que no puedan ser llamados desde el lado del cliente.

Considere el ejemplo de BuscarPublicaciones que discutimos previamente, donde los usuarios pueden ver todos los mensajes en su aplicación, pero sólo los administradores pueden borrar mensajes. En la sección Autorizar siempre del lado del servidor, hicimos la acción segura añadiendo autorización del lado del servidor. Ahora imagina que refactorizamos la eliminación de la entrada en un método dedicado, como podrías hacer para simplificar tu código:

// Advertencia: Este fragmento demuestra lo que NO se debe hacer...
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Publicacion;
class BuscarPublicaciones extends Component
{
public function eliminarPublicacion($id)
{
if (! Auth::user()->isAdmin) {
abort(403);
}
$this->delete($id);
}
public function eliminar($publicacionID)
{
$publicacion = Publicacion::find($publicacionID);
$publicacion->delete();
}
public function render()
{
return view('livewire.browse-posts', [
'publicaciones' => Publicacion::all(),
]);
}
}
<div>
@foreach ($publicaciones as $publicacion)
<div wire:key="{{ $publicacion->id }}">
<h1>{{ $publicacion->titulo }}</h1>
<span>{{ $publicacion->contenido }}</span>
<button wire:click="eliminarPublicacion({{ $publicacion->id }})">Eliminar</button>
</div>
@endforeach
</div>

Como puedes ver, hemos refactorizado la lógica de borrado de posts en un método dedicado llamado eliminar(). Aunque este método no está referenciado en ninguna parte de nuestra plantilla, si un usuario tuviera conocimiento de su existencia, podría llamarlo desde las DevTools del navegador porque es public.

Para remediar esto, podemos marcar el método como protected o private. Una vez marcado el método como protected o private, se lanzará un error si un usuario intenta invocarlo:

// Advertencia: Este fragmento demuestra lo que NO se debe hacer...
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Publicacion;
class BuscarPublicaciones extends Component
{
public function eliminarPublicacion($id)
{
if (! Auth::user()->isAdmin) {
abort(403);
}
$this->delete($id);
}
protected function eliminar($publicacionID)
{
$publicacion = Publicacion::find($publicacionID);
$publicacion->delete();
}
public function render()
{
return view('livewire.browse-posts', [
'publicaciones' => Publicacion::all(),
]);
}
}