Laravel 8,  Les CMS

Laravel Filament

Il existe un certain nombre de packages pour créer une administration pour Laravel dont l’officiel Nova (qui est payant). J’ai déjà parlé dans ce blog de Voyager, Orchid et Infyom. Je dois avouer que je me sens souvent gêné par ces outils qui sont remarquables mais qui obligent à jongler dans les situations particulières. C’est pour cette raison que dans les exemples pratiques que je donne je construis toujours cette partie administration pour en conserver la totale maîtrise.

Un nouveau venu a attiré mon attention, c’est Filament. Il se présente lui-même comme un content management framework et nous promet de pouvoir construire rapidement de magnifiques interfaces d’administration, par contre il n’est pas précisé « simplement ». Le package bénéficie d’une sérieuse documentation. Filament est construit avec les technologies à la mode : la TALL stack. Cet acronyme résume les technologies utilisées : Tailwind, Alpine, Laravel et Livewire. Je ne suis pas un grand adepte de cette approche et je l’ai déjà précisé dans quelques articles et interventions sur des forums. Mais je n’ai rien contre le fait qu’on nous propose sur un plateau un système pour créer rapidement une interface d’administration, ça risque juste de devenir problématique si on doit plonger les doigts dans le cambouis…

Je me suis donc penché sur ce nouveau-né pour voir son potentiel. Je vous propose de l’utiliser dans le cadre d’un projet simple pour voir comment le mettre en œuvre sans se prendre trop la tête.

Vous pouvez télécharger le code final de cet article ici.

Installation

Pour démarrer on crée un nouvelle application Laravel :

composer create-project laravel/laravel filament

On crée une base de données et on renseigne .env.

Et on installe Filament :

composer require filament/filament

On lance la migration :

php artisan migrate

Par défaut Filament utilise son propre système d’authentification et on trouve deux tables dédiées dans la base :

L’administration est donc bien indépendante du reste de l’application même s’il reste possible d’utiliser la table de base.

Ensuite il faut créer un compte administrateur :

php artisan make:filament-user

Là on répond aux questions et le compte est créé.

Avec l’url filament.ext/admin/login on arrive sur la page de connexion :

On utilise les données de l’administrateur qu’on a créé et on arrive sur le tableau de bord :

Évidemment tout est en anglais mais il a été ajouté récemment un certain nombre de traductions dont le français :

Il nous suffit donc de changer la locale dans la configuration (config.app) :

'locale' => 'fr',

Et c’est bon :

Des auteurs

On va créer une migration pour une table d’auteurs avec le modèle associé et le factory :

php artisan make:model Author -mfs

Dans la migration on prévoit ce code :

public function up()
{
    Schema::create('authors', function (Blueprint $table) {
        $table->id();
        $table->string('name')->unique();
        $table->date('birth');
        $table->timestamps();
    });
}

On a donc :

  • le nom
  • la date de naissance
  • les dates de création et de modification classiques

On renseigne le factory :

public function definition()
{
    return [
        'name' => $this->faker->name,
        'birth' => $this->faker->date(),
    ];
}

On utilise le factory dans DatabaseSeeder pour créer 10 auteurs :

use App\Models\Author;

...

public function run()
{
    Author::factory()->count(10)->create();
}

On ajoute la propriété $fillable dans le modèle Author :

protected $fillable = [
    'name',
    'birth',
];

Il ne reste plus qu’à lancer :

php artisan migrate --seed

On a nos 10 auteurs :

Comme on n’a pas trop pris de précautions pour les dates il y en a de très précoces !

Une ressource pour les auteurs

Filament utilise la notion de ressource définie comme un ensemble de classes statiques qui décrivent comment un administrateur interagit avec les données de la base. On va créer une ressource pour les auteurs :

php artisan make:filament-resource Author

On a la création de plusieurs dossiers et fichiers :

La ressource proprement dite réside dans la classe AuthorResource. Cette classe permet de définir les tableaux, les formulaires, les autorisations et les pages.

On voit déjà le menu complété :

On va quand même changer l’icône. Filament a comme dépendance le package blade-heroicons. On a donc un peu de choix. On change dans la ressource :

public static $icon = 'heroicon-o-user';

