您的位置:首页 > 编程语言 > PHP开发

【Laravel】Eloquent ORM的底层实现

2018-01-21 19:07 603 查看


动机

首先来说一个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[]

4000
*/
 //从数据库表中获取所有模型
 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集合的形式进行封装。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: