mr-punyapal/laravel-extended-relationships
Adds efficient, extended Eloquent relationship helpers for Laravel models to cut queries, boost performance, and reduce duplicate code. Includes a HasExtendedRelationships trait and custom relations like belongsToManyKeys. Compatible with Laravel 11–13 and PHP 8.2+.
Install the package:
composer require mrpunyapal/laravel-extended-relationships
Add the trait to your model:
use MrPunyapal\LaravelExtendedRelationships\HasExtendedRelationships;
class Post extends Model {
use HasExtendedRelationships;
}
Define a belongsToManyKeys relationship to map multiple foreign keys (e.g., created_by, updated_by) to a single relationship:
public function auditors() {
return $this->belongsToManyKeys(
related: User::class,
foreignKey: 'id',
relations: [
'created_by' => 'creator',
'updated_by' => 'updater',
]
);
}
Usage:
$post = Post::with('auditors')->find(1);
$post->auditors->creator; // User who created the post
$post->auditors->updater; // User who last updated it
Workflow:
belongsToManyKeys for models with audit columns (created_by, updated_by, deleted_by).$model->relationship->creator).Example:
// Post model
public function auditors() {
return $this->belongsToManyKeys(
User::class,
'id',
['created_by' => 'creator', 'updated_by' => 'updater']
);
}
// Controller
$posts = Post::with('auditors')->get();
foreach ($posts as $post) {
$post->auditors->creator->name; // Eager-loaded
}
Workflow:
hasManyArrayColumn for JSON/array columns storing IDs (e.g., tags, companies).belongsToArrayColumn if needed.Example:
// User model (stores company IDs in JSON)
public function companies() {
return $this->hasManyArrayColumn(
Company::class,
'id',
'company_ids'
);
}
// Company model (inverse)
public function users() {
return $this->belongsToArrayColumn(
User::class,
'id',
'company_ids',
isString: true // If IDs are stored as strings
);
}
Workflow:
hasManyKeys for the inverse of belongsToManyKeys (e.g., User ↔ Post).created_by → created).Example:
// User model
public function auditedPosts() {
return $this->hasManyKeys(
Post::class,
['created_by' => 'created', 'updated_by' => 'updated']
);
}
$posts = Post::with('auditors', 'auditors.creator', 'auditors.updater')->get();
$post = Post::find(1);
$post->load('auditors'); // Load on demand
public function update(User $user, Post $post) {
return $user->id === $post->auditors->updater->id;
}
Query Overhead with isString: true:
isString: true in belongsToArrayColumn forces string comparisons, which can slow down queries if the column contains mixed types (e.g., ["7", 71]).Lazy-Loading Sorting Issues:
belongsToManyKeys may return unsorted results when lazy-loaded.sorted() to enforce order:
$post->auditors->sorted('created_at', 'asc');
Circular Dependencies:
Post → User → Post) can cause infinite loops.with() selectively or break cycles with loadMissing().JSON Column Serialization:
hasManyArrayColumn fails, verify the column is cast as array in $casts:
protected $casts = ['company_ids' => 'array'];
Check Queries: Use Laravel Debugbar or Telescope to verify the package generates single queries for multiple relationships.
\DB::enableQueryLog();
$post = Post::with('auditors')->find(1);
\DB::getQueryLog(); // Should show 1 query, not 3
Validate Relationship Data:
Ensure foreign keys in relations arrays match the actual column names:
// ❌ Wrong
['created_by' => 'creator', 'non_existent' => 'updater']
// ✅ Correct
['created_by' => 'creator', 'updated_by' => 'updater']
Batch Processing:
Use cursor() for large datasets with hasManyArrayColumn:
User::cursor()->with('companies')->each(function ($user) {
// Process without loading all into memory
});
Caching: Cache frequently accessed relationships (e.g., audit trails):
public function auditors() {
return $this->belongsToManyKeys(
User::class,
'id',
['created_by' => 'creator']
)->remember(60); // Cache for 60 minutes
}
Custom Query Modifiers: Override the relationship query in your model:
public function auditors() {
return $this->belongsToManyKeys(
User::class,
'id',
['created_by' => 'creator']
)->where('active', 1); // Add custom conditions
}
Dynamic Relationships: Use dynamic properties for runtime-defined relationships:
public function getDynamicAuditorsAttribute() {
return $this->belongsToManyKeys(
User::class,
'id',
['created_by' => 'creator']
)->getResults();
}
Boost Integration: Leverage Laravel Boost for IDE hints and documentation:
php artisan boost:update --discover
Boost Skill:
Ensure Boost is installed (composer require laravel/boost --dev) to unlock IDE support for the package’s methods.
php artisan boost:install if Boost is missing.PHP 8.2+ Features:
The package uses generics (PHP 8.2+) for type safety. If you encounter issues, ensure your project’s php.ini enables:
opcache.enable=1
opcache.jit_buffer_size=100M
How can I help you explore Laravel packages today?