Et ça marche :

Le tableau

Pour le moment le tableau est vide :

On peut ajouter la traduction en créant un fichier resources/lang/fr.json avec ce code :

{
    "Authors": "Auteurs"
}

Le tableau est défini dans la méthode table de la ressource :

public static function table(Table $table)
{
    return $table
        ->columns([
            //
        ])
        ->filters([
            //
        ]);
}

On voit qu’on peut ajouter des colonnes et définir des filtres. Tout ça n’est pas rempli par défaut. On va compléter :

public static function table(Table $table)
{
    return $table
        ->columns([
            Columns\Text::make('name')->sortable()->primary(),
            Columns\Text::make('birth')->date('d/m/Y')->sortable(),
        ])
        ->filters([
            //
        ]);
}

On complète les traductions :

{
    ...
    "Name": "Nom",
    "Birth": "Date de naissance"
}

On obtient ce tableau :

On peut trier (sortable) sur les noms et les dates de naissance et la suppression fonctionne bien.

Si on veut activer la recherche il suffit d’ajouter searchable sur les colonnes concernées, par exemple pour le nom :

Columns\Text::make('name')->sortable()->searchable()->primary(),

Il apparait alors une zone de recherche :

Les formulaires

Voyons à présent comment créer les formulaires. Ça se passe dans la méthode form qui est vide au départ. On va aussi compléter le code à l’aide de la documentation :

public static function form(Form $form)
{
    return $form
        ->schema([
            Components\TextInput::make('name')
                ->autofocus()
                ->required()
                ->max(255)
                ->unique('authors', 'name', true),
            Components\DatePicker::make('birth')->displayFormat('d/m/Y')->required(),
        ]);
}

On ajoute directement la validation pour laquelle on dispose des principales règles de Laravel.

Au passage on ajoute les traductions :

{
    ...
    "Edit Author": "Modification d'un auteur",
    "Create Author": "Création d'un auteur"
}

On a ainsi un formulaire pour la modification :

Avec un joli Datepicker :

On vérifie la validation :

On peut charger les fichiers de langue pour franciser ça :

On a de la même manière un formulaire pour la création :

On a donc vu qu’avec peu de code on a une gestion complète de notre ressource. Sans Filament on aurait dû créer les routes, le contrôleur, les vues. Mais évidemment j’ai pris un cas très simple qui ne nous pose aucun problème particulier.

Des livres

Les auteurs sont censés écrire des livres.

On va créer une migration pour une table de livres avec le modèle et le factory associés :

php artisan make:model Book -mfs

Dans la migration on prévoit ce code :

public function up()
{
    Schema::create('books', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->timestamps();
        $table->foreignId('author_id')
            ->constrained()
            ->onDelete('cascade')
            ->onUpdate('cascade');
    });
}

On a juste le titre pour le livre mais surtout on crée une clé étrangère qui lie cette table à celle des auteurs.

On ajoute la propriété $fillable dans le modèle Book :

protected $fillable = ['title'];

On ajoute la relation dans Author :

public function books()
{
    return $this->hasMany(Book::class);
}

Et la réciproque dans Book :

public function author()
{
    return $this->belongsTo(Author::class);
}

On code le factory :

public function definition()
{
    return [
        'title' => $this->faker->sentence(3),
    ];
}

On utilise le factory dans DatabaseSeeder pour créer 10 auteurs avec pour chacun d’eux 5 livres :

public function run()
{
    Author::factory()->has(Book::factory()->count(5))->count(10)->create();
}

On rafraichit les migrations et on remplit les tables (pensez à recréer l’administrateur qui va disparaitre dans l’opération)  :

php artisan migrate:fresh --seed

On a de nouveau 10 auteurs mais chacun d’eux a 5 livres.

Une ressource pour les livres

On va créer une ressource pour les livres :

php artisan make:filament-resource Book

On change l’icône dans la ressource BookResource :

public static $icon = 'heroicon-o-book-open';

On ajoute des traductions :

{
    ...
    "Books": "Livres",
    "Title": "Titre",
    "Edit Book": "Modification d'un livre",
    "Create Book": "Création d'un livre",
    "Author": "Auteur",
    "Author name": "Nom de l'auteur"
}

