Difference Between Method Calls $Model-≫Relation(); and $Model-≫Relation;

Difference between method calls $model- relation(); and $model- relation;


Short answer

$model->relation() returns the relationship object

$model->relation returns the result of the relationship



Long answer

$model->relation() can be explained pretty simple. You're calling the actual function you defined your relation with. Yours for distributor probably looks somewhat like this:

public function distributors(){
return $this->hasMany('Distributor');
}

So when calling $store->distributors() you just get the return value of $this->hasMany('Distributor') which is an instance of Illuminate\Database\Eloquent\Relations\HasMany

When do you use it?

You usually would call the relationship function if you want to further specify the query before you run it. For example add a where statement:

$distributors = $store->distributors()->where('priority', '>', 4)->get();

Of course you can also just do this: $store->distributors()->get() but that has the same result as $store->distributors.


Which brings me to the explanation of the dynamic relationship property.

Laravel does some things under the hood to allow you to directly access the results of a relationship as property. Like: $model->relation.

Here's what happens in Illuminate\Database\Eloquent\Model

1) The properties don't actually exist. So if you access $store->distributors the call will be proxied to __get()

2) This method then calls getAttribute with the property name getAttribute('distributors')

public function __get($key)
{
return $this->getAttribute($key);
}

3) In getAttribute it checks if the relationship is already loaded (exists in relations). If not and if a relationship method exists it will load the relation (getRelationshipFromMethod)

public function getAttribute($key)
{
// code omitted for brevity

if (array_key_exists($key, $this->relations))
{
return $this->relations[$key];
}

$camelKey = camel_case($key);

if (method_exists($this, $camelKey))
{
return $this->getRelationshipFromMethod($key, $camelKey);
}
}

4) In the end Laravel calls getResults() on the relation which then results in a get() on the query builder instance. (And that gives the same result as $model->relation()->get().

Calling a model method as property and not a function

Eloquent relationships are defined as methods on your Eloquent model classes.

public function projects() {
return $this->hasMany(Project::class, 'project_owner_id');
}

Once the relationship is defined, we may retrieve the related record using Eloquent's dynamic properties. Dynamic properties allow you to access relationship methods as if they were properties defined on the model:

auth()->user()->projects;

Defining relationships as methods provides powerful method chaining and querying capabilities.For example, we may chain additional constraints on this projects relationship:

auth()->user()->projects()->where("status", 1)->get();

laravel with() method versus load() method

Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.

"Eager" here means that we're associating all the related models for a particular result set using just one query, as opposed to having to run n queries, where n is the number of items in the initial set.


Eager loading using with()

If we eager load using with(), for example:

$users = User::with('comments')->get(); 

...if we have 5 users, the following two queries get run immediately:

select * from `users`
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)

...and we end up with a collection of models that have the comments attached to the user model, so we can do something like $users->comments->first()->body.


"Lazy" eager loading using load()

Or, we can separate the two queries, first by getting the initial result:

$users = User::all();

which runs:

select * from `users`

And later, if we decide that we need the related comments for all these users, we can eager load them after the fact:

$users = $users->load('comments');

which runs the 2nd query:

select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)

...and we end up with the same result, just split into two steps. Again, we can call $users->comments->first()->body to get to the related model for any item.


Why use load() vs. with()? load() gives you the option of deciding later, based on some dynamic condition, whether or not you need to run the 2nd query. If, however, there's no question that you'll need to access all the related items, use with().


The alternative to either of these would be looping through the initial result set and querying a hasMany() relation for each item. This would end up running n+1 queries, or 6 in this example. Eager loading, regardless of whether it's done up-front with with() or later with load(), only runs 2 queries.

Laravel: What is the purpose of the `loadMissing` function?

Very good question; there are subtle differences which are not getting reflected instantly by reading through the documentation.

You are comparing "Lazy Eager Loading" using loadMissing() to "Lazy Loading" using magic properties on the model.

The only difference, as the name suggests, is that:

  • "Lazy loading" only happens upon the relation usage.
  • "Eager lazy loading" can happen before the usage.

So, practically, there's no difference unless you want to explicitly load the relation before its usage.

