【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集合的形式进行封装。
相关文章推荐
- laravel - Eloquent ORM 快速使用
- Laravel5.1-Eloquent ORM:起步
- laravel - Eloquent ORM 快速使用
- Laravel基础-Eloquent ORM 模型
- PHP 框架Laravel Eloquent 实现身份验证
- gorose, 最像 laravel's eloquent 的go数据库操作orm, 风骚的链式调用, 让你深深陷入不能自拔
- Laravel 5.3 auth中间件底层实现详解
- Laravel 5框架学习之Eloquent (laravel 的ORM)
- gorose, 最像 laravel's eloquent 的go数据库操作orm, 风骚的链式调用, 让你深深陷入不能自拔
- laravel5.1 数据库相关,操作底层实现
- 在 CodeIgniter 中使用 Laravel Eloquent ORM
- Laravel Eloquent ORM的hasOne和belongsTo
- Laravel 5.3 登录注册底层实现详解
- Laravel之Eloquent ORM关联
- PHP框架 Laravel Eloquent ORM 批量插入数据 && 批量更新目前没有
- 给 CI 插上翅膀——在 CodeIgniter 2 中使用 Laravel Eloquent ORM
- Laravel Eloquent ORM 时如何查询表中指定的字段
- Laravel Eloquent ORM Active Recode
- Laravel5 Eloquent ORM 查询
- laravel组件单独加载(2):模型 Eloquent ORM