Custom Laravel Relations?
Have you looked into the hasManyThrough
relationship that Laravel offers?
Laravel HasManyThrough
It should help you retrieve all the permissions for a user.
The closest thing to a solution was what @biship posted in the comments. Where you would manually modify the properties of an existing Relation
. This might work well in some scenarios. Really, it may be the right solution in some cases. However, I found I was having to strip down all of the constraints
added by the Relation
and manually add any new constraints
I needed.
My thinking is this... If you're going to be stripping down the constraints
each time so that the Relation
is just "bare". Why not make a custom Relation
that doesn't add any constraints
itself and takes a Closure
to help facilitate adding constraints
?
Solution
Something like this seems to be working well for me. At least, this is the basic concept:
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();
}
}
The methods not implemented yet are used for eager loading and must be declared as they are abstract. I haven't that far yet. :)
And a trait to make this new Custom
Relation easier to use.
trait HasCustomRelations
{
public function custom($related, Closure $baseConstraints)
{
$instance = new $related;
$query = $instance->newQuery();
return new Custom($query, $this, $baseConstraints);
}
}
Usage
// 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
I went a ahead and put all this on Github just in case there are more people who are interested in something like this. This is still sort of a science experiment in my opinion. But, hey, we can figure this out together. :)