Logomark

LARAVEL

Un framework qui rend heureux
Voir cette catégorie
Vers le bas
Voir cette série
Cours Laravel 12 - Livewire et Volt
Vendredi 7 mars 2025 13:54

Livewire est une innovation relativement récente dans l'écosystème Laravel. Initialement perçu comme une curiosité, il a rapidement gagné en popularité au point de devenir un incontournable. À première vue, Livewire peut sembler audacieux : pourquoi utiliser JavaScript ou l'un de ses nombreux frameworks comme Vue ou React, alors qu'il est possible de tout coder en PHP directement au sein 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 crucial d'assurer une liaison fluide entre ces deux environnements.

Cependant, Livewire s'est révélé être un outil remarquablement efficace et agréable à utiliser, permettant de réduire considérablement la quantité de JavaScript nécessaire, tant que l'on reste dans des cas d'usage standards. Livewire est de fait devenu incontrounable dans un cours consacré à Laravel. Dans cet article, je vous propose une introduction afin que vous puissiez découvrir ses avantages et apprendre à l'utiliser.

Installation

On va partir d'un Laravel tout neuf :

composer create-project laravel/laravel livewire --prefer-dist

Créez une clé de sécurité :

php artisan key:generate

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 blade existante
  • 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.

Désormais, 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à bien sûr ç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 et si vous entrez un index inconnu et vous allez tomber sur une erreur. C'est juste 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

Dans la vue :

<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 prévoit 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 cela 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 désormais 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

On prévoit une validation des deux propriétés :

protected $rules = [
    'note' => 'required|integer|between:0,20',
    'index' => 'required|exists:users,id',
];

Et pour le reste, c'est pratiquement pareil. Désormais, 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

On commence par créer les deux composants :

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');
    }
}

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;
use App\Models\User; class NoteUser extends Component { public $note = 0; public User $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 !

Volt

Livewire Volt est une extension innovante de Livewire qui vise à simplifier encore davantage le développement d'applications Laravel en réduisant la quantité de code nécessaire. Volt permet aux développeurs de créer des composants Livewire directement à partir de fichiers Blade, en utilisant une syntaxe intuitive et concise. Cette approche élimine le besoin de créer des classes PHP séparées pour chaque composant, ce qui accélère le processus de développement et rend le code plus lisible et maintenable.

Avec Livewire, le développement d'un composant nécessite généralement deux fichiers : une classe PHP pour la logique côté serveur et une vue Blade pour le rendu HTML. Cependant, Livewire Volt simplifie ce processus en permettant de regrouper tout le code dans un seul fichier Blade. Volt utilise une syntaxe intuitive directement dans les fichiers Blade pour définir les propriétés et les méthodes du composant, rendant le code plus lisible et maintenable.

Pour illustrer l'utilisation de Volt je vais reprendre le cas des deux composants imbriqués qu'on a vus ci-dessus avec Livewire.

Il faut commencer par installer Volt :

composer require livewire/volt
php artisan volt:install

Pour créer un composant Volt c'est simple (mais commencez par supprimer les composant Livewire précédemment créés parcequ'ils ont le même nom) :

php artisan make:volt show-user --class
php artisan make:volt note-user --class

Le code de départ est celui-ci :

<?php

use Livewire\Volt\Component;

new class extends Component {
    //
}; ?>

<div>
    //
</div>

Une classe pour la partie PHP et un emplacement pour le HTML.

On doit ajouter une route pour accéder à notre premier composant :

use Livewire\Volt\Volt;
 
Volt::route('user/{user}', 'show-user');

On code le composant note-user :

<?php

use Livewire\Volt\Component;
use App\Models\User;

new class extends Component {
    public $note = 0;
    public User $user;

    protected $rules = [
        'note' => 'required|integer|between:0,20',
    ];

    public function submit()
    {
        $this->validate();
        $this->user->note = $this->note;
        $this->user->save();
    }
}; ?>

<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>

Et le composant show-user :

<?php

use Livewire\Volt\Component;
use App\Models\User;

new class extends Component {
    public User $user;

    public function mount(User $user)
    {
        $this->user = $user;
    }
}; ?>

<div>
    <p>Nom = {{ $user->name }}</p>
    <p>Email = {{ $user->email }}</p>
    <livewire:note-user :user="$user" />
</div>

On a le même code que la version Livewire, mais l'avantage c'est qu'on n'a besoin que d'un fichier pour chaque composant. En coulisse, Livewire transforme ça pour le rendre conforme à son organisation de base mais c'est transparent pour nous. Je me suis bien habitué à Volt et je l'adopte maintenant presque systématiquement pour mes projets.

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.

Livewire connaît un certain succès qui me semble légitime.



Par bestmomo

Aucun commentaire

Article précédent : Cours Laravel 12 – les données – les ressources d'API
Article suivant : Cours Laravel 12 – l’authentification
Cet article contient un quiz. Vous devez être authentifié pour y avoir acces.