Saltearse al contenido

Propiedades

Las propiedades almacenan y gestionan datos dentro de sus componentes Livewire. Se definen como propiedades públicas en las clases de componentes y se puede acceder a ellas y modificarlas tanto en el lado del servidor como en el lado del cliente.

Inicializando propiedades

Puede establecer valores iniciales para las propiedades dentro del método mount() de su componente.

Considere el siguiente ejemplo:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class ListaTareas extends Component
{
public $tareas = [];
public $tarea = '';
public function mount()
{
$this->tareas = Auth::user()->tareas;
}
// ...
}

En este ejemplo, hemos definido un array de tareas vacío y lo hemos inicializado con los todos existentes del usuario autenticado. Ahora, cuando el componente se muestra por primera vez, todos los todos existentes en la base de datos se muestran al usuario.

Asignación masiva

A veces inicializar muchas propiedades en el método mount() puede parecer verboso. Para ayudar con esto, Livewire proporciona una forma conveniente de asignar múltiples propiedades a la vez a través del método fill(). Pasando una matriz asociativa de nombres de propiedades y sus respectivos valores, puede establecer varias propiedades simultáneamente y reducir las líneas repetitivas de código en mount().

Por ejemplo:

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Post;
class UpdatePost extends Component
{
public $post;
public $title;
public $description;
public function mount(Post $post)
{
$this->post = $post;
$this->fill(
$post->only('title', 'description'),
);
}
// ...
}

Dado que $post->only(...) devuelve una matriz asociativa de atributos y valores del modelo basados en los nombres que le pases, las propiedades $title y $description se establecerán inicialmente al title y description del modelo $post de la base de datos sin tener que establecer cada una individualmente.

Vinculación de datos (Data Binding)

Livewire admite la vinculación bidireccional de datos a través del atributo HTML wire:model. Esto permite sincronizar fácilmente los datos entre las propiedades del componente y las entradas HTML, manteniendo sincronizados la interfaz de usuario y el estado del componente.

Utilicemos la directiva wire:model para vincular la propiedad $tarea de un componente ListaTareas a un elemento input básico:

<?php
namespace App\Livewire;
use Livewire\Component;
class ListaTareas extends Component
{
public $tareas = [];
public $tarea = '';
public function almacenar()
{
$this->tareas[] = $this->tarea;
$this->tarea = '';
}
// ...
}
<div>
<input type="text" wire:model="tarea" placeholder="Tarea...">
<button wire:click="almacenar">Agregar Tarea</button>
<ul>
@foreach ($tareas as $tarea)
<li>{{ $tarea }}</li>
@endforeach
</ul>
</div>

En el ejemplo anterior, el valor de la entrada de texto se sincronizará con la propiedad $tarea en el servidor cuando se pulse el botón «Agregar Tarea».

Esto es sólo rascar la superficie de wire:model. Para más información sobre la vinculación de datos, consulta nuestra documentación sobre formularios.

Restablecer propiedades

A veces, puede que necesite restablecer sus propiedades a su estado inicial después de que el usuario realice una acción. En estos casos, Livewire proporciona un método reset() que acepta uno o más nombres de propiedades y restablece sus valores a su estado inicial.

En el ejemplo siguiente, podemos evitar la duplicación de código utilizando $this->reset() para restablecer el campo tarea después de que se pulse el botón «Añadir Tarea»:

<?php
namespace App\Livewire;
use Livewire\Component;
class AdministrarTareas extends Component
{
public $tareas = [];
public $tarea = '';
public function addTodo()
{
$this->tareas[] = $this->tarea;
$this->reset('tarea');
}
// ...
}

En el ejemplo anterior, después de que un usuario haga clic en «Añadir Tarea», el campo de entrada que contiene la tarea que se acaba de añadir se borrará, permitiendo al usuario escribir una nueva tarea.

Extraer propiedades

Alternativamente, puede utilizar el método pull() para restablecer y recuperar el valor en una sola operación.

Aquí está el mismo ejemplo de arriba, pero simplificado con pull():

<?php
namespace App\Livewire;
use Livewire\Component;
class AdministrarTareas extends Component
{
public $tareas = [];
public $tarea = '';
public function addTodo()
{
$this->tareas[] = $this->pull('tarea');
}
// ...
}

