Cours pour apprendre à utiliser le framework Laravel 5.5

Les données


précédentsommairesuivant

VII. Le polymorphisme

Lors des deux précédents chapitres on a vu les principales relations que nous offre Eloquent : hasMany et belongsTomany. Je ne vous ai pas parlé de la relation hasOne parce que c'est juste du hasMany limité à un seul enregistrement et elle est peu utilisée. Dans tous les cas qu'on a vus on considère deux tables en relation. Dans le présent chapitre on va envisager le cas où une table peut être en relation avec plusieurs autres tables, ce qui se nomme du polymorphisme.

VII-A. Un peu de théorie

VII-A-1. La relation 1:1 ou 1:n

On a vu cette relation, en voici une schématisation pour fixer les esprits :

Image non disponible

On a a possède un b (hasOne) ou a possède plusieurs b (hasMany).

La réciproque : b est possédé par un a (belongsTo).

VII-A-2. La relation n:n

On a vu aussi cette relation, en voici une schématisation pour fixer les esprits :

Image non disponible

On a a appartient à un ou plusieurs b (belongsToMany).

Et on a b appartient à un ou plusieurs a (belongsToMany).

VII-A-3. La relation une table vers plusieurs tables

VII-A-3-a. Type de relation 1:n

Maintenant imaginons cette situation :

Image non disponible

La table c peut être en relation soit avec la table a, soit avec la table b. Dans cette situation comment gérer une clé étrangère dans la table c ? Comment l'appeler et comment savoir avec quelle table elle est en relation ?

On voit bien qu'il va falloir une autre information : connaître sûrement la table en relation.

Puisqu'on a besoin de deux informations il nous faut deux colonnes :

Image non disponible

On a donc deux colonnes :

  • relatable_id : la clé étrangère qui mémorise l'identifiant de l'enregistrement en relation ;
  • relatable_type : la classe du modèle en relation.

Voici la figure complétée avec les noms de ces relations :

Image non disponible
  • morphOne : c'est le hasOne mais issu de plusieurs tables ;
  • morphMany : c'est le hasMany mais issu de plusieurs tables ;
  • morphTo : c'est le belongsTo mais à destination de plusieurs tables.

VII-A-3-b. Type de relation n:n

On peut avoir le même raisonnement pour une relation de type n:n avec plusieurs tables d'un côté de la relation :

Image non disponible
  • morphToMany : c'est le belongsToMany mais issu de plusieurs tables ;
  • morphedByMany : c'est le belongsToMany mais en direction de plusieurs tables.

VII-B. L'application d'exemple

Maintenant qu'on a vu la théorie passons à la pratique avec un cas dans l'application d'exemple.

VII-B-1. Présentation

Quand vous allez dans l'administration vous tombez sur le tableau de bord :

Image non disponible

Il y a des pavés qui indiquent le nombre de nouveaux contacts, articles, utilisateurs et commentaires. Chaque fois que l'une de ces entités est créée on le mémorise dans la base, dans la table ingoings :

Image non disponible

Il y a deux colonnes intéressantes :

  • ingoing_id ;
  • ingoing_type.

Ça doit vous évoquer quelque chose par rapport à ce que j'ai expliqué ci-dessus sur le polymorphisme. Cette table ingoings est en relation polymorphique avec quatre tables :

Image non disponible

Si vous regardez un peu le contenu de cette table :

Image non disponible

Vous voyez que la première colonne (ingoing_id) mémorise les identifiants, et la seconde (ingoing_type) mémorise la classe du modèle correspondant.

VII-B-2. Les modèles

Dans le modèle App\Models\Ingoing vous trouvez cette méthode :

 
Sélectionnez
1.
2.
3.
4.
public function ingoing()
{
    return $this->morphTo();
}

Dans les modèles App\Models\Post, App\Models\User, App\Models\Contact, App\Models\Comment, vous trouvez le trait App\Models\IngoingTrait avec ce code :

 
Sélectionnez
1.
2.
3.
4.
public function ingoing()
{
    return $this->morphOne(Ingoing::class, 'ingoing');
}

Et ça suffit pour avoir la relation fonctionnelle !

VII-B-3. La création des ingoings

Quand un modèle est créé il y a déclenchement d'un événement, nous verrons cet aspect événementiel dans un chapitre ultérieur. Pour le moment on va juste regarder l'écouteur (listener) de cet événement App\Listeners\ModelCreated.php :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
public function handle(EventModelCreated $event)
{
    $event->model->ingoing()->save(new Ingoing);

    ...
}

On connaît le modèle créé grâce à l'événement ($event->model), on utilise la méthode save sur la relation en passant une instance de Ingoing. Un nouvel enregistrement est ainsi créé dans la table ingoings avec les colonnes ingoing_id et ingoing_type parfaitement renseignées.

VII-B-4. Les panneaux de l'administration

Pour gérer les panneaux de l'administration il existe une classe particulière PannelAdmin :

Image non disponible

Avec ce code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
<?php

namespace App\Services;

class PannelAdmin
{
    ...

