Livewire est relativement récent dans l'écosystème Laravel. Au départ, il était perçu comme une curiosité, mais il est rapidement devenu si populaire qu'il est maintenant nécessaire de l'inclure dans un cours sur Laravel. Mon impression initiale de Livewire a été mitigée, car sa proposition est audacieuse: pourquoi utiliser Javascript ou l'un de ses nombreux frameworks tels que Vue ou React, lorsqu'on pourrait tout coder en PHP à l'intérieur de Laravel? Cette proposition peut surprendre, car PHP est exécuté côté serveur, tandis que JavaScript s'exécute côté client, dans le navigateur. Il est donc nécessaire d'assurer une liaison entre les deux. Cependant, Livewire s'est révélé être un outil efficace et agréable, qui permet de gagner du temps en réduisant la quantité de JavaScript à écrire, à condition de rester dans les cas d'usage standards. Dans cet article, je vous propose une introduction à Livewire, afin que vous puissiez découvrir ses avantages et ses limites.
Installation
On va partir d'un Laravel tout neuf :composer create-project laravel/laravel livewire
Livewire est un package pour Laravel qui s'installe classiquement avec composer :
composer require livewire/livewire
Et voilà on est prêts !
Créer un composant
Livewire fonctionne avec des composants. Pour en créer un c'est tout simple :php artisan make:livewire MonComposant
On se retrouve avec deux fichiers créés (et deux dossiers qui n'existaient pas auparavant) :
Le premier comporte l'intendance (la classe) et le second s'occupe de l'aspect (la vue).
Voyons le code généré dans la classe :
<?php
namespace App\Livewire;
use Livewire\Component;
class MonComposant extends Component
{
public function render()
{
return view('livewire.mon-composant');
}
}
On a une méthode render et on retourne la vue générée.
Dans la vue on a :
<div>
{{-- The whole world belongs to you. --}}
</div>
La phrase est générée aléatoirement.
Donc on n'a pas grand chose pour le moment.
Utiliser un composant
Maintenant qu'on sait créer un composant voyons comment l'utiliser. Il y a deux façons de le faire :- dans une vue avec une nouvelle commande blade qui fonctionne donc comme une inclusion : @livewire('mon-composant')
- dans une vue complète, dans ce cas le composant est la vue
C'est cette deuxième option qui est codée par défaut comme on l'a vu ci-dessus. Dans ce cas il nous faut aussi créer une route pour utiliser le composant :
use App\Livewire\MonComposant;
Route::get('composant', MonComposant::class);
Si on fait ça avec une installation toute fraiche de Laravel on va tomber sur une erreur en utilisant l'url .../composant :
Le souci vient du fait que par défaut lorsqu’on utilise une page complète pour un composant il va automatiquement chercher le layout resources/views/components/layouts/app.blade.php. Il existe une commande pour le créer :
php artisan livewire:layout
Avec ce code :
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? 'Page Title' }}</title>
</head>
<body>
{{ $slot }}
</body>
</html>
Le composant va se loger à l'emplacement du $slot.
Maintenant on n'a plus d'erreur mais évidemment une page vide puisqu'on n'a rien dans notre vue. On va vérifier si ça fonctionne :
<div>
<p>coucou</p>
</div>
Vous devriez avoir un simple coucou sur la page.
Les propriétés
Les composants de Livewire ont besoin de données. On peut créer des propriétés dans la classe :class MonComposant extends Component
{
public $message = 'Coucou !';
Une propriété publique dans la classe est automatiquement disponible dans la vue :
<p>{{ $message }}</p>
Jusque là on n'a rien inventé de bien utile. Par contre ça va devenir plus intéressant avec la liaison de données. On connait bien ça avec Vue.js par exemple on synchronise la valeur d'un élément sur la page web avec la propriété du composant.
On va recoder notre vue :<div>
<input type="text" wire:model.live="message"/>
<p>{{ $message }}</p>
</div>
C'est l'attribut wire:model.live qui relie le champ de saisie à la propriété. Maintenant quand on change le texte dans le champ de saisie il s'actualise au-dessous :
Un petit schéma pour bien visualiser ça : Maintenant la question qu'on peut se poser c'est : comment ça fonctionne ?On peut remarquer qu'à chaque changement on a une requête, il part une requête POST de la forme .../livewire/message/show-posts avec ce corps :
On a au retour une réponse JSON :
Sous le capot Livewire utilise Alpine pour gérer le Javascript sur la page.Les propriétés calculées
On peut aussi avoir des propriétés calculées (comme avec Vue.js), alors là évidemment ça devient plus intéressant parce qu'on a besoin d'information sur le serveur. Imaginons que nous ayons deux utilisateurs dans notre table users. Alors on peut créer cette propriété calculée (computed) :
use Livewire\Component;
use App\Models\User;
class MonComposant extends Component
{
public $index = 1;
public function getUserProperty()
{
return User::find($this->index);
}
Et dans la vue :
<input wire:model.live="index" type="text"/>
<p>{{ $this->user->name }}</p>
Maintenant quand on entre l'index dans la zone de texte le nom de l'utilisateur correspondant apparaît au-dessous :
Là évidemment ça devient bien plus intéressant parce qu'on n'a pas à se soucier de toute l'intendance d'Ajax ! Je n'ai pris aucune précaution si vous entrez in index inconnu et vous allez tomber sur une erreur. C'estjuste un exemple épuré pour le principe de fonctionnement.
Un petit schéma à nouveau pour bien visualiser le fonctionnement :Les actions
Une action dans Livewire c'est la capacité à écouter une action sur la page web et d'appeler une méthode dans le composant pour modifier la page. C'est donc un pas de plus dans l'interactivité par rapport aux propriétés calculées vues ci-dessus.
Préparation
Pour montrer ça on va ajouter une colonne dans notre table users. On crée donc une migration :
php artisan make:migration alter_users_table --table=users
On complète le code de la migration :
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->integer('note')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('note');
});
}
};
Et on lance la migration :
php artisan migrate
Maintenant on dispose d'une colonne note.
Action simple
On va coder ainsi notre composant :<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\User;
class MonComposant extends Component
{
public $note = 0;
public function noter()
{
$user = User::find(1);
$user->note = $this->note;
$user->save();
}
public function render()
{
return view('livewire.mon-composant');
}
}
On a :
- une propriété note
- une action noter
<div>
<input wire:model.defer="note" type="text"/>
<button wire:click="noter">
Noter
</button>
</div>
On a :
- une liaison de données avec la propriété note : wire:model.defer="note" (on prevoit defer pour éviter les multiples requêtes)
- un bouton pour noter avec une action sur le click : wire:click="noter"
Maintenant quand on met une note dans la zone de texte et qu'on clique sur le bouton on retrouve la valeur de la note dans la colonne note de l'utilisateur (ici on a pris simplement le premier, mais on pourrait évidemment rendre ça dynamique ou prendre l'utilisateur connecté) :
Passage de paramètre
On peut passer un paramètre pour l'action. Poursuivons notre exemple en transmettant l'index de l'utilisateur. Dans la classe on ajoute une propriété $index et on utilise le paramètre dans l'action :
public $note = 0;
public $index = 1;
public function noter($indexUser)
{
$user = User::find($indexUser);
$user->note = $this->note;
$user->save();
}
Dans la vue on ajoute la saisie de l'index et la transmission du paramètre :
<div>
<label>Index de l'utilisateur</label>
<input wire:model.defer="index" type="text"/><br><br>
<label >Note</label>
<input wire:model.defer="note" type="text"/><br><br>
<button wire:click="noter({{ $index }})">
Noter
</button>
</div>
La partie intéressante est le passage du paramètre :
wire:click="noter({{ $index }})"
On a donc deux zones de texte :
On peut maintenant choisir l'utilisateur à noter. Bon, c'est sommaire et pas du tout réaliste, mais ça donne le principe de fonctionnement. On pourrait par exemple remplir une liste de choix avec les noms des utilisateurs. Je ne vais pas le faire pour rester simplement dans les principes de fonctionnement.
La validation
Dans mon exemple précédent je n'ai prévu aucun contrôle quant aux valeurs transmises. Dans une approche réaliste il nous faut évidemment une validation des valeurs. La bonne nouvelle c'est que le système est le même que pour la validation dans Laravel.
Validation classique
On va changer le code pour adopter quelque chose de plus réaliste avec un formulaire :<div>
<form wire:submit.prevent="submit">
<label >Index de l'utilisateur</label>
<input wire:model.defer="index" type="text"/>
<p style="color: red">{{ $errors->first('index') }}</p>
<label>Note</label>
<input wire:model.defer="note" type="text"/>
<p style="color: red">{{ $errors->first('note') }}</p>
<button type="submit">
Noter
</button>
</form>
</div>
Pour notre composant on a maintenant ce code :
<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\User;
class MonComposant extends Component
{
public $note = 0;
public $index = 1;
protected $rules = [
'note' => 'required|integer|between:0,20',
'index' => 'required|exists:users,id',
];
public function submit()
{
$this->validate();
$user = User::find($this->index);
$user->note = $this->note;
$user->save();
}
public function render()
{
return view('livewire.mon-composant');
}
}
On a donc deux propriétés :
- note
- index
protected $rules = [
'note' => 'required|integer|between:0,20',
'index' => 'required|exists:users,id',
];
Et pour le reste c'est pratiquement pareil. Maintenant on a un contrôle de la validité des entrées :
Bon je n'ai pas traduit en français mais c'est une autre histoire qu'on a déjà rencontrée plusieurs fois...
Mais on peut personnaliser les messages selon l'erreur :
protected $messages = [
'note.integer' => 'C\'est quand même mieux un nombre pour une note !',
];
Imbrication de composants
Les composants de Livewire peuvent être imbriqués (nested), autrement dit on peut mettre des composants dans des composants. On va poursuivre notre exemple avec cette fois deux composants :
- un composant parent qui va afficher un utilisateur
- un composant enfant qui permet de noter l'utilisateur
php artisan make:livewire ShowUser
php artisan make:livewire NoteUser
On va prévoir une route avec un paramètre pour préciser l'index de l'utilisateur :
use App\Livewire\ShowUser;
Route::get('user/{user}', ShowUser::class);
Comme Livewire est parfaitement intégré à Laravel on peut utiliser la liaison de données (Route Model Binding), donc là je précise le nom de paramètre user pour que Laravel comprenne ce que je veux.
Avec Livewire on n'utilise pas de contrôleur alors comment récupérer le paramètre ? C'est tout simple avec Livewire :
<?php
namespace App\Livewire;
use App\Models\User;
use Livewire\Component;
class ShowUser extends Component
{
public User $user;
public function render()
{
return view('livewire.show-user')->layout('layouts.guest');
}
}
Là notre composant récupère bien le paramètre, va chercher dans la base les informations de l'utilisateur, et affecte la propriété $user qui sera disponible dans la vue :
<div>
<p>Nom = {{ $user->name }}</p>
<p>Email = {{ $user->email }}</p>
</div>
Avec une url de la forme .../user/1 on obtient les renseignements sur l'utilisateur :
On va maintenant inclure le composant pour noter l'utilisateur. On code la classe NoteUser :
<?php
namespace App\Livewire;
use Livewire\Component;
class NoteUser extends Component
{
public $note = 0;
public $user;
protected $rules = [
'note' => 'required|integer|between:0,20',
];
public function submit()
{
$this->validate();
$this->user->note = $this->note;
$this->user->save();
}
public function render()
{
return view('livewire.note-user');
}
}
Et la vue note-user :
<div>
<form wire:submit.prevent="submit">
<label>Note</label>
<input wire:model="note" type="text"/>
<p style="color: red">{{ $errors->first('note') }}</p>
<button type="submit">
Noter
</button>
</form>
</div>
Il ne reste plus qu'à insérer ce composant dans l'autre, donc dans la vue show-user :
<div>
<p>Nom = {{ $user->name }}</p>
<p>Email = {{ $user->email }}</p>
@livewire('note-user', ['user' => $user])
</div>
On prend la précaution d'envoyer l'information de l'utilisateur dans le composant enfant.
Et ça devrait fonctionner avec la validation !
Conclusion
Je n'ai fait dans cet article que montrer les fonctionnalités de base du système proposé. Je conseille vivement de consulter la documentation. C'est une autre façon d'envisager la gestion du frontend en allégeant le codage de ce côté. On fait de l'Ajax sans s'en rendre compte. Il faut évidemment bien mesurer la charge à assumer côté serveur parce que ça peut vite devenir lourd. D'autre part étant donné le délai qui peut se produire en attendant une réponse on peut se retrouver avec une réactivité dans les chaussettes mais la version 3 a vraiment apporté de très nettes améliorations.
Livewire connaît un certain succès qui me semble légitime.
Par bestmomo
Aucun commentaire