Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Eloquent\Builder inherit from Query\Builder in laravel?

Tags:

php

laravel

In laravel, the Eloquent\Builder class sends every call to methods which it doesn't have to an internal Query\Builder. To me this sounds like inheritance. Somebody knows why they didn't implement it so that the Eloquent\Builder extends Query\Builder? The reason I first noticed it was that I got "Call to undefined method" errors in IDEs, despite the code working fine which is the curse of magic methods I suppose.

For reference, here is relevant source from Eloquent\Builder.

/**
 * The base query builder instance.
 *
 * @var \Illuminate\Database\Query\Builder
 */
protected $query;

protected $passthru = array(
    'toSql', 'lists', 'insert', 'insertGetId', 'pluck', 'count',
    'min', 'max', 'avg', 'sum', 'exists', 'getBindings',
);

public function __call($method, $parameters)
{
    if (method_exists($this->model, $scope = 'scope'.ucfirst($method)))
    {
        return $this->callScope($scope, $parameters);
    }
    else
    {
        $result = call_user_func_array(array($this->query, $method), $parameters);
    }

    return in_array($method, $this->passthru) ? $result : $this;
}
like image 463
Simon Bengtsson Avatar asked Aug 06 '14 10:08

Simon Bengtsson


People also ask

What is the difference between eloquent and query builder in Laravel?

Eloquent ORM is best suited working with fewer data in a particular table. On the other side, query builder takes less time to handle numerous data whether in one or more tables faster than Eloquent ORM. In my case, I use ELoquent ORM in an application with tables that will hold less than 17500 entries.

How does Laravel eloquent work?

Eloquent is an object relational mapper (ORM) that is included by default within the Laravel framework. An ORM is software that facilitates handling database records by representing data as objects, working as a layer of abstraction on top of the database engine used to store an application's data.

What is Laravel eloquent query?

The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table. Before getting started, be sure to configure a database connection in app/config/database. php .

Is Laravel eloquent slow?

Using eloquent is great because its have many cool features but when it come to the speed, it slightly slower than query builder because of ORM.


2 Answers

I'll start off with my conclusion. I think it was just poorly engineered.

Let me do a few explanations, and also reply to the accepted answer by @Simon Bengtsson

I'm by no means trying to offend anyone, but I'm just trying to word out my thoughts.


Actually the whole point of inheritance is that they have similar characteristics, but you would like to add an extra layer to put some extended features. Obviously, the extended "child" class will know more than the "parent" class (such as knowing about the Eloquent Model)

To me, a "Builder" is a Builder. Afterall, both are actually building a query, regardless of what conditions its checking or observing. That's the purpose of a Builder (from what I see at least). So it's in the same layer (except Eloquent\Builder has some extra features).

The current implementation is overriding the nature of a few methods such as where() (which are basically those in Query\Builder, same name, same arguments). It's also adding a few methods, which eventually calls methods like where(). This is all about inheritance. If I call where(), and it exists in child class, the child class method gets called. If not, it will call the method in parent class.


Later down the line it becomes more difficult to then decouple the low level and ORM level components if you hypothetically wanted to use some new NoSQL DB instead and simply write a Query\Builder signature compliant drop-in class.

@Simon Bengtsson

I don't see how it gets difficult to decouple. All you have to do, is to write a new class Query\MongoDBBuilder, which has the same interface as Query\Builder. If you're worried, you can always make the classes "implements" an Interface. If you ask me, actually the current way is harder to decouple, because it's quite messy right now as to "which features are overridden and which are not".