Tableau

On code le tableau :

public static function table(Table $table)
{
    return $table
        ->columns([
            Columns\Text::make('title')->sortable()->searchable()->primary(),
            Columns\Text::make('author.name'),
        ])
        ->filters([
            //
        ]);
}

On voit qu’on a facilement ajouté le nom de l’auteur issu de la relation.

Les formulaires

On code les formulaires en tenant compte de la relation avec les auteurs :

public static function form(Form $form)
{
    return $form
        ->schema([
            Components\TextInput::make('title')
                ->autofocus()
                ->required()
                ->max(255),
            Components\BelongsToSelect::make('author_id')
                ->relationship('author', 'name'),
        ]);
}

On utilise la méthode BelongsToSelect pour référer les auteurs.

Dans le champ de l’auteur si on clique on a ce message qui nous propose une recherche :

On entre des caractères et on obtient la liste des auteurs qui correspondent, il suffit ensuite de cliquer sur le bon :

C’est bien pratique !

Le nombre de livres par auteur

Ce qui serait intéressant maintenant c’est d’avoir dans le tableau des auteurs le nombre de livres pour chacun. On complète la ressource AuthorResource :

public static function table(Table $table)
{
    return $table
        ->columns([
            ...
            Columns\Text::make('Books')->getValueUsing(
                function ($author) { 
                    return (string)$author->books()->count(); 
                }
            ),
        ])
        ...
}

On utilise la méthode getValueUsing qui permet de calculer ce qu’on veut, ici le nombre de livres pour chaque auteur. On obtient cet affichage :

Conclusion

On a vu avec un exemple simple que Filament permet de construire rapidement une administration esthétique et efficace. Je l’ai aussi essayé sur un projet plus complexe et je suis rapidement tombé sur des difficultés. Mais le projet est tout récent et déjà très prometteur. Je n’ai pas traité dans cet article des sujets déjà bien pris en charge comme les autorisations et le chargement de fichier. Au niveau des formulaire il est aussi proposé un éditeur de texte « riche » mais encore assez limité sans possibilité d’intégration de médias.

En résumé on verra dans l’avenir si Filament se développe et arrive à nous proposer un outil suffisamment complet et adaptable pour qu’on puisse l’utiliser sans trop de bidouillage de code.

Print Friendly, PDF & Email

6 commentaires

  • lowzer

    salut j’ai un probleme je veut reccuperer mes donnees dans ma base de donnees avec eloquent mais cela m’affiche une erreur « Variable non définie: » et pourtant tous fais comme il faut

    voici la parti index de mon controller

    public function index()
    {

    $ppc = ppc::all();
    return view(‘adherent.adherent’, compact(‘ppc’));
    }

    la vue adherent se trouvant dans le dossier adherent

    @foreach($ppc as $pp)

    {{ $pp->prenom }}
    {{ $pp->nom }}
    {{ $pp->lienparente }}
    {{ $pp->adresse }}
    {{ $pp->telephone }}
    {{ $pp->cin }}
    id) }} »>edit

    @endforeach

    et enfin web.php

    Route::resource(‘ppc’, ppc::class );

    la table s’appel ppc

  • gil

    Hello,
    Ce que j’aime moyen dans filament, c’est d’avoir à redéclarer les champs dans chaque vue (index, form), alors que par exemple Nova ce n’est fait qu’à un seul endroit. Même si c’est un peu complexifié par l’usage de méthodes spécifique à chaque vue (les rules pour création / update, les méthode qui indique dans quelles vues on veut le voir…) au moins c’est centralisé.
    Sinon, je viens de tomber sur un autre « CMF » laravel qui me semble… monstrueusement puissant, si on regarde les exemples d’écrans, la doc, les démos. C SHARP. (https://sharp.code16.fr/docs/). Réalisé par une boite (code16) qui l’utilise en production. Fait pour laravel 6+, je ne sais pas si ça tourne avec laravel 8.
    Le code par contre semble un peu moins simple, mais… C’est peut-être un mal nécessaire pour la puissance. Je vais peut-être jeter un oeil… Ca m’agace un peu car je venais de partir sur Nova !

Laisser un commentaire