【Laravel】Eloquent ORM的底层实现

动机

首先来说一个Eloquent ORM的设计思想:Eloquent ORM就是将数据库中复杂的数据结果封装成更加smart的接口提供给用户使用。数据库中每一个表对应一个类,而类的实例对应数据库表中的一行记录,数据库中的列值会对应到类的属性上。一直在使用这个东西,之前并没有深入去了解过,借这个机会查阅了Laravel底层的源码。

笔者的环境:Laravel5.5, Sublime编辑器(强烈建议按转代码提示插件)。

模型类的创建


先创建一个简单的例子用来跟踪底层的代码,Controller与Model对应如下:

namespace App\Http\Controllers;

use App\Article;

class ArticleController extends Controller
{
    public function index() 
    {
        dd(Article::all());
    }
}
namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model 
{

}
     注意:路由就不出来了。

模型类的实现原理


新创建的类什么都没有做,却可以实现对数据库的相关操作,关键点就是它继承了Illuminate\Database\Eloquent\Model。分阶段介绍:第一阶段是Eloquent ORM查询构造器的生成,第二阶段是数据库操作方法的执行。

Eloquent ORM查询构造器的生成


英文不好有些地方借助了翻译软件加上自己的理解些的注释
文件 \Illuminate\Database\Eloquent\Model.php;
/**
     * Get all of the models from the database.
     *
     * @param  array|mixed  $columns
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
 //从数据库表中获取所有模型
 public static function all($columns = ['*'])
    {
        return (new static)->newQuery()->get(
            is_array($columns) ? $columns : func_get_args()
        );
    }

/**
     * Create a new Eloquent model instance.
     *
     * @param  array  $attributes
     * @return void
     */
 //这个类的构造函数
    public function __construct(array $attributes = [])
    {
        $this->bootIfNotBooted();

        $this->syncOriginal();

        $this->fill($attributes);
    }

这里通过"new static"创建了一个模型实例,查了资料发现这里是用到了静态绑定,生成App\Article类的实例,"newQuery()"语句生成了Eloquent ORM的查询构造器。

文件 \Illuminate\Database\Eloquent\Model.php;

/**
     * Get a new query builder for the model's table.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
 //针对模型类对应的数据表生成一个查询构造器
 public function newQuery()
    {
        return $this->registerGlobalScopes($this->newQueryWithoutScopes());
    }

 /**
     * Get a new query builder that doesn't have any global scopes.
     *
     * @return \Illuminate\Database\Eloquent\Builder|static
     */
 //获取查询构造器
    public function newQueryWithoutScopes()
    {
        $builder = $this->newEloquentBuilder($this->newBaseQueryBuilder());

        // Once we have the query builders, we will set the model instances so the
        // builder can easily access any information it may need from the model
        // while it is constructing and executing various queries against it.
        return $builder->setModel($this)
                    ->with($this->with)
                    ->withCount($this->withCount);
    }

/**
     * Get a new query builder instance for the connection.
     *获取针对一个连接的查询构造器     
 * @return \Illuminate\Database\Query\Builder
     */
 //
    protected function newBaseQueryBuilder()
    {
        $connection = $this->getConnection();

        return new QueryBuilder(
            $connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
        );
    }

/**
     * Get the database connection for the model.
     * 通过模型类获取数据库连接
     * @return \Illuminate\Database\Connection
     */
    public function getConnection()
    {
        return static::resolveConnection($this->getConnectionName());
    }

/**
     * Resolve a connection instance.
     * 获取一个连接实例
     * @param  string|null  $connection
     * @return \Illuminate\Database\Connection
     */
    public static function resolveConnection($connection = null)
    {
        return static::$resolver->connection($connection);
    }
这里的$resolver其实是Illuminate\Database\DatabaseManager,是Illuminate\Database\ConnectionResolverInterface接口的实例。

文件Illuminate\Database\DatabaseManager.php如下:
/**
     * Get a database connection instance.
     * 获取一个数据库连接的实例
     * @param  string  $name
     * @return \Illuminate\Database\Connection
     */
    public function connection($name = null)
    {
        list($database, $type) = $this->parseConnectionName($name);

        $name = $name ?: $database;

        // If we haven't created this connection, we'll create it based on the config
        // provided in the application. Once we've created the connections we will
        // set the "fetch mode" for PDO which determines the query return types.
        if (! isset($this->connections[$name])) {
            $this->connections[$name] = $this->configure(
                $this->makeConnection($database), $type
            );
        }

        return $this->connections[$name];
    }

 
文件 \Illuminate\Database\Eloquent\Model.php;
/**
     * Create a new Eloquent query builder for the model.
     * 为模型创建一个新的Eloquent查询构造器
     * @param  \Illuminate\Database\Query\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder|static
     */
    public function newEloquentBuilder($query)
    {
        return new Builder($query);
    }