I do encourage class decoupling in some cases, but if I were to decouple this one (which I don't think is necessary here), I would do it this way:

  • Eloquent\Model
  • Eloquent\Adapter (This is some sort of intermediate layer, where you could put connection settings etc.)
  • Eloquent\Builder extends Query\Builder (This is a "wrapper" for Query\Builder so that it could do slightly more, but achieving the same goals)

Child classes also have access to protected properties on parent classes so that Eloquent\ChildOfQueryBuilder would have the freedom to rely on the low level implementation of Query\Builder and could be coupled to it.

@Simon Bengtsson

This is the whole point about inheritance. You would to be able to access protected properties to override a feature given by the parent. Let's look at it from another point of view, what if you NEED to access these variables in order to modify the feature? In fact, the author declaring these members "protected" already implies that he is ready for another class to extend it. That's the only purpose of making it "protected", correct?

That's why you would have to know exactly what a parent class is doing before inheriting it, because it could be dangerous.


The reason why I'm giving this huge explanation is because I'm trying to modify some features for Eloquent. I've been looking at the implementation of days (the 3 god classes: Eloquent\Model, Eloquent\Builder, and Query\Builder). The layering and aliasing was really messed up. Extending the Model class requires me to redeclare almost equivalent functions due to its poor implementation. But that's off topic.

In conclusion, if you ask "why", I would actually say (now I know people will kick me) that it was just poorly engineered and implemented.

like image 113
Thomas Cheng Avatar answered Oct 18 '22 10:10

Thomas Cheng


I think delmadord was sort of heading towards the right answer but I think bringing the IoC into it blurs the issue. The basic answer is that the two classes represent abstractions of the database at different levels. Query\Builder is the lower level abstraction and Eloquent\Builder is a higher level abstraction which is why, as you pointed out it makes use of Query\Builder for its core functionality.

You can tell this from the properties that are defined for each class. One has properties surrounding things such as:

  • connection settings,
  • database grammars and,
  • various things relating to the query that will be assembled

The other is more an ORM and is concerned with higher level concerns such as whether eager relationship loading should be used and which model the query is currently bound to. Arguably, you could use inheritance here and might have pretty much the same functionality but from an architectural standpoint there are issues with this.

One is that, strictly speaking, I should always be able to use a child class as a drop in replacement for a parent class. This is simpler to see if you adopt the "is a" reading of inheritance. This means that if you used inheritance for this pair of classes, strictly speaking, any developer down the line is able to make use of Eloquent\Builder just as easily as Query\Builder and in fact might be encouraged to do so throughout the code in order to have one less class to track in their minds. This will mean code may call $builder->getRelation() and rely on the consequences on (for argument's sake) $builder->wheres all in the same block of code.

Later down the line it becomes more difficult to then decouple the low level and ORM level components if you hypothetically wanted to use some new NoSQL DB instead and simply write a Query\Builder signature compliant drop-in class.

Child classes also have access to protected properties on parent classes so that Eloquent\ChildOfQueryBuilder would have the freedom to rely on the low level implementation of Query\Builder and could be coupled to it. You could argue that you could just make it private but then you can longer write a thin low-level extension to Query\Builder (adding some clustering, sharding or security logic maybe) that may (rightfully) need access to the implementation details that could have been a drop-in replacement for the low-level functionality.

Another is that the size of the class grows quite rapidly if you pile everything related into one "God object". Both of the Builder classes have a large amount of abstraction layer specific logic within them. If you inherit, then Eloquent\Builder would effectively be the union of the two and you would have almost twice as much code to scan for issues and your IDE would give you a much broader range of autocompletion options, with a significant fraction being unrelated to the abstraction depth you're working at.


The same pattern is used sometimes in other places where two classes are very related to each other, but inheritance would cause perception issues which would encourage their use in ways the developer does not want. With Java for example, immutable classes typically have mutable counterparts but neither inherits the other and they might not even have common ancestry.

This happens because the immutable class can't have any mutator methods. Inheriting from the mutable version would require overriding and disabling each mutator method and forever ensuring this happens correctly. Forget this once and your immutable class may be mutable for a few revisions. And you can't allow inheritance the other way around because doing so means a method expecting an immutable class might get a mutable child and add grey hairs to people.

This is another instance of related things playing distinct roles and thus being conceptually separated. Its not that you can't do things a particular way but more that certain architecture choices discourage behaviours you don't want and leave room to do things that you do think you'll want to do down the line. Sometimes with very large code-bases, decoupling things along an interface like that really helps to make it easier to reason about and work with.

like image 4
jeteon Avatar answered Oct 18 '22 11:10

jeteon