In the previous article, we talked about Query Builder of Database and learned the code implementation of Fluent Api provided by Query Builder to build SQL statements. In this article, we will learn another important part of Laravel Database: the Eloquent Model.

Eloquent Model Abstracts the properties, associations, and so on of a data table into each Model class, so a Model class is an abstraction of a data table, and a Model object is an abstraction of a single record on that table. Eloquent Model Eloquent Query Builder provides Eloquent Builder for interaction with a database, as well as Eloquent Model association to gracefully connect multiple tables.

Load the Eloquent Builder

Eloquent Builder Is built on top of the Query Builder as well, Eloquent As well.

DB::table('user')->where('name', 'James')->where('age', 27)->get();
Copy the code

When you rewrite it to use Model, it becomes

User::where('name', 'James')->where('age', 27)->get();
Copy the code

We don’t find where, find, or first in the Model class file. We all know that PHP triggers __callStatic when calling a nonexistent class method, and __call when calling a nonexistent instance method. It’s easy to guess that the above methods are dynamically called by these two magic methods. Let’s take a look at the source code.

namespace Illuminate\Database\Eloquent;
abstract class Model implements ...
{
    public function __call($method.$parameters)
    {
        if (in_array($method['increment'.'decrement']) {return $this->$method(...$parameters);
        }

        return $this->newQuery()->$method(...$parameters);
    }
    
    public static function __callStatic($method.$parameters)
    {
        return (new static)->$method(...$parameters);
    }
    
    // new Eloquent Builder
    public function newQuery()
    {
        return $this->registerGlobalScopes($this->newQueryWithoutScopes());
    }
    
    public function newQueryWithoutScopes()
    {
        $builder = $this->newEloquentBuilder($this->newBaseQueryBuilder()); // Set the Model instance of builder so that the information in the Model can be used when building and executing the queryreturn $builder->setModel($this)
                    ->with($this->with)
                    ->withCount($this->withCount); } // Create QueryBuilder protected for database connectionsfunction newBaseQueryBuilder()
    {
        $connection = $this->getConnection();

        return new QueryBuilder(
            $connection.$connection->getQueryGrammar(), $connection->getPostProcessor() ); }}Copy the code

Model queries

As you can see from the above code, all query-related methods called to Model are eventually called to Eloquent Builder instance methods via __call. Eloquent Builder relies on Query Builder for its interaction with the underlying database, Eloquent Builder: QueryBuilder: QueryBuilder: QueryBuilder: QueryBuilder: QueryBuilder: QueryBuilder: QueryBuilder: QueryBuilder: QueryBuilder: QueryBuilder

namespace Illuminate\Database\Eloquent;
class Builder
{
    public function __construct(QueryBuilder $query)
    {
        $this->query = $query;
    }
    
    public function where($column.$operator = null, $value = null, $boolean = 'and')
    {
        if ($column instanceof Closure) {
            $query = $this->model->newQueryWithoutScopes();

            $column($query);

            $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
        } else {
            $this->query->where(... func_get_args()); }return $this;
    }
    
    public function get($columns = [The '*'])
    {
        $builder = $this->applyScopes(); // Load the preloaded model association if the model is obtained, avoiding running n+1 queriesif (count($models = $builder->getModels($columns)) > 0) {
            $models = $builder->eagerLoadRelations($models);
        }

        return $builder->getModel()->newCollection($models);
    }
    
    public function getModels($columns = [The '*'])
    {
        return $this->model->hydrate(
            $this->query->get($columns)->all() )->all(); } // Convert the query result to a Collection public composed of Model objectsfunction hydrate(array $items) {// Create a new model instance$instance = $this->newModelInstance();

        return $instance->newCollection(array_map(function ($item) use ($instance) {
            return $instance->newFromBuilder($item);
        }, $items)); } // The first method is the applicationlimitArr::first() retrieves the model object public from the collectionfunction first($columns = [The '*'])
    {
        return $this->take(1)->get($columns)->first(); } //newModelInstance newFromBuilder is defined in the \Illuminate\Database\EloquentModel class file as publicfunction newFromBuilder($attributes= [].$connection= null) {// Create an instance and set its EXISTS attribute totrue, the save will use this attribute to determine whether it is an INSERT or update$model = $this->newInstance([], true);

    $model->setRawAttributes((array) $attributes.true);

    $model->setConnection($connection? :$this->getConnectionName());

    $model->fireModelEvent('retrieved'.false);

    return $model;
}
Copy the code

