PHP Livewire
Sumario
Introducción
- Livewire permite o uso de compoñentes integrados en Laravel nos que se establece unha comunicación bidireccional (podemos enviar información desde o cliente (HTML) ao servidor (controlador) e viceversa sen necesidade de recargar completamente a páxina).
- É o mesmo concepto que se empregaba en AJAX e que agora se usa con FETCH, pero integrado no framework de Laravel.
- IMPORTANTE: Cando vexades a documentación mirade que version estades a consultar xa que dende a versión 2 á versión 3 houbo cambios.
- Unha vez instalado en Laravel podedes consultar a versión instalada buscando a entrada livewire/livewire no arquivo composor.json no raíz do proxecto.
- Polo de agora (versión 10-12) estamos a traballar coa versión 3.x de Livewire.
Instalación
- No directorio raíz do proxecto Laravel escribe: composer require livewire/livewire
- Unha vez instalado en Laravel podedes consultar a versión instalada buscando a entrada livewire/livewire no arquivo composor.json no raíz do proxecto.
- Polo de agora (versión 10-12) estamos a traballar coa versión 3.x de Livewire.
Validación de campos
- Imos crear a táboa marcas e imos crear unha relación entre perfumes e marcas.
- A nivel de mysql:
CREATE TABLE perfumes.marcas ( `id` int NOT NULL, `descripcion` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; INSERT INTO `marcas` (`id`, `descripcion`) VALUES (1, 'Descripción actualizada da marca con id=1'), (2, 'ADIDAS'), (3, 'ARAMIS');
- Táboa perfumes
CREATE TABLE perfumes.perfumes ( `id` int NOT NULL, `descripcion` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_spanish2_ci NOT NULL, `prezo` decimal(8,2) NOT NULL, `data_compra` date DEFAULT NULL, `marca_id` int DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci; ALTER TABLE perfumes.perfumes ADD PRIMARY KEY (`id`), ADD KEY `fk_PERFUMES_1_idx` (`id`), ADD KEY `fk_PERFUMES_MARCA_idx` (`marca_id`); INSERT INTO perfumes.perfumes (`id`, `descripcion`, `prezo`, `data_compra`, `marca_id`) VALUES (18, 'Best Aramis', '54.32', '2024-01-18', 2), (19, 'Novo perfume da marca con id 1', '100.22', '2024-01-22', 1);
- Creamos o modelo, ruta, controlador e polo de agora imos facer a vista para crear unha nova marca.
- Lembrar que esta parte ven ser a continuación do feito na UD6 - Modelo Vista Controlador de Laralve.
- Dende o terminal escribimos: php artisan make:model Marca --resource
- Arquivo: app/models/Marca.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use App\Models\Perfume; class Marca extends Model { public $timestamps = false; protected $fillable = [ 'descripcion', ]; public function perfumes(){ return $this->hasMany(Perfume::class, 'marca_id','id'); // Non necesitamos poñer xa que 'marca_id','id' cumpren as normas de nomeamento } }
- Arquivo: app/models/Perfume.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Casts\Attribute; use Carbon\Carbon; class Perfume extends Model { protected $table = 'perfumes'; // Indicamos a táboa relacionada con este modelo. protected $primaryKey = 'id'; // Indicamos a clave primaria da táboa public $timestamps = false; // Por defecto laravel inclúe dous campos (created_at e updated_at) de tipo datetime cando se fai unha operación de inserción e actualización sobre unha fila. Con isto indicamos que a táboa non ten eses campos. protected $casts = [ // Cando accedamos a unha fila empregando este modelo, se poñemos $fila->data_compra teremos un string. Se poñemos o cast, obteremos unha instancia da [https://carbon.nesbot.com/docs/ clase Carbon] de Laravel que nos permite facer operacións e formatear a data. 'data_compra' => 'date', // Tamén se pode empregar datetime. Devolve un Carbon ]; protected $fillable = [ // Isto xa o veremos. O que indica é que cando facemos unha operación masiva de inserción ou un insert da forma create(datos) soamente deixa enviar en datos (que é un array) as columnas indicadas nesta propiedade (as columnas indicadas son descripcion, prezo e data_compra). Faise por motivos de seguridade. 'descripcion', 'prezo', 'data_compra' ]; protected function DataCompra(): Attribute { return Attribute::make( get: fn($value) => $value ? Carbon::parse($value)->format('d/m/Y') : null, // Cando empregamos a data da forma echo($perfume->data_compra); convirte o obxecto Carbon a unha cadea có formato correcto set: fn($value) => $value ? Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d') : null // Cando empregamos a data da forma: $perfume->data_compra = "10/01/2025" o gardará ); } protected function Prezo(): Attribute { return Attribute::make( // Cuando accedemos al valor del campo, convertimos el punto en coma get: fn ($value) => number_format($value, 2, ',', '.'), // Cuando se asigna el valor, reemplazamos la coma por un punto set: fn ($value) => str_replace(',', '.', $value), ); } }
- Arquivo: routes/web.php
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\PerfumeController; use App\Http\Controllers\MarcaController; Route::resource('perfumes', PerfumeController::class); Route::resource('marcas', MarcaController::class);
- O arquivo de MarcaController xa se crearia con todo o código necesario para a xestión.
- Arquivo resources/views/marcas/form_alta_marcas.blade.php (creado o cartafol marcas dentro de view)
@extends('plantilla'); @section('pageTitle', 'Alta de marcas') @section('content') <h2>Alta de Marcas</h2> @if (session()->has('idPerfume')) <h4>Creado un novo rexistro co id: {{session('idPerfume')}}</h4> @endif @if(count($errors) > 0) <div class="errors"> <ul> @foreach($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif @include('mensaxes.alertas_javascript') @endsection
- Creamos o compoñente Livewire con esta orde: php artisan make:livewire marca
- Isto creará dous arquivos en Laravel:
- CLASS: app/Livewire/Marca.php => Ven a ser o controlador que vimos ata o de agora en Laravel.
- VIEW: resources/views/livewire/marca.blade.php => Ven a ser a vista que vai visualizar Laravel no cliente.
- Arquivo: app/Livewire/Marca.php
<?php namespace App\Livewire; use Livewire\Component; class Marca extends Component { public function render() { return view('livewire.marca'); } }
- Básicamente indicamos en render a vista que vai enviar ao cliente.
- Arquivo resource/views/Livewire/marca.blade.php
<form method="post" action="{{route('marcas.store')}}" name="formulario_marcas"> @csrf <div> Descripción:<input type="text" maxlength="45" size="45" required name="descripcion" /> </div> <div> <input type="submit" name="btnAlta" value="Alta" onClick="return confirm('Estás seguro de dar de alta a nova marca ');"/> <input type="reset" name="btnBorrar" value="Limpar" /> </div> </form>
- <u<Nota: Non separamos os campos nun arquivo diferente xa que en livewire as operacións de alta e modificación vanse facer sobre a mesma vista.
- Agora para incluir o compoñente Livewire nunha view de Laravel temos que poñer:
- Arquivo resouces/views/marcas/form_alta_marca.blade.php
@extends('plantilla'); @section('pageTitle', 'Alta de Marcas') @section('content') <h2>Alta de novos perfumes</h2> <livewire:marca /> ................
- Se agora poñemosm na url: http://localhost:8080/marcas/create veremos que aparecerá unha vista tal cual aparecía cando fixemos o de perfume, pero a diferenza de perfumes, esta vista (os campos e form) se xestionan cun compoñente Livewire.
- Agora imos ver os cambios que temos que facer para xestionar os campos do formulario.
- Primero imos definir no controlador do compoñente livewire os campos que imos xestionar. No noso caso está a descripcion.
<?php namespace App\Livewire; use Livewire\Component; class Marca extends Component { public $descripcion = "Valor inicial"; // O valor inicial non é necesasrio. Soamente para que vexades como dito valor se carga na vista. public function render() { return view('livewire.marca'); } }
- Modificamos o campo descripcion.
- Arquivo: resources/views/livewire/marca.blade.php
<div> Descripción:<input type="text" maxlength="45" required size="45" wire:model="descripcion" name="descripcion" /> </div>
- wire:model="descripcion": O que estamos a dicir con isto é que este input está vinculado á propiedade $descripcion definida no compoñente Livewire. Calquera modificación realizada polo usuario no cliente (na páxina web) actualizará automaticamente o valor da propiedade no servidor grazas á reactividade proporcionada por Livewire. Ademais, se a propiedade no servidor cambia, ese cambio tamén se reflectirá no cliente.
- Se vos dades conta non temos a propiedade value como tiñamos en perfumes.
- Se agora cargades a páxina veredes como o valor inicial se carga no input.
- Agora imos a facer a validación no servidor. Para iso temos que definir unha propiedade $rules coas regras de validación que xa empregamos en Laravel.
- Ademáis imos crear un método ao que vai chamar o formulario cando faga submit e que fará a validación dos campos. No exemplo imos chamar ao método gardarMarca.
- Arquivo: app/Livewire/Marca.php
<?php namespace App\Livewire; use Illuminate\Support\Facades\Log; use Livewire\Component; class Marca extends Component { public $descripcion = "Valor inicial"; protected $rules = [ 'descripcion' => 'required|string|min:5|max:45', ]; public function render() { return view('livewire.marca'); } public function gardarMarca(){ $this->validate(); Log::info('Pasa a validación'); } }
- Para depurar o funcionamento dos compoñente livewire podemos empregar o facade Log::info e ver as mensaxes no arquivo /storage/logs/laravel.log (podedes borrar o contido antes de executar para ter todo limpo ao principio).
- Para chamar a ese método temos que modificar a vista do compoñente livewire.
- Arquivo: resources/views/livewire/marca.blade.php
<form wire:submit.prevent="gardarMarca"> @csrf <div> Descripción:<input type="text" maxlength="45" size="45" required wire:model="descripcion" name="descripcion" /> </div> <div> <input type="submit" name="btnAlta" value="Alta" onClick="return confirm('Estás seguro de dar de alta a nova marca ');"/> <input type="reset" name="btnBorrar" value="Limpar" /> </div> </form>
- Con isto wire:submit.prevent="gardarMarca" estamos a decir que cando faga o submit chame de forma asíncrona ao controlador do compoñente livewire ao método gardarMarca.
- Nota: Podemos asociar calquera click de botón directamente ao un método no controlador do livewire, da forma: <button wire:click="gardarMarca">+</button>
- Tamén imos visualizar os erros de validación que poida enviar o controlador Livewire se non pasa a validación. Para iso temos que empregar a directiva @error sobre cada un dos campos que teñen validación. No exemplo se coloca ao lado do input, pero poderían ir en calquera parte.
- Arquivo: resources/views/livewire/marca.blade.php
<div> Descripción:<input type="text" maxlength="45" required size="45" wire:model="descripcion" name="descripcion" /> @error('descripcion') <span class="error">{{ $message }}</span> @enderror </div>
- Se queremos personalizar as mensaxes de erro:
- Arquivo: app/Livewire/Marca.php
<?php ........... protected $messages = [ 'descripcion.required' => 'La descripción es obligatoria.', 'descripcion.string' => 'La descripción debe ser una cadena de texto.', 'descripcion.min' => 'La descripción debe tener al menos 5 caracteres.', 'descripcion.max' => 'La descripción no puede tener más de 45 caracteres.', ]; ..............
- Fixerase que neste caso a validación faise cando o usuario preme o botón de submit.
- Se queremos que se faga a validación cando o usuario cambio o contido da caixa de texto temos que:
- Empregar o método update($propiedade) que será chamado de forma automáticamente cando o campo teña unha modificación. Dito método fará a validación da propiedade. Temos que chamar ao método validatyOnly.
- Cambiar a propiedade wire:model da caixa de texto por wire:model.live
- Lembrar que estas chamadas fanse ao servidor polo que pode ser que non nos interese para aforrar recursos e comunicacións entre cliente e o servidor.
- Arquivo: resources/views/livewire/marca.blade.php
- MOI IMPORTANTE: Tedes que cambiar a propiedade wire:model por wire:model.live
<div> Descripción:<input type="text" maxlength="45" size="45" required wire:model.live="descripcion" name="descripcion" /> @error('descripcion') <span class="error">{{ $message }}</span> @enderror </div>
- Arquivo: app/Livewire/Marca.php
.............. public function updated($propiedade){ $this->validateOnly($propiedade); } .............
- Ao facer isto, cada vez que se preme no imput fará a validación.
- Lembrar que poñer isto non quita que teñades a chamada $this->validate() cando se vaia a dar de alta/modificar os datos
- Mirade a documentación xa que a validación tamén se pode definir en cada un dos campos en vez de empregar a propiedade $rules: https://livewire.laravel.com/docs/validation (escoller versión 3 na parte esquerda da páxina)
Operacións de alta-baixa-modificación
Alta
* A alta faise como sempre pero agora está no controlador do compoñente livewire. : Hai un pequeno detalle xa que como chamamos Alta ao compoñente Livewire, se empregamos o modelo Marca teríamos o mesmo nome. Por iso empregaremos un alias para o modelo.
- Arquivo: app/Livewire/Marca.php
use App\Models\Marca as MarcaModel; ............. public function gardarMarca(){ $this->validate(); Log::info('Pasa a validación'); $marcaCreada = MarcaModel::create([ 'descripcion' => $this->descripcion ]); session()->flash('message', "Marca creada correctamente con id $marcaCreada->id"); $this->reset(['descripcion']); // Borra os datos da caixa de texto. }
- Fixarse que para enviar unha información á vista empregamos session()->flash que vai gardar nunha variable de sesión (message) o texto a amosar.
- Dita variable non pode ser empregada pola páxina, soamente polo compoñente livewire xa que non se recarga a páxina enteira.
- A chamada a reset vai poñer en branco os valor da variable $descripcion do compoñente. Pero como temos un valor por defecto na definición poñerá dito valor. Lembrar que non é obrigatorio e podemos quitalo.
- Tamén indicar que podemos facer o create da forma: MarcaModel::create($this->all())
- Neste caso quedaríamos na pantalla de alta pero dita pantalla non se refresca. Soamente refresca o compoñente livewire.
- Para amosar as mensaxes de que todo foi correcto, imos crear un arquivo na vista.
- Tamén imos poñer nese arquivo os posibles erros que enviemos dende o controlador.
- Arquivo app/views/mensaxes/mensaxes.blade.php. Lembrar crear o cartafol mensaxes.
@if (session()->has('message')) <div class="alert alert-success"> {{ session('message') }} </div> @endif
- Simplemente amosamos o contido da variable de sesión message.
- Incluimos dito arquivo na vista do compoñente livewire:
- Arquivo app/views/livewire/marca.blade.php.
<div> <form wire:submit.prevent="gardarMarca"> @csrf <div> Descripción:<input type="text" maxlength="45" size="45" required wire:model.live="descripcion" name="descripcion" /> @error('descripcion') <span class="error">{{ $message }}</span> @enderror </div> <div> <input type="submit" name="btnAlta" value="Alta" onClick="return confirm('Estás seguro de dar de alta a nova marca ');"/> <input type="reset" name="btnBorrar" value="Limpar" /> </div> </form> @include('mensaxes.mensaxes') </div> : '''IMPORTANTE:''' O compoñente livewire soamente pode ter un elemento raíz, no exemplo posto <div>. Non podedes poñer o include fora do div.
- E que pasa se queremos ir a outra pantalla ou que recargue toda a páxina e non soamente o compoñente livewire ? Debemos empregar o método redirectRoute() poñendo o nome da ruta á que queremos ir.
- : Arquivo: app/Livewire/Marca.php
................... public function gardarMarca() { $this->validate(); Log::info('Pasa a validación'); $marcaCreada = MarcaModel::create([ 'descripcion' => $this->descripcion ]); session()->flash('message', "Marca creada correctamente con id $marcaCreada->id"); $this->reset(['descripcion']); return $this->redirectRoute('marcas.create'); }
Modificar
- Agora, ao engadir unha nova operación temos un problema.
- Se non empregáramos livewire teríamos dúas vistas diferentes, unha para alta e outra para modificación, pero ao estar nun compoñente livewire temos que engadir a lóxica para que a vista faga unha cousa ou outra.
- Creamos a vista en Laravel para actualizar a marca:
- Arquivo resources/views/marcas/form_modificar_marcas.blade.php
- Polo tanto imos engadir ao controlador:
- Unha variable que vai indicar se estamos en modo edición ou en modo alta. En función desa variable a vista amosará un texto diferente no botón e fará un actión diferente.
- Unha variable $id que vai gardar o id da marca que imos querer modificar. Dita id será o que enviemos dende a vista de Laravel cando premamos na modificación.
- Tamén teremos un método actualizarMarca() que actualizará os campos necesarios (no noso exemplo descripcion).
Funcionamento
-- Ángel D. Fernández González -- (2025).