stayallive/laravel-inverse-relations
Installation:
composer require stayallive/laravel-inverse-relations
Publish the config (if needed):
php artisan vendor:publish --provider="Stayallive\InverseRelations\InverseRelationsServiceProvider"
Define an Inverse Relation:
Add HasInverseRelation trait to your model and define the inverse relation in the $inverseRelations property.
use Stayallive\InverseRelations\HasInverseRelation;
class Post extends Model
{
use HasInverseRelation;
protected $inverseRelations = [
'comments' => ['Comment', 'post'], // [relation_name, [related_model, inverse_relation]]
];
public function comments()
{
return $this->hasMany(Comment::class);
}
}
First Use Case:
Fetch a Post with its comments and automatically hydrate the inverse relation (post) on each Comment:
$post = Post::with('comments')->find(1);
// $post->comments now have their `post` relation populated automatically
Circular Relationships: Avoid N+1 queries when fetching models with inverse relations:
// Without inverse relations (N+1 risk):
$comments = Comment::where('post_id', 1)->get();
foreach ($comments as $comment) {
$comment->post; // N+1 query
}
// With inverse relations (single query):
$post = Post::with('comments')->find(1);
// All comments' `post` relations are hydrated in one go
MorphMany Support: Define inverse relations for polymorphic relationships:
class Comment extends Model
{
use HasInverseRelation;
protected $inverseRelations = [
'commentable' => ['Commentable', 'comments'],
];
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
Conditional Inverse Relations: Use closures to conditionally define inverse relations:
protected $inverseRelations = [
'tags' => function () {
return ['Tag', 'taggable'];
},
];
Eager Loading: Always eager-load inverse relations when possible to avoid runtime hydration:
$posts = Post::with(['comments', 'comments.post'])->get();
Customizing Hydration:
Override the hydrateInverseRelations method in your model to customize behavior:
protected function hydrateInverseRelations()
{
$this->loadMissing(['post']); // Custom logic
}
Performance: Disable inverse relations for read-heavy models where hydration isn’t needed:
protected $inverseRelations = [];
Circular Dependencies:
Avoid defining inverse relations that create infinite loops (e.g., A has B, B has A with inverse pointing back to A). This can cause stack overflows or excessive queries.
Model Not Found:
If the inverse relation’s parent model doesn’t exist, the relation will be null. Handle this gracefully:
$comment->post ?? throw new ModelNotFoundException();
Polymorphic Conflicts:
Ensure polymorphic relations (morphTo, morphMany) have consistent inverse definitions across all related models.
Caching Issues: Inverse relations bypass Laravel’s query cache. If you rely on caching, manually cache the results:
$comments = Cache::remember("post.$post->id.comments", now()->addHours(1), function () use ($post) {
return $post->comments;
});
Enable Query Logging: Check for unexpected queries when hydrating inverse relations:
DB::enableQueryLog();
$post = Post::with('comments')->find(1);
dd(DB::getQueryLog());
Verify Relation Definitions:
Ensure $inverseRelations matches the actual relation names in your models. Typos will cause silent failures.
Global Configuration:
The package respects Laravel’s default query behavior (e.g., DB::select won’t trigger inverse hydration). Use Eloquent models for inverse relations to work.
Extension Points:
Override Stayallive\InverseRelations\InverseRelationsServiceProvider to customize the package’s behavior globally.
Useful for APIs: Inverse relations reduce payload size by avoiding redundant data in responses:
return Post::with('comments')->find(1); // Comments include `post_id` implicitly
Testing: Mock inverse relations in tests to avoid real database queries:
$comment = new Comment();
$comment->setRelation('post', new Post());
Documentation: Clearly document inverse relations in your model’s PHPDoc to help other developers:
/**
* @property-read Post $post The parent post (inverse of comments).
*/
How can I help you explore Laravel packages today?