El ejemplo anterior extrae un único valor, pero pull() también puede utilizarse para restablecer y recuperar (como un par clave-valor) todas o algunas propiedades:

// Lo mismo que $this->all() y $this->reset();
$this->pull();
// Lo mismo que $this->only(...) y $this->reset(...);
$this->pull(['title', 'content']);

Tipos de propiedades soportados

Livewire soporta un conjunto limitado de tipos de propiedades debido a su enfoque único para gestionar los datos de los componentes entre peticiones al servidor.

Cada propiedad en un componente Livewire es serializada o «deshidratada» en JSON entre peticiones, y luego «hidratada» de JSON de vuelta a PHP para la siguiente petición.

Este proceso de conversión bidireccional tiene ciertas limitaciones, restringiendo los tipos de propiedades con los que Livewire puede trabajar.

Tipos primitivos

Livewire soporta tipos primitivos como cadenas, enteros, etc. Estos tipos pueden convertirse fácilmente a y desde JSON, haciéndolos ideales para su uso como propiedades en componentes Livewire.

Livewire soporta los siguientes tipos de propiedades primitivas: Array, String, Integer, Float, Boolean y Null.

class TodoList extends Component
{
public $todos = []; // Array
public $todo = ''; // String
public $maxTodos = 10; // Integer
public $showTodos = false; // Boolean
public $todoFilter; // Null
}

Tipos PHP comunes

Además de los tipos primitivos, Livewire soporta tipos de objetos PHP comunes usados en aplicaciones Laravel. Sin embargo, es importante tener en cuenta que estos tipos serán deshidratados en JSON e hidratados de vuelta a PHP en cada petición. Esto significa que la propiedad puede no preservar valores en tiempo de ejecución tales como cierres. Además, la información sobre el objeto, como los nombres de las clases, puede estar expuesta a JavaScript.

Tipos PHP soportados:

TipoNombre completo de la clase
BackedEnumBackedEnum
CollectionIlluminate\Support\Collection
Eloquent CollectionIlluminate\Database\Eloquent\Collection
ModelIlluminate\Database\Eloquent\Model
DateTimeDateTime
CarbonCarbon\Carbon
Stringable Illuminate\Support\Stringable

He aquí un ejemplo rápido de configuración de propiedades como estos distintos tipos:

public function mount()
{
$this->todos = collect([]); // Collection
$this->todos = Todos::all(); // Eloquent Collection
$this->todo = Todos::first(); // Model
$this->date = new DateTime('now'); // DateTime
$this->date = new Carbon('now'); // Carbon
$this->todo = str(''); // Stringable
}

Soporte de tipos personalizados

Livewire permite a su aplicación soportar tipos personalizados a través de dos potentes mecanismos:

  • Wireables
  • Synthesizers

Los Wireables son sencillos y fáciles de usar para la mayoría de las aplicaciones, por lo que los exploraremos a continuación. Si eres un usuario avanzado o un autor de paquetes que desea más flexibilidad, los Synthesizers son el camino a seguir.

Wireables

Wireables es cualquier clase de tu aplicación que implemente la interfaz Wireable.

Por ejemplo, imaginemos que tienes un objeto Cliente en tu aplicación que contiene los datos primarios sobre un cliente:

class Cliente
{
protected $nombre;
protected $edad;
public function __construct($nombre, $edad)
{
$this->nombre = $nombre;
$this->edad = $edad;
}
}

Al intentar establecer una instancia de esta clase a una propiedad de un componente Livewire se producirá un error que le indicará que el tipo de propiedad Cliente no está soportado:

class mostrarCliente extends Component
{
public Cliente $cliente;
public function mount()
{
$this->cliente = new Cliente('Juan', 29);
}
}

Sin embargo, puedes resolver esto implementando la interfaz Wireable y añadiendo un método toLivewire() y fromLivewire() a tu clase. Estos métodos indican a Livewire cómo convertir propiedades de este tipo en JSON y viceversa:

use Livewire\Wireable;
class Cliente implements Wireable
{
protected $nombre;
protected $edad;
public function __construct($nombre, $edad)
{
$this->nombre = $nombre;
$this->edad = $edad;
}
public function toLivewire()
{
return [
'nombre' => $this->nombre,
'edad' => $this->edad,
];
}
public static function fromLivewire($value)
{
$nombre = $value['nombre'];
$edad = $value['edad'];
return new static($nombre, $edad);
}
}

