I have a data structure in which I need objects to be aware of their needed dependencies for loading.
What I can do
Currently, I can do this to load the first layer of relationships, this is obviously a very basic model:
class Ticket {
public function notes(){}
public function events(){}
public function tags(){}
public function scopeWithAll($query)
{
$query->with('notes', 'events', 'tags');
}
}
// Loads Ticket with all 3 relationships
$ticket = Ticket::withAll();
This works great! The problem being, I need to chain this functionality down to 3-5 levels of dependent relationships. Each of the 3 loaded models is going to have n relationships of its own.
I know I can do this through eager loading if I specify all of the relationship names, as follows:
public function scopeWithAll($query)
{
$query->with('notes.attachments', 'notes.colors', 'events', 'tags', 'tags.colors.', 'tags.users.email');
}
This works great too. But I need my code to be smarter than that.
What I need to do
Statically defining the scope of each object load is not desirable at this point in my project. I need to be able to load a Ticket, and the Ticket load all of its relationships, and each of those relationships load all of their relationships.
The only way I can think to do this is find some way to eagerly load a query scope for each relationship on the class. Something like
public function scopeWithAll($query)
{
$query->with('notes.withAll()', 'events.withAll()', 'tags.withAll()');
}
Is there currently a way to do this within Eloquent?
Maybe you can try something like this:
User::withRelatives()->find(1);
Okay, that's an idea and how to implement that? For example, if you have some related methods for your User
model such as 'posts', 'roles' etc then keep all the related methods (methods that make relationship) in a separate trait
, for example:
trait UserRelatives {
public function posts()
{
// ...
}
public function roles()
{
// ...
}
}
Now, in the User
model you may create a scopeMethod
like withAll
and inside there you may try something like this:
public function scopeWithAll($query)
{
// Get all the related methods defined in the trait
$relatives = get_class_methods(UserRelatives::class);
return $query->with($relatives);
}
So, if you do something like this:
$user = User::withAll()->find(1);
You'll be able to load all related models. Btw, get_class_methods(UserRelatives::class)
will give you an array of all methods defined in that trait which may look something like this:
['posts', 'roles']
So, User::withAll()
will load all the related models and then run the query
.So, as a result the scope will do something like this:
$query->with(['posts', 'roles']);
Well, this is an abstract idea but hope you got it. Share your idea if you found something better.
According to your Model and related methods, this may look something like this:
class Ticket {
use TicketRelativesTrait;
public function scopeWithAll($query)
{
$relatives = get_class_methods(TicketRelativesTrait::class);
return $query->with($relatives);
}
}
Trait:
trait TicketRelativesTrait {
public function notes(){}
public function events(){}
public function tags(){}
}
// Loads Ticket with all relationships
$ticket = Ticket::withAll()->find(1);
This is more dynamic, no need to mention the related methods and whenever you add a new relationship method in the trait, that will also be loaded.