Installation
composer require culturegr/custom-relation
No manual service provider registration required in Laravel 5.5+ (auto-discovered).
First Use Case: Custom Many-to-Many Relation
Define a custom relation in your User model:
use CultureGr\CustomRelation\HasCustomRelations;
class User extends Model
{
use HasCustomRelations;
public function permissions()
{
return $this->customRelation(
'permissions', // Relation name
Permission::class, // Related model
'user_id', // Local key
'permission_id', // Foreign key
'role_user', // Intermediate table
'user_id', // Intermediate local key
'role_id' // Intermediate foreign key
);
}
}
Now call it like a native Eloquent relation:
$user = User::find(1);
$permissions = $user->permissions; // Returns a Collection of Permissions
Where to Look First
/tests/ directory for edge cases and validation logic.CustomRelationServiceProvider.php for bootstrapping behavior.Defining Custom Relations
Use customRelation() for non-standard relationships (e.g., multi-level joins, custom pivot tables):
// Example: User → Role → Permission (3-table join)
public function permissions()
{
return $this->customRelation(
'permissions',
Permission::class,
'user_id',
'permission_id',
'role_user',
'user_id',
'role_id',
'role_permission',
'role_id',
'permission_id'
);
}
Query Scoping Chain Eloquent query methods after defining the relation:
$user->permissions()->where('name', 'like', '%admin%')->get();
Dynamic Relations Useful for polymorphic or context-dependent relations:
public function dynamicPermissions($context = 'admin')
{
return $this->customRelation(
'permissions_' . $context,
Permission::class,
// ... keys
)->where('context', $context);
}
Integration with Policies Leverage custom relations in authorization logic:
public function authorize($user, Permission $permission)
{
return $user->permissions()->where('id', $permission->id)->exists();
}
Eager Loading
Optimize N+1 queries with with():
User::with('permissions')->get();
getPivot() method in the related model.$user->permissions()->remember(60); // Cache for 60 minutes
retrieved, saved) via:
$this->permissions()->listen(function ($query) {
// Modify query or log access
});
Key Mismatches
user_id vs id) throws silent RelationNotFoundException.dd($this->getTable().' keys') to debug.Overwriting Native Relations
belongsTo) will override it.custom_permissions).Performance with Complex Joins
select() to limit columns:
$this->permissions()->select('permissions.id', 'permissions.name');
Circular References
User → Role ← User) cause infinite loops.$user->permissions()->load('user'); // Explicitly load inverse
DB::enableQueryLog();
$user->permissions()->get();
dd(DB::getQueryLog());
if (method_exists($user, 'permissions')) {
$user->permissions;
}
Service Provider Bootstrapping
Illuminate\Database\Eloquent\Builder class. If you override this macro elsewhere, conflicts may arise.Model Events
retrieved or saved events by default. Use listeners for custom logic:
$this->permissions()->listen(function ($query) {
// Custom logic here
});
Custom Relation Macros
Extend the package by adding macros to the HasCustomRelations trait:
\CultureGr\CustomRelation\HasCustomRelations::macro('scoped', function ($callback) {
return $this->customRelation(...)->where($callback);
});
Usage:
$user->permissions()->scoped(fn($q) => $q->active(true));
Relation Caching
Override the getCustomRelation() method in your model to add caching:
public function getCustomRelation($relation)
{
return cache()->remember("{$this->id}.{$relation}", 60, function() use ($relation) {
return parent::getCustomRelation($relation);
});
}
Validation Add validation to relation keys in a model observer or accessor:
public function getPermissionsAttribute()
{
if (!Schema::hasColumn('role_user', 'user_id')) {
throw new \RuntimeException('Invalid relation keys');
}
return $this->customRelation(...);
}
How can I help you explore Laravel packages today?