Ahora puedes establecer libremente objetos Cliente en tus componentes Livewire y Livewire sabrá cómo convertir estos objetos a JSON y de vuelta a PHP.

Como se mencionó anteriormente, si quieres soportar tipos de forma más global y potente, Livewire ofrece Sintetizadores, su mecanismo interno avanzado para manejar diferentes tipos de propiedades. Aprende más sobre Sintetizadores.

Accediendo a las propiedades desde JavaScript

Dado que las propiedades de Livewire también están disponibles en el navegador a través de JavaScript, puede acceder y manipular sus representaciones JavaScript desde AlpineJS.

Alpine es una librería JavaScript ligera que se incluye con Livewire. Alpine proporciona una manera de construir interacciones ligeras en sus componentes Livewire sin tener que hacer viajes completos al servidor.

Internamente, el frontend de Livewire está construido sobre Alpine. De hecho, cada componente Livewire es en realidad un componente Alpine. Esto significa que puedes utilizar libremente Alpine dentro de tus componentes Livewire.

El resto de esta página asume una familiaridad básica con Alpine. Si no está familiarizado con Alpine, eche un vistazo a la documentación de Alpine.

Acceso a las propiedades

Livewire expone un objeto mágico $wire a Alpine. Puede acceder al objeto $wire desde cualquier expresión Alpine dentro de su componente Livewire.

El objeto $wire puede ser tratado como una versión JavaScript de su componente Livewire. Tiene las mismas propiedades y métodos que la versión PHP de su componente, pero también contiene algunos métodos dedicados para realizar funciones específicas en su plantilla.

Por ejemplo, podemos utilizar $wire para mostrar un recuento de caracteres en vivo del campo de entrada tareas:

<div>
<input type="text" wire:model="tarea" />
Longitud de la tarea:
<h2 x-text="$wire.tarea.length"></h2>
</div>

A medida que el usuario escribe en el campo, la longitud de los caracteres de la tarea que se está escribiendo se mostrará y actualizará en la página, todo ello sin enviar una petición de red al servidor.

Si lo prefiere, puede utilizar el método más explícito .get() para conseguir lo mismo:

<div>
<input type="text" wire:model="tarea" />
Longitud de la tarea:
<h2 x-text="$wire.get('tarea').length"></h2>
</div>

Manipulación de propiedades

De forma similar, puede manipular las propiedades de sus componentes Livewire en JavaScript utilizando $wire.

Por ejemplo, vamos a añadir un botón «Borrar» al componente ListaTareas para permitir al usuario restablecer el campo de entrada utilizando sólo JavaScript:

<div>
<input type="text" wire:model="tarea" />
<button x-on:click="$wire.tarea = ''">Borrar</button>
</div>

Después de que el usuario haga clic en «Borrar», la entrada se restablecerá a una cadena vacía, sin enviar una solicitud de red al servidor.

En la siguiente petición, el valor de $tarea del lado del servidor se actualizará y sincronizará.

Si lo prefiere, también puede utilizar el método más explícito .set() para establecer propiedades en el lado del cliente. Sin embargo, debe tener en cuenta que el uso de .set() por defecto desencadena inmediatamente una petición de red y sincroniza el estado con el servidor. Si se desea esto, entonces esta es una excelente API:

<button x-on:click="$wire.set('tarea', '')">Borrar</button>

Para actualizar la propiedad sin enviar una petición de red al servidor, puede pasar un tercer parámetro bool. Esto aplazará la solicitud de red y en una solicitud posterior, el estado se sincronizará en el lado del servidor:

<button x-on:click="$wire.set('tarea', '', false)">Borrar</button>

Cuestiones de seguridad

Aunque las propiedades Livewire son una característica poderosa, hay algunas consideraciones de seguridad que debe tener en cuenta antes de usarlas.

En resumen, trate siempre las propiedades públicas como entradas de usuario - como si fueran entradas de solicitud de un punto final tradicional. A la luz de esto, es esencial validar y autorizar las propiedades antes de persistirlas en una base de datos - al igual que lo haría cuando trabaja con entradas de solicitud en un controlador.

No confíes en los valores de las propiedades