文件 \Illuminate\Database\Eloquent\Builder.php;

/**
     * Create a new Eloquent query builder instance.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @return void
     */
    public function __construct(QueryBuilder $query)
    {
        $this->query = $query;
    }

通过newBaseQueryBuilder()函数创建一个基础查询构造器,而这个查询构造器数据库连接最终式通过数据库控制器Illuminate\Database\DatabaseManager.php中的connection()函数实现的。然后\Illuminate\Database\Eloquent\Model.php
中newEloquentBuilder()函数对该基础查询构造器进行封装,实现Eloquent查询构造器的创建。

文件 \Illuminate\Database\Eloquent\Builder.php;
/**
     * Set a model instance for the model being queried.
     * 为查询构造器设置一个模型实例
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return $this
     */
    public function setModel(Model $model)
    {
        $this->model = $model;

        $this->query->from($model->getTable());

        return $this;
    }
文件 \Illuminate\Database\Query\Builder.php;

/**
     * Set the table which the query is targeting.
     * 设置所要查询的数据表
     * @param  string  $table
     * @return $this
     */
    public function from($table)
    {
        $this->from = $table;

        return $this;
    }
文件 \Illuminate\Database\Eloquent\Model.php;
    /**
     * Get the table associated with the model.
     * 获取模型关联的数据表
     * @return string
     */
    public function getTable()
    {
        if (! isset($this->table)) {
            return str_replace(
                '\\', '', Str::snake(Str::plural(class_basename($this)))
            );
        }

        return $this->table;
    }

完成Eloquent查询构造器的实例化后,也就获得了与数据库的连接。数据库表名式通过getTable函数获得的,如果对象的$table没有设置,则以模型类名的复数作为数据表名,注意上面getTable()中的方法。这就是为什么我们项目中如果创建的模型类没有指定数据表名,依然可以操作数据库中的数据表。

对数据库的操作


完成Eloquent查询构造器的实例化后,可以对数据库进行操作,下面来看Eloquent查询构造器中的方法实现的原理即"$instance->newQuery()->get($columns)"的get部分。


文件 \Illuminate\Database\Eloquent\Builder.php;
/**
     * Execute the query as a "select" statement.
     * 执行一个select查询语句
     * @param  array  $columns
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public function get($columns = ['*'])
    {
        $builder = $this->applyScopes();

        // If we actually found models we will also eager load any relationships that
        // have been specified as needing to be eager loaded, which will solve the
        // n+1 query issue for the developers to avoid running a lot of queries.
        if (count($models = $builder->getModels($columns)) > 0) {
            $models = $builder->eagerLoadRelations($models);
        }

        return $builder->getModel()->newCollection($models);
    }

 

接下来介绍Eloquent ORM是如何封装结果数据的,这部分是由hydrate()实现的。

文件 \Illuminate\Database\Eloquent\Builder.php;

/**
     * Create a collection of models from plain arrays.
     * 为数组创建一个模型类实例的集合
     * @param  array  $items
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function hydrate(array $items)
    {
        $instance = $this->newModelInstance();

        return $instance->newCollection(array_map(function ($item) use ($instance) {
            return $instance->newFromBuilder($item);
        }, $items));
    }


/**
     * Create a new model instance that is existing.
     * 创建一个新的模型类实例
     * @param  array  $attributes
     * @param  string|null  $connection
     * @return static
     */
    public function newFromBuilder($attributes = [], $connection = null)
    {
        $model = $this->newInstance([], true);

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

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

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

        return $model;
    }

/**
     * Set the array of model attributes. No checking is done.
     * 通过数组设置模型类实例的attributes
     * @param  array  $attributes
     * @param  bool  $sync
     * @return $this
     */
    public function setRawAttributes(array $attributes, $sync = false)
    {
        $this->attributes = $attributes;

        if ($sync) {
            $this->syncOriginal();
        }

        return $this;
    }
文件 \Illuminate\Database\Eloquent\Model.php;
/**
     * Create a new Eloquent Collection instance.
     * 创建一个新的Eloquent集合实例
     * @param  array  $models
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function newCollection(array $models = [])
    {
        return new Collection($models);
    }
文件\Illuminate\Support\Collection.php

/**
     * Create a new collection.
     * 创建一个\Illuminate\Support\Collection类实例
     * @param  mixed  $items
     * @return void
     */
    public function __construct($items = [])
    {
        $this->items = $this->getArrayableItems($items);
    }

获取查询数据后将对数据进行封装,通过array_map()函数对数组中的每项进行处理。处理函数为一个匿名函数,该函数中调用newFromBuilder()函数进行数据处理,该函数通过newInstance()函数实例化一个Model类,并将数据复制给该实例的$attributes属性.接着将查询获得的数组传递给集合类构造函数,最终将查询获得的数据以Eloquent集合的形式进行封装。



你可能感兴趣的:(>>PHP<<)