Relações De Laravel Personalizadas?

Situação hipotética: digamos que temos 3 modelos:
  • User
  • Role
  • Permission
Vamos também dizer que User tem uma relação de muitos para muitos com Role, e Role tem uma relação de muitos para muitos com Permission.

Então, os modelos deles podem parecer-se com isto. (Mantive-os breves de propósito.)

class User
{
    public function roles() {
        return $this->belongsToMany(Role::class);
    }
}

class Role
{
    public function users() {
        return $this->belongsToMany(User::class);
    }

    public function permissions() {
        return $this->belongsToMany(Permission::class);
    }
}

class Permission
{
    public function roles() {
        return $this->belongsToMany(Role::class);
    }
}

e se quisesses todos os Permissionpara um User?Não há nenhum.

parece que tu estão presos a fazer algo que não parece bem e não funciona com coisas como User::with('permissions') ou User::has('permissions').

class User
{
    public function permissions() {
        $permissions = [];
        foreach ($this->roles as $role) {
            foreach ($role->permissions as $permission) {
                $permissions = array_merge($permissions, $permission);
            }
        }
        return $permissions;
    }
}

Este exemplo é, apenas um exemplo, não leias muito nele. A questão é, como você pode definir uma relação customizada? Outro exemplo poderia ser a relação entre um comentário no facebook e a mãe do autor. Estranho, eu sei, mas espero que percebas a ideia. Relações Personalizadas. Como?

Na minha mente, uma boa solução seria para isso. relação a ser descrita de forma semelhante a como descrever qualquer outra relação em Laravel. Algo que retorna uma eloquente Relation.

class User
{
    public function permissions() {
        return $this->customRelation(Permission::class, ...);
    }
}

já existe algo assim?

Author: Johnny, 2016-08-29

3 answers

O mais próximo de uma solução foi o que @biship postou nos comentários . Onde você modificaria manualmente as propriedades de um Relation existente. Isto pode funcionar bem em alguns cenários. Realmente, pode ser a solução certa em alguns casos. No entanto, descobri que tinha de Despir todos os constraints adicionados pelo Relation e adicionar manualmente qualquer novo constraints de que precisasse.

A minha ideia é esta... Se vais despir o constraints de cada vez para que o Relation seja apenas "nua". Por que não fazer um costume Relation que não adiciona nenhum constraints e toma um Closure para ajudar a facilitar a adição constraints?

Solução

Algo assim parece estar a funcionar bem para mim. Pelo menos, este é o conceito básico:
class Custom extends Relation
{
    protected $baseConstraints;

    public function __construct(Builder $query, Model $parent, Closure $baseConstraints)
    {
        $this->baseConstraints = $baseConstraints;

        parent::__construct($query, $parent);
    }

    public function addConstraints()
    {
        call_user_func($this->baseConstraints, $this);
    }

    public function addEagerConstraints(array $models)
    {
        // not implemented yet
    }

    public function initRelation(array $models, $relation)
    {
        // not implemented yet
    }

    public function match(array $models, Collection $results, $relation)
    {
        // not implemented yet
    }

    public function getResults()
    {
        return $this->get();
    }
}

Os métodos ainda não implementados são utilizados para o carregamento ansioso e devem ser declarados como abstractos. Ainda não cheguei tão longe. :)

E uma característica para tornar esta nova relação mais fácil de usar.
trait HasCustomRelations
{
    public function custom($related, Closure $baseConstraints)
    {
        $instance = new $related;
        $query = $instance->newQuery();

        return new Custom($query, $this, $baseConstraints);
    }
}

Utilização

// app/User.php
class User
{
    use HasCustomRelations;

    public function permissions()
    {
        return $this->custom(Permission::class, function ($relation) {
            $relation->getQuery()
                // join the pivot table for permission and roles
                ->join('permission_role', 'permission_role.permission_id', '=', 'permissions.id')
                // join the pivot table for users and roles
                ->join('role_user', 'role_user.role_id', '=', 'permission_role.role_id')
                // for this user
                ->where('role_user.user_id', $this->id);
        });
    }
}

// app/Permission.php
class Permission
{
    use HasCustomRelations;

    public function users()
    {
        return $this->custom(User::class, function ($relation) {
            $relation->getQuery()
                // join the pivot table for users and roles
                ->join('role_user', 'role_user.user_id', '=', 'users.id')
                // join the pivot table for permission and roles
                ->join('permission_role', 'permission_role.role_id', '=', 'role_user.role_id')
                // for this permission
                ->where('permission_role.permission_id', $this->id);
        });
    }
}

You could now do all the normal stuff for relations without having to query in-between relations first.

Github

Eu fui em frente e coloquei Tudo isto no Github só no caso de haver mais pessoas que estão interessadas em algo assim. Na minha opinião, isto ainda é uma espécie de experiência científica. Mas podemos resolver isto juntos. :)

 1
Author: Johnny, 2017-05-23 12:08:31
Creio que este conceito já existe. Você pode escolher em usar papéis Laravel ACL e permissões ou Gate, ou um pacote conhecido como confiar por zizaco.

Zizaco-Trust

Laracast-watch video 13 e 14

Boa sorte!
 0
Author: choy vallejo, 2016-08-29 18:54:19
Já investigaste o "tem muitos" através da relação que o Laravel oferece?

Laravel HasManyThrough

Deve ajudá-lo a obter todas as permissões para um utilizador.

 0
Author: pseudoanime, 2016-08-29 23:59:38