Eloquent under the hood: where is the where method?

Sep 3, 2022

When you look through the source code for the Model class, you might be surprised to find that the where method (along with other methods used to create queries) is nowhere to be found.

How then, can you call where as if it was a static method defined on the model class? What is this magic?

YourModel::where()
// where is ☝️ this defined? 🤔

Is it magic?

In a way, you could say it's magic. Under the hood, Laravel uses something called magic methods.

From the documentation:

Magic methods are special methods which override PHP's default's action when certain actions are performed on an object.

In this article, I'm going to focus on two of the magic methods offered by PHP: __call and __callStatic.

But before that, let's talk about where is the where method actually defined. Since it doesn't come out of thin air.

Where is the where?

If the where method is not defined in the Model class, where is it?

If you keep digging through the Laravel framework code you will find it hiding in the Eloquent query builder class:

Here is the source code.

namespace Illuminate\Database\Eloquent;

//...

class Builder implements BuilderContract
{
    // ...
    
    /**
     * Add a basic where clause to the query.
     *
     * @param  \Closure|string|array  $column
     * @param  mixed  $operator
     * @param  mixed  $value
     * @param  string  $boolean
     * @return $this
     */
    public function where($column, $operator = null, $value = null, $boolean = 'and')
    {   
        // ... 
    }
    
    //...
}

But, how does that function get executed when we statically call where on a model?

That's where __call and __callStatic come in.

The magic methods

These two magic methods are very similar. Their function is almost the same.

When the __call method is defined in a class, it will be triggered when you try to call a method that doesn't exist on the instance of that class.

That might sound confusing, so here is an example:


class SomeObject
{
    public function __call($name, $arguments)
    {
        echo "$name 🔥 " . implode(', ', $arguments) . "\n";
    }
}

$obj = new SomeObject();
$obj->someMethod('is fire'); // prints "someMethod 🔥 is fire

As you can see above, the __call method receives two parameters.

  1. The name of the method called (in this case someMethod)
  2. An array contained the arguments passed to the method (in this case ["is fire"])

The __callStatic method works exactly the same, except it gets triggered when you try to call an undefined static method.

<?php

class SomeObject
{
    public static function __callStatic($name, $arguments)
    {
        echo "$name 🪄 " . implode(', ', $arguments) . "\n";
    }
}

SomeObject::someOtherMethod("is magic"); // prints "someOtherMethod 🪄 is magic"

Back to the model class

In the Model class, Laravel uses the __callStatic method, and this gets triggered when we call where statically in our models:

abstract class Model 
{
    //...

    public static function __callStatic($method, $parameters)
    {
        return (new static)->$method(...$parameters);
    }
    
    //...
}

// That means that this 👇
User::where('is_active', '=', true);

(new User())->where('is_active', '=', true);
// is the same as this ☝️

From there, Laravel creates a new instance of our model class and calls the where method on it, but this time it's called on the instance, so it's not called as a static method anymore.

That means that the __call method gets triggered since there is no where method defined in the Model class.

abstract class Model 
{
    //...

    public function __call($method, $parameters)
    {
        // Some other checks..

        // newQuery() returns a new eloquent builder   
        // with this instance as the model 👇
        return $this->forwardCallTo($this->newQuery(), $method, $parameters);
    }
    

    protected function forwardCallTo($object, $method, $parameters)
    {
        try {
            return $object->{$method}(...$parameters);
        } catch (Error|BadMethodCallException $e) {
            // Error handling
        }
    }

    //...
}

// That means that this 👇
User::where('is_active', '=', true);

(new User())->newQuery()->where('is_active', '=', true)
// is the same as this ☝️

In that __call method, Laravel makes a call to the method newQuery(). This method creates an instance of Eloquent query builder, the same class I mentioned previously where the where is actually defined (🤯).

And then forwards the call to that instance. So in short, when you call the where method on your Eloquent model, Laravel makes use of these magic methods to forward that call to the Eloquent query builder class.

Conclusion

Now you know a little more about how Eloquent works under the hood. In my experience, knowing how the framework works under the hood can make debugging an easier task. So next time you use the where on a model. Remember, you are actually calling where on the Eloquent builder class.

If you would like to know how other parts of the framework work under the hood or have any other questions, send me a message on Twitter or write me an email at [email protected]. I'm happy to help.