Eloquent Builder’s WHERE method sends a call request directly to the Query Builder’s WHERE method, Eloquent Builder. Then the get method is also used to execute the Query through the Get method of Query Builder, and then the newFromBuilder method is used to convert the result array into a set of Model objects. Another commonly used method, first, is also implemented on the basis of the GET method. Limit 1 is applied to query, and the model object is returned to the caller with Arr::first() from the collection returned by the get method.

The Model updating

The update, create, and delete implementations continue from the original query example:

$user = User::where('name', 'James')->where('age', 27)->first();
Copy the code

Now we get an instance of the User Model from the Model query, and we want to change the age of the User to 28:

$user->age = 28;
$user->save();
Copy the code

We know that the properties of the Model correspond to the fields of the table, The $Attributes attribute of the Model instance is set by __get and __set magic methods. The $attributes attribute is set by __get and __set magic methods.

abstract class Model implements ...
{
    public function __get($key)
    {
        return $this->getAttribute($key);
    }
    
    public function __set($key.$value)
    {
        $this->setAttribute($key.$value);
    }
    
    public function getAttribute($key)
    {
        if (! $key) {
            return; } // If the attributes array has an index$keyor$keyCorresponds to a property accessor ''get' . $key . 'Attribute'I take it out here$key// Otherwise try to get the value associated with the modelif (array_key_exists($key.$this->attributes) ||
            $this->hasGetMutator($key)) {
            return $this->getAttributeValue($key);
        }

        if (method_exists(self::class, $key)) {
            return; } // Get the values associated with the modelreturn $this->getRelationValue($key);
    }
    
    public function getAttributeValue($key)
    {
        $value = $this->getAttributeFromArray($key);

        if ($this->hasGetMutator($key)) {
            return $this->mutateAttribute($key.$value);
        }

        if ($this->hasCast($key)) {
            return $this->castAttribute($key.$value);
        }

        if (in_array($key.$this->getDates()) &&
            ! is_null($value)) {
            return $this->asDateTime($value);
        }

        return $value;
    }
    
    protected function getAttributeFromArray($key)
    {
        if (isset($this->attributes[$key]) {return $this->attributes[$key];
        }
    }
    
    public function setAttribute($key.$value) {// If$keyIf a property modifier exists$keyProperty modifier ''set' . $key . 'Attribute'` such as `setNameAttribute`
        if ($this->hasSetMutator($key)) {
            $method = 'set'.Str::studly($key).'Attribute';

            return $this- > {$method} ($value);
        }

        elseif ($value && $this->isDateAttribute($key)) {
            $value = $this->fromDateTime($value);
        }

        if ($this->isJsonCastable($key) &&! is_null($value)) {
            $value = $this->castAttributeAsJson($key.$value);
        }

        if (Str::contains($key.'- >')) {
            return $this->fillJsonAttribute($key.$value);
        }

        $this->attributes[$key] = $value;

        return $this; }}Copy the code

If the Model defines a property modifier, the modifier will be executed when the property is set. We did not use the property modifier in our example. When $user->age = 28, the $Attributes attribute in the User Model instance becomes

protected $attributes= [...'age' => 28,
	...
]
Copy the code

Executing the Eloquent Model save method once you have set the new value for the property will update the database as follows:

abstract class Model implements ...
{
    public function save(array $options = [])
    {
        $query = $this->newQueryWithoutScopes();

        if ($this->fireModelEvent('saving') = = =false) {
            return false; } // All Model instances have exists attributestrue
        if ($this->exists) {
            $saved = $this->isDirty() ?
                        $this->performUpdate($query) : true;
        }

        else {
            $saved = $this->performInsert($query);

            if (! $this->getConnectionName() &&
                $connection = $query->getConnection()) {
                $this->setConnection($connection->getName()); }}if ($saved) {
            $this->finishSave($options);
        }

        return $saved; } // Determine if there are changes to the field publicfunction isDirty($attributes = null)
    {
        return $this->hasChanges(
            $this->getDirty(), is_array($attributes)?$attributes: func_get_args() ); } // The data table fields will be saved in$attributesand$originalSelect public from both arrays by comparing the values of each field before updatefunction getDirty()
    {
        $dirty = [];

        foreach ($this->getAttributes() as $key= >$value) {
            if (! $this->originalIsEquivalent($key.$value)) {
                $dirty[$key] = $value; }}return $dirty;
    }
    