It also worths a note that both load and loadMissing methods give you the opportunity to customize the relation loading logic by passing a closure which is not an option when using magic properties.

$book->loadMissing(['author' => function (Builder $query) {
$query->where('approved', true);
}]);

Which translates to "Load missing approved author if not already loaded" which is not achievable using $book->author unless you define an approvedAuthor relation on the model (which is a better practice, though).


To answer your question directly; yeah, there won't be any difference if you remove:

$book->loadMissing('author'); 

in that particular example as it's being used right after the loading. However, there might be few use cases where one wants to load the relation before its being used.


So, to overview how relation loading methods work:

Eager loading

Through the usage of with() you can "eager load" relationships at the time you query the parent model:

$book = Book::with('author')->find($id);

Lazy eager loading

To eager load a relationship after the parent model has already been retrieved:

$book->load('author');

Which also might be used in a way to only eager load missing ones:

$book->loadMissing('author');

Contrary to the load() method, loadMissing() method filters through the given relations and lazily "eager" loads them only if not already loaded.

Through accepting closures, both methods support custom relation loading logics.

Lazy loading

Lazy loading which happens through the usage of magic properties, is there for developer's convenience. It loads the relation upon its usage, so that you won't be needing to load it beforehand.


@rzb has mentioned a very good point in his answer as well. Have a look.

Laravel Eloquent Relationships has no results

For the first problem call:

$state_1->cities 

For the updated question do this:

City::with('state')->get(); /* the ->where('state_id',$id); shouldn't be 
* required as you've already declared the relationship
*/

This probably will resolve your problem. If get() is not defined in your Eloquent query it will return nothing.

Load all relationships for a model

No it's not, at least not without some additional work, because your model doesn't know which relations it supports until they are actually loaded.

I had this problem in one of my own Laravel packages. There is no way to get a list of the relations of a model with Laravel. It's pretty obvious though if you look at how they are defined. Simple functions which return a Relation object. You can't even get the return type of a function with php's reflection classes, so there is no way to distinguish between a relation function and any other function.

What you can do to make it easier is defining a function that adds all the relationships.
To do this you can use eloquents query scopes (Thanks to Jarek Tkaczyk for mentioning it in the comments).

public function scopeWithAll($query) 
{
$query->with('foo', 'bar', 'baz');
}

Using scopes instead of static functions allows you to not only use your function directly on the model but for example also when chaining query builder methods like where in any order:

Model::where('something', 'Lorem ipsum dolor')->withAll()->where('somethingelse', '>', 10)->get();

Alternatives to get supported relations

Although Laravel does not support something like that out of the box you can allways add it yourself.

Annotations

I used annotations to determine if a function is a relation or not in my package mentioned above. Annotations are not officially part of php but a lot of people use doc blocks to simulate them.
Laravel 5 is going to use annotations in its route definitions too so I figuered it not to be bad practice in this case. The advantage is, that you don't need to maintain a seperate list of supported relations.

Add an annotation to each of your relations:

/**
* @Relation
*/
public function foo()
{
return $this->belongsTo('Foo');
}

And write a function that parses the doc blocks of all methods in the model and returns the name. You can do this in a model or in a parent class:

public static function getSupportedRelations() 
{
$relations = [];
$reflextionClass = new ReflectionClass(get_called_class());

foreach($reflextionClass->getMethods() as $method)
{
$doc = $method->getDocComment();

if($doc && strpos($doc, '@Relation') !== false)
{
$relations[] = $method->getName();
}
}

return $relations;
}

And then just use them in your withAll function:

public function scopeWithAll($query) 
{
$query->with($this->getSupportedRelations());
}

Some like annotations in php and some don't. I like it for this simple use case.

Array of supported relations

You can also maintain an array of all the supported relations. This however needs you to always sync it with the available relations which, especially if there are multiple developers involved, is not allways that easy.

protected $supportedRelations = ['foo','bar', 'baz'];

And then just use them in your withAll function:

public function scopeWithAll($query) 
{
return $query->with($this->supportedRelations);
}

You can of course also override with like lukasgeiter mentioned in his answer. This seems cleaner than using withAll. If you use annotations or a config array however is a matter of opinion.



Related Topics



Leave a reply



Submit