coucou, j'ai un problème avec des boucles de requêtes quand je fais une manip que je trouve assez standart (mais google ne m'a pas donné de solution) :

j'essaie de construire un CMS avec Laravel.
j'ai suivi la doc pour l'Authorization et crée une classe PagePolicy : https://laravel.com/docs/5.5/authorization

Je veux vérifier les permissions de l'user courant pour le modèle Page :

class PagePolicy
{
    use HandlesAuthorization;

    public function before(User $user, $ability)
    {
        if ($user->hasRole('SuperAdmin')){
            return true;
        }
    }

    public function create(User $user)
    {
        return Auth::check();
    }

    public function update(User $user, Page $page)
    {
        return $user->id === $page->user_id;
    }

    public function delete(User $user, Page $page)
    {
        return $user->id === $page->user_id;
    }
}

la méthode $user->hasRole ressemble à ça :

public function hasRole($role)
{
    return $this->roles()->where('name', $role)->count();
}

ensuite dans mon template blade je veux un index de toutes les pages avec les boutons edit et delete seulement si j'ai les permissions nécessaires, ce qui donne un page.index.blade comme ça :

@foreach($pages as $page)
    <div>
        <h2>{{ $page->title }}</h2>
        @can('update', $page)
            <a href="{{ route('pages.edit',$page) }}">Edit</a>
        @endcan
        @can('delete', $page)
            <a href="{{ route('pages.delete',$page) }}">Delete</a>
        @endcan
    </div>
@endforeach

ça marche, mais pour 10 pages j'ai 20 requêtes SQL et ce sont toutes les même : celle qui check si je suis SuperAdmin ($this->roles()->where('name', $role)->count();)

Comment faire pour vérifier mes permissions en évitant les requêtes inutiles ?

4 réponses


wesh poussin
Auteur
Réponse acceptée

argh faut que j'étudie encore un poil la question mais le simple fait de remplacer

return $this->roles()->where('name', $role)->count();

par

return $this->roles->where('name', $role)->count();

(sans les paranthèses) a résolu mon problème !

merci pour le coup de main Heyden !

Hello crachecode,

"The before method will be executed before any other methods on the policy, giving you an opportunity to authorize the action before the intended policy method is actually called."

Je pense que la raison est assez simple, à chaque fois que tu fais @can, la méthode before est appelée, et comme ta méthode "hasRole" fait lui même un appel à ta BDD, il y a effectivement 2 appels à ta BDD pour chacune de tes pages.

La solution pourrait être de stocker le résultat de ta requête dans ton objet User afin de le réutiliser par la suite.

public function hasRole($role)
{
    if (!isset($this->userRoles[$role])) {
        $this->userRoles[$role] = $this->roles()->where('name', $role)->count() > 0;
    }
    return $this->userRoles[$role];
}

merci Edwin, oui j'avais bien compris la raison mais je me demandais quelle était la meilleure technique à employer pour éviter ce problème (qui doit être relativement courant...)

je pense que ton code ne marchera pas pour la fonction hasRole (à cause de count()) mais je vois bien l'idée !

je peux p-e mettre en place un getter pour stocker mes rôles dans une propriété de l'objet user ?

en fait la question c'est aussi de savoir à quel moment il serait bien de récupérer mes rôles et permissions (une bonne fois pour toute). il faudrait peut-être lier ça à l'objet Auth plutôt qu'à User ?

Tu pourrais effctivement récupérer la totalité des rôles en définissant

protected $with = ['roles'];

Puis en parcourant la collection au sein de ton hasRole de la manière suivante:

return $this->roles->where(function ($userRole) use ($role) {
    return $userRole->name === $role;
})->count() > 0;