Para demostrar cómo descuidar la autorización y validación de propiedades puede introducir agujeros de seguridad en tu aplicación, el siguiente componente UpdatePost es vulnerable a un ataque:

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Post;
class UpdatePost extends Component
{
public $id;
public $titulo;
public $contenido;
public function mount(Post $post)
{
$this->id = $post->id;
$this->titulo = $post->titulo;
$this->contenido = $post->contenido;
}
public function update()
{
$post = Post::findOrFail($this->id);
$post->update([
'titulo' => $this->titulo,
'contenido' => $this->contenido,
]);
session()->flash('message', 'Post actualizado!');
}
public function render()
{
return view('livewire.update-post');
}
}
<form wire:submit="update">
<input type="text" wire:model="titulo" />
<input type="text" wire:model="contenido" />
<button type="submit">Actualizar</button>
</form>

A primera vista, este componente puede parecer completamente correcto. Pero, veamos como un atacante podría usar el componente para hacer cosas no autorizadas en tu aplicación.

Debido a que estamos almacenando el id de la entrada como una propiedad pública en el componente, puede ser manipulada en el cliente de la misma manera que las propiedades titulo y contenido.

No importa que no hayamos escrito un input con wire:model=«id». Un usuario malintencionado puede fácilmente cambiar la vista a lo siguiente usando las DevTools de su navegador:

<form wire:submit="update">
<input type="text" wire:model="id" />
<input type="text" wire:model="titulo" />
<input type="text" wire:model="contenido" />
<button type="submit">Actualizar</button>
</form>

Ahora el usuario malicioso puede actualizar la entrada id al ID de un modelo de entrada diferente. Cuando se envíe el formulario y se llame a update(), Post::findOrFail() devolverá y actualizará una entrada de la que el usuario no es propietario.

Para prevenir este tipo de ataque, podemos usar una o ambas de las siguientes estrategias:

  • Autorizar la entrada
  • Bloquear la propiedad de las actualizaciones

Autorizar la entrada

Debido a que $id puede ser manipulado del lado del cliente con algo como wire:model, al igual que en un controlador, podemos utilizar la autorización de Laravel para asegurarnos de que el usuario actual puede actualizar el post:

public function update()
{
$post = Post::findOrFail($this->id);
$this->authorize('update', $post);
$post->update(...);
}

Si un usuario malicioso muta la propiedad $id, la autorización añadida lo detectará y lanzará un error.

Bloquear la propiedad

Livewire también permite «bloquear» propiedades para evitar que sean modificadas en el lado del cliente. Puede «bloquear» una propiedad para evitar que sea manipulada por el cliente utilizando el atributo #[Locked]:

use Livewire\Attributes\Locked;
use Livewire\Component;
class UpdatePost extends Component
{
#[Locked]
public $id;
// ...
}

Ahora, si un usuario intenta modificar $id en el front-end, se producirá un error.

Usando #[Locked], puedes asumir que esta propiedad no ha sido manipulada en ningún lugar fuera de la clase de tu componente.

Para más información sobre el bloqueo de propiedades, consulte la documentación sobre propiedades bloqueadas.

Modelos Eloquent y bloqueo

Cuando un modelo Eloquent se asigna a una propiedad de un componente Livewire, Livewire bloqueará automáticamente la propiedad y se asegurará de que el ID no se cambie, para que estés a salvo de este tipo de ataques:

<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Post;
class UpdatePost extends Component
{
public Post $post;
public $titulo;
public $contenido;
public function mount(Post $post)
{
$this->post = $post;
$this->titulo = $post->titulo;
$this->contenido = $post->contenido;
}
public function update()
{
$this->post->update([
'titulo' => $this->titulo,
'contenido' => $this->contenido,
]);
session()->flash('message', 'Post actualizado!');
}
public function render()
{
return view('livewire.update-post');
}
}

Las propiedades exponen información del sistema al navegador

Otra cosa esencial a recordar es que las propiedades Livewire son serializadas o «deshidratadas» antes de ser enviadas al navegador. Esto significa que sus valores son convertidos a un formato que puede ser enviado a través del cable y entendido por JavaScript. Este formato puede exponer información sobre su aplicación al navegador, incluyendo los nombres y nombres de clase de sus propiedades.