    protected function performUpdate(Builder $query)
    {
        if ($this->fireModelEvent('updating') = = =false) {
            return false;
        }

        if ($this->usesTimestamps()) {
            $this->updateTimestamps();
        }

        $dirty = $this->getDirty();

        if (count($dirty) > 0) {
            $this->setKeysForSaveQuery($query)->update($dirty);

            $this->fireModelEvent('updated'.false);

            $this->syncChanges();
        }

        return true; } // Set for querywhere primary key = xxx
    protected function setKeysForSaveQuery(Builder $query)
    {
        $query->where($this->getKeyName(), '='.$this->getKeyForSaveQuery());

        return $query; }}Copy the code

In save, the exists property of the Model instance is used to determine whether to perform update or insert. $attributes = $original; $original = $original; $original = $original If there is no field that has been changed, the update ends here. If there is, go ahead and execute the performUpdate method, which executes the Eloquent Builder update method, Eloquent Builder relies on a database connection instance called Query Builder to execute the last database update.

The Model in

The exists property is set to true when acquiring a Model using the Eloquent Model (in the newFromBuilder method). The value of this property is false for new Model instances as well. The performInsert method is executed when the save method is executed

protected function performInsert(Builder $query) { if ($this->fireModelEvent('creating') === false) { return false; If ($this->usesTimestamps()) {$this->updateTimestamps(); } $attributes = $this->attributes; If ($this-> getincremented ()) {$this->insertAndSetId($query, $attributes); } else {if (empty($attributes)) {return true; } $query->insert($attributes); $this->exists = true; $this->exists = true; $this->wasRecentlyCreated = true; $this-> firemodeling Event('created', false); return true; }Copy the code

In performInsert, if the table is an increment of the primary key, the new primary key ID is set to the Model instance property after the insert. It also maintains the time field and exists property.

The Model to delete

Eloquent Model Delete: Eloquent Model Delete: Eloquent Model Delete: Eloquent Model Query Builder

//Eloquent Model
public function delete()
{
    if (is_null($this->getKeyName())) {
        throw new Exception('No primary key defined on model.');
    }

    if (! $this->exists) {
        return;
    }

    if ($this->fireModelEvent('deleting') === false) {
        return false;
    }

    $this->touchOwners();

    $this->performDeleteOnModel();

    $this->fireModelEvent('deleted', false);

    return true;
}

protected function performDeleteOnModel()
{
    $this->setKeysForSaveQuery($this->newQueryWithoutScopes())->delete();

    $this->exists = false;
}

//Eloquent Builder
public function delete()
{
    if (isset($this->onDelete)) {
        return call_user_func($this->onDelete, $this);
    }

    return $this->toBase()->delete();
}

//Query Builder
public function delete($id = null)
{
    if (! is_null($id)) {
        $this->where($this->from.'.id', '=', $id);
    }

    return $this->connection->delete(
        $this->grammar->compileDelete($this), $this->cleanBindings(
            $this->grammar->prepareBindingsForDelete($this->bindings)
        )
    );
}
Copy the code

Query Builder implementation details were covered in the previous article, but if you’re curious about how Query Builder performs SQL operations, go back to the previous article.

conclusion

Eloquent Model Uses Eloquent Builder to operate the database Eloquent Model Eloquent Builder is an Eloquent encapsulation of Query Builder. Eloquent Builder transfers calls to these CRUD methods to its Eloquent Query Builder counterpart. So everything you can use in Query Builder can also be used in the Eloquent Model.

In addition to the abstraction of data tables and basic CRUD, another important feature of the model is model association, which helps us elegantly solve the relationship between data tables. We will look at the implementation of the model association section in more detail in a later article.

This article has been included in the series of articles Laravel core code learning, welcome to visit and read.