    public function __construct(array $infos)
    {
        $this->color = $infos['color'];
        $this->icon = $infos['icon'];
        $this->model = new $infos['model'];
        $this->name = __($infos['name']);
        $this->url = $infos['url'];
        $this->nbr = $this->getNumber ();
    }

    protected function getNumber()
    {
        return $this->model->has('ingoing')->count();
    }
}

Une classe toute simple qui comporte les 6 propriétés nécessaires pour les panneaux :

  • couleur (color) ;
  • icône (icon) ;
  • modèle (model) ;
  • nom (name) ;
  • URL (url) ;
  • nombre de nouveaux éléments (nbr).

On voit que la dernière propriété est renseignée avec la fonction getNumber, dans laquelle on trouve ce code :

 
Sélectionnez
return $this->model->has('ingoing')->count();

Donc on considère tous les enregistrements qui ont (has) un ingoing et on les compte (count).

Si vous regardez les requêtes générées avec la barre de débogage vous allez trouver ce genre de requête :

 
Sélectionnez
select count(*) as aggregate from `contacts` where exists (select * from `ingoings` where `contacts`.`id` = `ingoings`.`ingoing_id` and `ingoings`.`ingoing_type` = 'App\Models\Contact')

Voici le code dans le contrôleur :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
public function index()
{
    $pannels = [];

    foreach (config('pannels') as $pannel) {

        $panelAdmin = new PannelAdmin($pannel);

        if ($panelAdmin->nbr) {
            $pannels[] = $panelAdmin;
        }
    }

    return view('back.index', compact('pannels'));
}

Les panneaux sont définis dans le fichier config/pannels.php :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<?php

return [

    [
        'color' => 'primary',
        'icon' => 'envelope',
        'model' => \App\Models\Contact::class,
        'name' => 'admin.new-messages',
        'url' => 'admin/contacts?new=on',
    ],

    ...

];

Dans le contrôleur on a une boucle pour itérer tous les panneaux :

 
Sélectionnez
foreach (config('pannels') as $pannel) {

Et on ne crée le panneau que s'il y a au moins un nouvel enregistrement :

 
Sélectionnez
if ($panelAdmin->nbr) {
    $pannels[] = $panelAdmin;
}

On envoie ensuite les panneaux à la vue :

 
Sélectionnez
return view('back.index', compact('pannels'));

Cette vue resources/views/back/index.blade.php est assez légère :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
@extends('back.layout')

@section('main')
    @admin
        <div class="row">
            @each('back/partials/pannel', $pannels, 'pannel')
        </div>
    @endadmin
@endsection

Je ne vais pas entrer dans le détail de la syntaxe parce que je parlerai plus longuement des vues ultérieurement mais la directive @each de Blade permet de faire une itération, on appelle donc la vue partielle …back/partials/pannel.blade.php pour chaque panneau. C'est la vue partielle qui a le code pour l'affichage :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
<div class="col-lg-3 col-xs-6">
    <!-- small box -->
    <div class="small-box bg-{{ $pannel->color }}">
        <div class="inner">
            <h3>{{ $pannel->nbr }}</h3>

            <p>{{ $pannel->name }}</p>
        </div>
        <div class="icon">
            <span class="fa fa-{{ $pannel->icon }}"></span>
        </div>
        <a href="{{ $pannel->url }}" class="small-box-footer">
            @lang('More info')
            <span class="fa fa-arrow-circle-right"></span>
        </a>
    </div>
</div>

VII-B-5. Suppression des ingoings

Dans l'administration quand on affiche la liste d'une entité, par exemple les contacts, on a une case à cocher pour signaler si l'entité est nouvelle ou pas :

Image non disponible

Si on décoche ça envoie une requête en Ajax pour la mise à jour dans la base. Je ne vais pas entrer dans le détail du JavaScript mais juste montrer la méthode du contrôleur, ici Back/Contact/Controller :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
public function updateSeen(Contact $contact)
{
    $contact->ingoing->delete ();

    return response ()->json ();
}

On utilise la méthode delete sur la relation (ingoing) et c'est fait ! Ensuite on renvoie une réponse vide au format JSON puisqu'on est en Ajax.

D'ailleurs si on regarde la méthode du ContactRepository pour afficher les contacts dans l'administration :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
public function getAll($nbrPages, $parameters)
{
    return Contact::with ('ingoing')
        ->latest()
        ->when ($parameters['new'], function ($query) {
            $query->has ('ingoing');
        })->paginate($nbrPages);
}

On voit qu'on fait un chargement (with) de l'ingoing. D'autre part quand le paramètre new est présent dans l'URL on ajoute à la requête le filtrage des ingoing ($query->has ('ingoing')) parce qu'on ne veut que les nouveaux contacts, ce qui donne ce genre de requête :

 
Sélectionnez
select * from `contacts` where exists (select * from `ingoings` where `contacts`.`id` = `ingoings`.`ingoing_id` and `ingoings`.`ingoing_type` = 'App\Models\Contact') order by `created_at` desc limit 3 offset 0

VII-C. En résumé

  • Lorsque plusieurs tables sont concernées d'un côté d'une relation on doit appliquer le polymorphisme.
  • Laravel propose de nombreuses méthodes pour gérer le polymorphisme selon la situation.

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2018 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.