Reputation: 16283
In my project I'm writing a method on one of my models that uses one of it's relationships and a sub-relationship so I am having to use lazy eager loading:
class MyModel extends Model
{
public function doSomething()
{
// Lazy eager load relationship and subrelationship
$this->load('relationship', 'relationship.subrelationship');
$relatedItems = $this->relationship;
foreach ($relatedItems as $relatedItem) {
$subrelatedItems = $relatedItem->subrelationship;
foreach ($subrelatedItems as $subrelatedItem) {
// Do something...
return true;
}
}
return false;
}
}
The Model::load()
method in Laravel can be used to re-load a relationship and does a new database query every time. Therefore, each time I call my method MyModel::doSomething()
, (Or call another method that uses similar relationships) another database query is executed.
I know that in Laravel you can call a relationship a number of times like this:
$relatedItems = $model->relationship;
$relatedItems = $model->relationship;
$relatedItems = $model->relationship;
$relatedItems = $model->relationship;
..and it doesn't repeat the query since it has already loaded the relationship.
I was wondering if it was possible to avoid querying the database every time I want to use my relationship inside the model? I had the idea that I could use $this->getRelations()
to work out which relationships had been loaded and then just skip them if they already had:
$toLoad = ['relationship', 'relationship.subrelationship'];
$relations = $this->getRelations();
foreach ($toLoad as $relationship) {
if (array_key_exists($relationship, $relations)) {
unset($toLoad[$relationship]);
}
}
if (count($toLoad) > 0) {
$this->load($toLoad);
}
That works to an extent, it's able to skip loading relationship
each time, but relationship.subrelationship
isn't actually stored in the array returned by $this->getRelations()
. I would imagine it is stored in the sub-model(s) as subrelationship
.
Cheers
Upvotes: 1
Views: 1829
Reputation: 16283
I've managed to work out a way around this problem. Originally I had something like this:
class MyModel extends Model
{
public function relationship()
{
return $this->hasMany('App\Related');
}
}
class Related extends Model
{
public function subrelated()
{
return $this->belongsTo('App\Subrelated');
}
}
class Subrelated extends Model
{
}
After a lot of digging round in the Laravel source, I have found that when you call a relationship like it's a property using a magic method (namely __get()
), Laravel stores it in the models $relations
property for use later. With that in mind I added another method to MyModel
like this:
class MyModel extends Model
{
public function relationship()
{
return $this->hasMany('App\Related');
}
public function relationshipWithSubrelated()
{
return $this->relationship()->with('subrelated');
}
}
Now I can call the relationship like the following as many times as I need to and it always gives me the same results:
$myModel = MyModel::find(1);
$myModel->relationshipWithSubrelated;
Wish I thought of it before I spent hours trying to work around!
Upvotes: 1
Reputation: 989
I think the problem here is, that you're creating the function in the Model file. And that kind of limits you and you have to load the relationship every time.
What I would do is create a function which takes the model objects you want to doSomething with as arguments. Let me clarify:
Define a function
Now if you're following a design pattern you probably have a place to put the function in. Otherwise put it where you'd normally put it.
public function doSomething($myModels)
{
$relatedItems = $myModels->relationship;
foreach ($relatedItems as $relatedItem) {
$subrelatedItems = $relatedItem->subrelationship;
foreach ($subrelatedItems as $subrelatedItem) {
// Do something...
return true;
}
}
return false;
}
Now when calling the function, pass the models with the relationship.
For example
$myModels = MyModel::where('created_at', <, Carbon::now())->with('relationship')->get();
doSomething($myModels)
If you need to load several or deeper relationships you can do
...->with('relationship.subrelationship', 'secondrelationship')->get()
The code isn't tested but I think you get the point. Look into with()
Upvotes: 1