This package provides an easy way to implement custom relationships between Eloquent models
Via Composer:
$ composer require culturegr/custom-relation
In Laravel 5.5+, the package's service provider should be auto-discovered, so you won't need to register it. If for some reason you need to register it manually you can do so by adding it to the providers array in config/app.php
:
'providers' => [
// ...
CultureGr\CustomRelation\CustomRelationServiceProvider::class,
],
Suppose we have a Laravel application that implements a simple ACL (Access Control List) layer: there are users that are assigned some roles, each of them consists of many permissions. A simplified version of the database structure could be the following:
There is a User
model that has a many-to-many relationship with a Role
model, which in turn has a many-to-many relationship with a Permission
model.
Now suppose that at some point, we need to access all permissions assigned to a specific user. Let's make this possible by creating a CustomRelation
class that will be used to define the relationship between the User
and the Permission
models.
A CustomRelation class should facilitate all required logic needed to join users
and permissions
tables, as well as to support relationship eager-loading. It can be created by running the make:relation
Artisan command:
$ php artisan make:relation UserPermissionRelation
This will generate a new CustomRelation
class named UserPermissionRelation
inside app/Eloquent/CustomRelations
directory with all required boilerplate:
<?php
namespace App\Eloquent\CustomRelations;
use CultureGr\CustomRelation\CustomRelation;
use Illuminate\Database\Eloquent\Collection;
class UserPermissionRelation extends CustomRelation
{
/**
* The Eloquent query builder instance.
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $query;
/**
* The parent model instance.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $parent;
/**
* Set the base constraints on the relation query.
*
* @return void
*/
public function addConstraints()
{
// ...
}
/**
* Set the constraints for an eager load of the relation.
*
* @param array $apps An array of parent models
* @return void
*/
public function addEagerConstraints(array $apps)
{
// ...
}
/**
* Match the eagerly loaded results to their parents.
*
* @param array $apps An array of parent models
* @param \Illuminate\Database\Eloquent\Collection $results The result of the query executed by our relation class.
* @param string $relation The name of the relation
* @return array
*/
public function match(array $apps, Collection $results, $relation)
{
// ...
}
}
The UserPermissionRelation
class initializes two properties:
$this->query
provides access to the relatedPermission
model's query builder instance$this->parent
provides access to the parentUser
model
In order to define the users/permissions relationship, the following methods should be implemented:
Sets the base constraints on the relation query. In our example:
public function addConstraints()
{
$this->query
->join('permission_role', 'permission_role.permission_id', '=', 'permissions.id')
->join('roles', 'permission_role.role_id', '=', 'roles.id')
->join('role_user', 'role_user.role_id', '=', 'roles.id');
// If relation is not eager loaded
if (!is_null($this->parent->getAttribute('id'))) {
$this->query->where('role_user.user_id', '=', $this->parent->getAttribute('id'));
}
}
Sets the constraints for an eager load of the relation. In our example:
public function addEagerConstraints(array $users)
{
$this->query
->whereIn('role_user.user_id', collect($users)->pluck('id'))
->with('roles.users'); // To avoid N+1 problem when eager loading
}
Matches the eagerly loaded results to their parents. In our example:
public function match(array $users, Collection $results, $relation)
{
if ($results->isEmpty()) {
return $users;
}
foreach ($users as $user) {
$user->setRelation(
$relation,
$results->unique()->filter(function (Permission $permission) use ($user) {
return in_array($user->id, $permission->roles->pluck('users.*.id')->flatten()->toArray());
})->values()
);
}
return $users;
}
Once the UserPermissionRelation
class has been implemented, it can be used to define a new custom relationship between the User
and the Permission
model via relatesTo
method which is available to the model through HasCustomRelation
trait:
use CultureGr\CustomRelation\HasCustomRelation;
class User extends Model
{
use HasCustomRelation;
// ...
public function permissions(): UserPermissionRelation
{
return $this->relatesTo(Permission::class, UserPermissionRelation::class);
}
}
That's it ๐ฅ! Now we can use our new custom permissions
relationship like any usual Eloquent relationship:
// Use relationship as a method
$userPermissions = User::find('id')->permissions()->get();
// Use relationship as a dynamic property
$userPermissions = User::find('id')->permissions;
// Eager loading
$user = User::with('permissions')->where(/* ... */)->get();
// Lazy eager loading
$user = User::find('id');
$user->load('permissions');
composer test
Please see the license file for more information.
- Awesome Laravel/PHP community