Eloquent

People's misunderstandings.

Posted by lagbox on June 4, 2016 eloquent laravel oop

Eloquent and people's misunderstandings

Eloquent is an amazing Active Record implementation in Laravel. It is powerful, expressive and simplifies a lot of querying for you. The ability to use relationships is a big benefit of using it.

If you don't like Active Record or don't want to use it, don't! Most people will as the Active Record pattern is a nice way to deal with records, as models.

This article has what I think in a general way is the issue I see with developers in relation to Eloquent, but can be applied in general terms.

What everyone gets wrong

There are numerous things that people get wrong constantly when dealing with Eloquent. Majority of the problems come from people not paying attention to what they are calling methods on. People often write code never really knowing what objects they are dealing with. Lets change this :).

For a better awareness of what objects are returning from calls and what you are calling methods on, I recommend using artisan tinker. You can read my brief article about Artisan Tinker, Tinker all the things

Misunderstandings

find, findOrFail, first, firstOrFail, where, orderBy, lists, pluck, get ... are not Model methods. These methods are not Eloquent Model methods at all, they do not exist on that class.

When you have a model instance and you are trying to do something with that instance, as in thinking of it as a Model (representing a record), you are mostly using methods that exist on the Model class: update, save, delete, etc... . When you are doing query related calls (thinking of the model as a means to query the table), they most likely do not exist on Model: where, get, first, etc ... .

Those methods calls are being passed to Eloquent Builder. The magic method __call and __callStatic are doing this for you. When a method doesn't exist on Eloquent Model an instance of Eloquent Builder is created and the method call is called on that object. This object also has __call which will pass any nonexistent method calls to an instance of Query Builder.

Please check out Model@__call and Model@__callStatic to see how this works as well as Builder@__call.

Illuminate\Database\Eloquent\Model Illuminate\Database\Eloquent\Builder

Eloquent Builder

Eloquent Builder is what your model is passing calls off to in many cases and what Eloquent Model methods are using to do query building. This has its own methods that mimic Query Builder but is using Query Builder inside of it. There is actually very little 'magic' going on once you understand how these relate.

The methods listed in the beginning of the previous section exist on this class. This is what will be calling Query Builder and hydrating models for you.

Some Eloquent Builder Methods:

first, firstOrFail, firstOrFind, firstOrNew, firstOrCreate, find, findOrFail, findOrNew, findMany, update, updateOrCreate, where (and derivatives), get, has, whereHas, pluck, delete, value, chunk, paginate, increment, decrement, with, ...

Query Builder

Query Builder itself is the workhorse for querying. Eloquent Builder is using Query Builder as Query Builder is the mechanism for building queries.

If you check the Eloquent Docs you will see a note like this

Note: Since Eloquent models are query builders, you should review all of the methods available on the query builder. You may use any of these methods in your Eloquent queries.

Laravel 5.2 Docs - Eloquent - Retrieving Multiple Models

All your database related method calls via Eloquent are using Eloquent Builder which is using Query Builder.

Relations

One of the largest benefits of Eloquent is the ability to define and use relationships between models.

Relation methods that you define do not return Models. They return Relation type objects ($this->hasOne(...) => HasOne, $this->hasMany(...) => HasMany, $this->belongsTo(...) => BelongsTo, $this->belongsToMany(...) => BelongsToMany, ...). They can be found in the namespace Illuminate\Database\Eloquent\Relations.

$model->relation() is returning what you have declared it to return. There is no magic here. You create a PHP method on a class and you call that method. That is basic PHP, nothing changes. People often think that Laravel changes PHP, which is completely incorrect. Everything is still just PHP. (You can check Model@belongsTo, Model@hasOne etc, to see what they return)

This is why you can add constraints to your relationship definitions and also do your own queries on them: $model->comments()->where(...)->get().

These objects are using Eloquent Builder. When dealing with Eloquent, Eloquent Builder is used.

Dynamic Properties and Relations

