Optimisations Relations polymorphiques Laravel

Par Alexandre #lbac, il y a 10 ans


Salut à vous,

J'aurais une question. Actuellement je travaille sur un système de notifications, et pour cela j'utilise une relation polymorphique, car les notifications peuvent faire références à toutes sortes d'actions (réponse à un commentaire, "j'aime" sur un commentaire, etc). Le soucis du coup c'est que les relations polymorphiques obligent (ou alors je ne sais pas comment contourner ce problème) à une requête pour récupérer les données.

Sachant aussi que les "j'aimes" font aussi l'objet d'une relation polymorphique, vu qu'on peut "aimer" plusieurs éléments sur le site.

Du coup lorsque je me retrouve à faire un affichage comme ceci :

$notificable->article->slug $notif->notificable->article->category->slug // ou $notificable->likeable->article->slug $notificable->likeable->article->category->slug

Je me retrouve alors avec 4 requêtes générées fois 5 notifications du coup, ce qui n'est pas super optimisé. Voici comment je récupère mes notifications, et les relations qui en découlent :

public function getLastNotifications(User $userTo, $take = 5, $paginate = false) { $query = $this->model ->with(['from' => function($query) { $query->select('id', 'pseudo'); }]) ->where('user_to', $userTo->id) ->latest(); if( $paginate ) { return $query->paginate($take); } else { return $query->take($take)->get(); } } /** * Relation polymorphique * * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function notificable() { return $this->morphTo(); }

Ce que je veux

En fait j'aimerais diviser mon nombre de requêtes par 2 (ou 4 dans l'optimum). Par exemple, la liste des 5 dernières notifications

Imaginons qu'il y a deux notifications pour des "j'aimes" et 3 notifications pour une réponse à un commentaire, je me retrouve donc avec :

Pour chaque "j'aime" :

Pour chaque Réponse de commentaire :

je me retrouve donc avec 20 requêtes en tout pour liste ces éléments, ce qui fait beaucoup. Laravel n'aurait pas un tour caché pour alléger tout ça ? Ou devrais-je faire autrement ?

Voici mon foreach listant les notifications si ça peut aider :

@foreach( $notifications as $notif )

   @if( $notif->type == 'comment' )
      <li><a href="{{ route('articles.show', ['cat' => $notif->notificable->article->category->slug, $notif->notificable->article->slug]) }}#{{ $notif->notificable_id }}">{{ $notif->from->pseudo }} a répondu à votre commentaire</a></li>
   @elseif ($notif->type == 'like' )
      @if( $notif->notificable->up )
          <li><a href="{{ route('articles.show', ['cat' => $notif->notificable->likeable->article->category->slug, 'slug' => $notif->notificable->likeable->article->slug]) }}#{{ $notif->notificable->likeable_id }}">{{ $notif->from->pseudo }} a aimé votre commentaire</a></li>
      @else
          <li><a href="{{ route('articles.show', ['cat' => $notif->notificable->likeable->likeable->article->category->slug, 'slug' => $notif->notificable->likeable->article->slug]) }}#{{ $notif->notificable->likeable_id }}">{{ $notif->from->pseudo }} n'a pas aimé votre commentaire</a></li>
      @endif
   @endif
@endforeach

Ce que j'obtiens

Et du coup j'obtiens forcément trop de requêtes.

select * from `notifications` where `user_to` = '34639' and `read` = '0' order by `created_at` desc limit 5 select `id`, `pseudo` from `users` where `users`.`deleted_at` is null and `users`.`id` in ('34813') select * from `likes` where `likes`.`id` = '10' limit 1 select * from `comments` where `comments`.`deleted_at` is null and `comments`.`id` = '126648' limit 1 select * from `articles` where `articles`.`deleted_at` is null and `articles`.`id` = '13460' limit 1 select * from `categories` where `categories`.`deleted_at` is null and `categories`.`id` in ('1') select * from `likes` where `likes`.`id` = '9' limit 1 select * from `comments` where `comments`.`deleted_at` is null and `comments`.`id` = '126657' limit 1 select * from `articles` where `articles`.`deleted_at` is null and `articles`.`id` = '13460' limit 1 select * from `categories` where `categories`.`deleted_at` is null and `categories`.`id` in ('1') select * from `likes` where `likes`.`id` = '8' limit 1 select * from `comments` where `comments`.`deleted_at` is null and `comments`.`id` = '126658' limit 1 select * from `articles` where `articles`.`deleted_at` is null and `articles`.`id` = '13460' limit 1 select * from `categories` where `categories`.`deleted_at` is null and `categories`.`id` in ('1') select * from `comments` where `comments`.`deleted_at` is null and `comments`.`id` = '126661' limit 1 select * from `articles` where `articles`.`deleted_at` is null and `articles`.`id` = '13460' limit 1 select * from `categories` where `categories`.`deleted_at` is null and `categories`.`id` in ('1') select * from `comments` where `comments`.`deleted_at` is null and `comments`.`id` = '126660' limit 1 select * from `categories` where `categories`.`deleted_at` is null and `categories`.`id` in ('1') select * from `comments` where `comments`.`deleted_at` is null and `comments`.`id` = '126

Alors je ne suis pas à la recherche d'une solution toute faîte, mais d'une piste sur comment mettre en place un cache, ou des techniques pour permettre de réduire le nombre de requêtes en trichant sur les relations ou je ne sais pas :/

Merci à vous =)

4 réponses

Azorgh, il y a 10 ans

Hello,

Si tu utilise la méthode with dispo sur les requetes eloquent ?

Par exemple :

Model::with(['my_poly_relation','my_poly_relation.likes','my_poly_relation.comments']);

Ca ne changerais pas le nombre de requetes ?

Alexandre #lbac, il y a 10 ans

Salut et merci de ta réponse,

Le soucis c'est que tu ne peux pas (il me semble) faire une relation de ce type avec les relations polymorphiques.

Alexandre #lbac, il y a 10 ans

Personne n'aurait une idée ?

Azorgh, il y a 10 ans

Salut à toi,

Alors je te confirme que de mon côté, ce système fonctionne.
J'ai un model User avec une relation poly avec Citizen et Company (qui contient les détails des users en fonction du type).

Et dans mes requetes Eloquent, je fait :

//Controller User::whereTrucMuch('truc')->with('details')->get(); //Model User public function details(){ return $this->morphTo(); }