上一篇文章咱们讲了Database的查询构建器QueryBuilder, 学习了QueryBuilder为构建生成SQL语句而提供的Fluent Api的代码实现。这一篇文章咱们来学习Laravel Database里另一个重要的部分: Eloquent Model。git
Eloquent Model把数据表的属性、关联关系等抽象到了每一个Model类中,因此Model类是对数据表的抽象,而Model对象则是对表中单条记录的抽象。Eloquent Model以上文讲到的QueryBuilder为基础提供了Eloquent Builder与数据库进行交互,此外还提供了模型关联优雅的解决了多个数据表之间的关联关系。github
Eloquent Builder是在上文说到的Query Builder的基础上实现的,咱们仍是经过具体的例子来看,上文用到的:数据库
DB::table('user')->where('name', 'James')->where('age', 27)->get();
把它改写为使用Model的方式后就变成了数组
User::where('name', 'James')->where('age', 27)->get();
在Model类文件里咱们并无找到where
、find
、first
这些经常使用的查询方法,咱们都知道当调用一个不存在的类方法时PHP会触发魔术方法__callStatic
, 调用不存在的实例方法会触发__call
, 很容易就猜到上面这些方法就是经过这两个魔术方法来动态调用的,下面让咱们看一下源码。app
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()); //设置builder的Model实例,这样在构建和执行query时就能使用model中的信息了 return $builder->setModel($this) ->with($this->with) ->withCount($this->withCount); } //建立数据库链接的QueryBuilder protected function newBaseQueryBuilder() { $connection = $this->getConnection(); return new QueryBuilder( $connection, $connection->getQueryGrammar(), $connection->getPostProcessor() ); } }
经过上面的那些代码咱们能够看到对Model调用的这些查询相关的方法最后都会经过__call
转而去调用Eloquent Builder实例的这些方法,咱们看到实例化的时候把数据库链接的QueryBuilder对象传给了Eloquent Builder的构造方法, 咱们去看一下Eloquent Builder的源码。学习
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 = ['*']) { $builder = $this->applyScopes(); //若是获取到了model还会load要预加载的模型关联,避免运行n+1次查询 if (count($models = $builder->getModels($columns)) > 0) { $models = $builder->eagerLoadRelations($models); } return $builder->getModel()->newCollection($models); } public function getModels($columns = ['*']) { return $this->model->hydrate( $this->query->get($columns)->all() )->all(); } //将查询出来的结果转换成Model对象组成的Collection public function hydrate(array $items) { //新建一个model实例 $instance = $this->newModelInstance(); return $instance->newCollection(array_map(function ($item) use ($instance) { return $instance->newFromBuilder($item); }, $items)); } //first 方法就是应用limit 1,get返回的集合后用Arr::first()从集合中取出model对象 public function first($columns = ['*']) { return $this->take(1)->get($columns)->first(); } } //newModelInstance newFromBuilder 定义在\Illuminate\Database\EloquentModel类文件里 public function newFromBuilder($attributes = [], $connection = null) { //新建实例,而且把它的exists属性设成true, save时会根据这个属性判断是insert仍是update $model = $this->newInstance([], true); $model->setRawAttributes((array) $attributes, true); $model->setConnection($connection ?: $this->getConnectionName()); $model->fireModelEvent('retrieved', false); return $model; }
代码里Eloquent Builder的where方法在接到调用请求后直接把请求转给来QueryBuilder的where方法,而后get方法也是先经过QueryBuilder的get方法执行查询拿到结果数组后再经过newFromBuilder
方法把结果数组转换成Model对象构成的集合,而另一个比较经常使用的方法first
也是在get
方法的基础上实现的,对query应用limit 1,再从get
方法返回的集合中用 Arr::first()
取出model对象返回给调用者。ui
看完了Model查询的实现咱们再来看一下update、create和delete的实现,仍是从一开始的查询例子继续扩展:this
$user = User::where('name', 'James')->where('age', 27)->first();
如今经过Model查询咱们获取里一个User Model的实例,咱们如今要把这个用户的age改为28岁:spa
$user->age = 28; $user->save();
咱们知道model的属性对应的是数据表的字段,在上面get方法返回Model实例集合时咱们看到过把数据记录的字段和字段值都赋值给了Model实例的$attributes属性, Model实例访问和设置这些字段对应的属性时是经过__get
和__set
魔术方法动态获取和设置这些属性值的。code
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; } //若是attributes数组的index里有$key或者$key对应一个属性访问器`'get' . $key` 则从这里取出$key对应的值 //不然就尝试去获取模型关联的值 if (array_key_exists($key, $this->attributes) || $this->hasGetMutator($key)) { return $this->getAttributeValue($key); } if (method_exists(self::class, $key)) { return; } //获取模型关联的值 return $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 ($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; } }
因此当执行$user->age = 28
时, User Model实例里$attributes属性会变成
protected $attributes = [ ... 'age' => 28, ... ]
设置好属性新的值以后执行Eloquent Model的save
方法就会更新数据库里对应的记录,下面咱们看看save
方法里的逻辑:
abstract class Model implements ... { public function save(array $options = []) { $query = $this->newQueryWithoutScopes(); if ($this->fireModelEvent('saving') === false) { return false; } //查询出来的Model实例的exists属性都是true 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; } //判断对字段是否有更改 public function isDirty($attributes = null) { return $this->hasChanges( $this->getDirty(), is_array($attributes) ? $attributes : func_get_args() ); } //数据表字段会保存在$attributes和$original两个属性里,update前要找出被更改的字段 public function 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; } //为查询设置where primary key = xxx protected function setKeysForSaveQuery(Builder $query) { $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); return $query; } }
在save里会根据Model实例的exists
属性来判断是执行update仍是insert, 这里咱们用的这个例子是update,在update时程序找出$attributes
和$original
两个数组属性的差集(获取Model对象时会把数据表字段会保存在$attributes
和$original
两个属性),若是没有更改那么update到这里就结束了,有更改那么就继续去执行performUpdate
方法,performUpdate
方法会执行Eloquent Builder的update方法, 而Eloquent Builder依赖的仍是数据库链接的Query Builder实例去最后执行的数据库update。
刚才说经过Eloquent Model获取模型时(在newFromBuilder
方法里)会把Model实例的exists
属性设置为true,那么对于新建的Model实例这个属性的值是false,在执行save
方法时就会去执行performInsert
方法
protected function performInsert(Builder $query) { if ($this->fireModelEvent('creating') === false) { return false; } //设置created_at和updated_at属性 if ($this->usesTimestamps()) { $this->updateTimestamps(); } $attributes = $this->attributes; //若是表的主键自增insert数据并把新记录的id设置到属性里 if ($this->getIncrementing()) { $this->insertAndSetId($query, $attributes); } //不然直接简单的insert else { if (empty($attributes)) { return true; } $query->insert($attributes); } // 把exists设置成true, 下次在save就会去执行update了 $this->exists = true; $this->wasRecentlyCreated = true; //触发created事件 $this->fireModelEvent('created', false); return true; }
performInsert
里若是表是主键自增的,那么在insert后会设置新记录主键ID的值到Model实例的属性里,同时还会帮咱们维护时间字段和exists
属性。
Eloquent Model的delete操做也是同样, 经过Eloquent Builder去执行数据库链接的QueryBuilder里的delete方法删除数据库记录:
//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) ) ); }
Query Builder的实现细节咱们在上一篇文章里已经说过了这里再也不赘述,若是好奇Query Builder是怎么执行SQL操做的能够回去翻看上一篇文章。
本文咱们详细地看了Eloquent Model是怎么执行CRUD的,就像开头说的Eloquent Model经过Eloquent Builder来完成数据库操做,而Eloquent Builder是在Query Builder的基础上作了进一步封装, Eloquent Builder会方法调用转给Query Builder里对应的方法来完成操做,因此在Query Builder里能使用的方法到Eloquent Model中一样都能使用。
除了对数据表、基本的CRUD的抽象外,模型另外的一个重要的特色是模型关联,它帮助咱们优雅的解决了数据表间的关联关系。咱们在以后的文章再来详细看模型关联部分的实现。
本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。