The dynamic properties that eloquent uses for accessing a 'loaded' relationship (or unloaded ones) is confusing to new people. This is something that can be cleared up by looking at the code. This is where people think 'magic' is happening; if you don't look at the code, this is understandable though.

$model->relation is actually going to check if a visible property exists on the model (that is normal PHP). If there isn't an accessible property on the model with that name, a call to __get will be made. This will go to getAttribute which will check if an attribute is defined with that name, if not it will check if a relation is loaded matching that name. If not it will check if a method exists by that name. If a method does exist it will try to load that relationship and return the result.

This can be demonstrated very easily by something like this:

$user = App\User::first();
$user->getTable;

LogicException with message 'Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation'

Because a property doesn't exist by that name, an attribute doesn't exist by that name, there is no relation loaded by that name, but a method exists on that class by that name (the method to get the table name, getTable), Eloquent is trying to load it as a relationship. What makes a relationship method a relationship method? The fact that it returns an object of type Illuminate\Database\Eloquent\Relations\Relation. If it does not return that type of object, it is NOT a relationship method.

This is why $user = User::with('comments')->first(); $user->comments; doesn't cause another query when accessing $user->comments. That relation was loaded via eager loading. Accessing the dynamic property will cause it to return the already loaded relationship, so no new query is executed.

$user->comments is going to return a Collection as it is more than likely a hasMany relationship.

$user->profile would return a single model or null, as I would assume this relationship was defined as a hasOne.

Do yourself a favor and name relationships in a way that makes sense. If they return many, name them plural, if they return one, name them singular. This will help a lot down the road.

$user->comments() - Relation (Builder) object. $user->comments - Collection, the loaded comments relationship.

Eloquent knows how to load these relationships based on their type. For relationships that return one, it knows to only query for one. For relationships that return many, it knows to query for many.

Dynamic Property priority:

  1. Existing visible property on the class (Not dynamic)
    • $model->exists - public property on the class
  • Existing attribute (including accessors)
    • __get() -> getAttribute()
    • array_key_exists($key, $this->attributes) || $this->hasGetMutator($key)
  • Loaded relationship
    • array_key_exists($key, $this->relations)
  • Relationship method
    • method_exists($this, $key)
  • Return null

(Extracted from getAttribute and the methods it calls.)

Calling methods directly on a model compared to a Builder

When you are calling methods like update directly on a model instance that is calling Model@update. When you have a relation object or a builder you are calling the methods that exist on it, not a model.

People seem to struggle with this one often. This is another case of just needing to know what type of object you are calling a method on and checking that code.

You can very easily figure this stuff out by looking. There is no reason to guess or be in a state mystery about what is happening :-). If you need to do some DDDD (Dump and Die [dd()] Driven Development, har har) or use tinker to know what you are dealing with, you should do that.

Round up

I see all these misunderstandings every day. As you can see these misunderstandings are just that, not understanding what objects are in play. This stuff is slightly complex but you can understand it with some time.

If this stuff was easy, we would all be out of work yesterday. Knowing what you are doing in context and reading code is vital to a developer.

Theories

I think a lot of this has to do with how 'magical' things appear to be when you don't understand what is happening. Which then creates the expectation that everything is magic and you couldn't know about it because its a mystery, which is just not true.

To me there can potentially be an issue with using IDEs. They are very helpful but could be making people very unaware of the libraries they are using. The IDE is giving you all this autocomplete but are you ever actually learning where this stuff comes from or what these methods actually look like? Depending on your flow, perhaps you do, perhaps you don't.

You really need to have a very strong base in PHP to use a modern Object Oriented framework like Laravel. You should take the time to be as competent with those basics as possible.

PHP actually has a simple syntax, there isn't a lot involved. How the language works for 90% of it is very straight forward. The OOP part might take a little more work to understand but is also not complex compared to other languages. These are all fundamentals you are required to know and put the time in to be proficient with.

Future

There will be some articles about how to use these features and how they can help you. Some explanation of how these things work and can be used.

This article is kinda skewed to a particular point that is important to me. If there are misunderstandings that you see or don't get with Eloquent add some comments and I can add some more stuff to this article.