Por ejemplo, suponga que tiene un componente Livewire que define una propiedad pública llamada $post. Esta propiedad contiene una instancia de un modelo Post de su base de datos. En este caso, el valor deshidratado de esta propiedad enviado a través del cable podría tener este aspecto:

{
"type": "model",
"class": "AppModelsPost",
"key": 1,
"relationships": []
}

Como puedes ver, el valor deshidratado de la propiedad $post incluye el nombre de clase del modelo (App\Models\Post) así como el ID y cualquier relación que haya sido eager-loaded.

Si no quieres exponer el nombre de clase del modelo, puedes utilizar la funcionalidad «morphMap» de Laravel desde un proveedor de servicios para asignar un alias al nombre de clase de un modelo

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Relations\Relation;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Relation::morphMap([
'post' => 'App\Models\Post',
]);
}
}

Ahora, cuando el modelo Eloquent es «deshidratado» (serializado), el nombre original de la clase no será expuesto, sólo el alias «post»:

{
"type": "model",
"class": "AppModelsPost", // Antes
"class": "post", // AHORA
"key": 1,
"relationships": []
}

Las restricciones Eloquent no se conservan entre peticiones

Normalmente, Livewire es capaz de preservar y recrear propiedades del lado del servidor entre peticiones; sin embargo, hay ciertos escenarios en los que preservar valores es imposible entre peticiones.

Por ejemplo, cuando se almacenan colecciones Eloquent como propiedades Livewire, las restricciones de consulta adicionales como select(...) no se volverán a aplicar en solicitudes posteriores.

Para demostrarlo, considere el siguiente componente MostrarTareas con una restricción select() aplicada a la colección Tareas de Eloquent:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class MostrarTareas extends Component
{
public $tareas;
public function mount()
{
$this->tareas = Auth::user()
->tareas()
->select(['titulo', 'contenido'])
->get();
}
public function render()
{
return view('livewire.show-todos');
}
}

Cuando este componente se carga inicialmente, la propiedad $tareas se establecerá en una colección Eloquent de los todos del usuario; sin embargo, sólo los campos de título y contenido de cada fila de la base de datos se habrán consultado y cargado en cada uno de los modelos.

Cuando Livewire hidrate el JSON de esta propiedad de vuelta a PHP en una petición posterior, la restricción select se habrá perdido.

Para asegurar la integridad de las consultas Eloquent, le recomendamos que utilice propiedades computadas en lugar de propiedades.

Las propiedades computadas son métodos de su componente marcados con el atributo #[Computed]. Se puede acceder a ellas como una propiedad dinámica que no se almacena como parte del estado del componente, sino que se evalúa sobre la marcha.

Aquí está el ejemplo anterior reescrito usando una propiedad computada:

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;
class MostrarTareas extends Component
{
#[Computed]
public function tareas()
{
return Auth::user()
->tareas()
->select(['titulo', 'contenido'])
->get();
}
public function render()
{
return view('livewire.show-todos');
}
}

Así es como se accede a estos todos desde la vista Blade:

<ul>
@foreach ($this->tareas as $tarea)
<li>{{ $tarea }}</li>
@endforeach
</ul>

Fíjate, dentro de tus vistas, sólo puedes acceder a propiedades calculadas en el objeto $thi de la siguiente manera: $this->tareas.

También puedes acceder a $todos desde dentro de tu clase. Por ejemplo, si tuvieras una acción markAllAsComplete():

<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;
class MostrarTareas extends Component
{
#[Computed]
public function tareas()
{
return Auth::user()
->tareas()
->select(['titulo', 'contenido'])
->get();
}
public function markAllComplete()
{
$this->tareas->each->complete();
}
public function render()
{
return view('livewire.show-todos');
}
}

Puede que te preguntes ¿por qué no llamar a $this->tareas() como método directamente donde lo necesites? ¿Por qué usar #[Computed] en primer lugar?

La razón es que las propiedades computadas tienen una ventaja de rendimiento, ya que se almacenan automáticamente en caché después de su primer uso durante una única petición. Esto significa que puede acceder libremente a $this->tareas dentro de su componente y tener la seguridad de que el método real sólo se llamará una vez, de forma que no ejecute una consulta costosa varias veces en la misma petición.

Para más información, visite la documentación sobre propiedades computadas.