思考并回答以下问题:
获取模型
get函数
1 | public function get($columns = ['*']) |
get 函数会将QueryBuilder所获取的数据进一步包装hydrate。hydrate函数会将数据库取回来的数据打包成数据库模型对象EloquentModel,如果可以获取到数据,还会利用函数eagerLoadRelations来预加载关系模型。
1 | public function hydrate(array $items) |
newModelInstance函数创建了一个新的数据库模型对象,重要的是这个函数为新的数据库模型对象赋予了connection:
1 | public function newModelInstance($attributes = []) |
newFromBuilder函数会将所有数据库数据存入另一个新的Eloquent Model的attributes中:
1 | public function newFromBuilder($attributes = [], $connection = null) |
newInstance函数专用于创建新的数据库对象模型:
1 | public function newInstance($attributes = [], $exists = false) |
值得注意的是newInstance将exist设置为true,意味着当前这个数据库模型对象是从数据库中获取而来,并非是手动新建的,这个exist为真,我们才能对这个数据库对象进行update。
setRawAttributes函数为新的数据库对象赋予属性值,并且进行sync,标志着对象的原始状态:
1 | public function setRawAttributes(array $attributes, $sync = false) |
这个原始状态的记录十分重要,原因是save函数就是利用原始值original与属性值attributes 的差异来决定更新的字段。
find函数
find函数用于利用主键id来查询数据,find函数也可以传入数组,查询多个数据
1 | public function find($id, $columns = ['*']) |
findOrFail
laravel 还提供 findOrFail 函数,一般用于 controller,在未找到记录的时候会抛出异常。
1 | public function findOrFail($id, $columns = ['*']) |
其他查询与数据获取方法
所用 Query Builder支持的查询方法,例如select、selectSub、whereDate、whereBetween等等,都可以直接对Eloquent Model直接使用,程序会通过魔术方法调用Query Builder的相关方法:
1 | protected $passthru = [ |
passthru 中的各个函数在调用前需要加载查询作用域,原因是这些操作基本上是aggregate的,需要添加搜索条件才能更加符合预期:
1 | public function toBase() |
save函数不会加载全局作用域,原因是凡是利用save函数进行的插入或者更新的操作都不会存在where条件,仅仅利用自身的主键属性来进行更新。如果需要where条件可以使用query\builder的update函数,我们在下面会详细介绍:
1 | public function newQueryWithoutScopes() |
newQueryWithoutScopes函数创建新的没有任何其他条件的Eloquent\builder类,而Eloquent\builder 类需要 Query\builder 类作为底层查询构造器。
performUpdate函数
如果当前的数据库模型对象是从数据库中取出的,也就是直接或间接的调用get()函数从数据库中获取到的数据库对象,那么其 exists 必然是 true
1 | public function isDirty($attributes = null) |
getDirty函数可以获取所有与原始值不同的属性值,也就是需要更新的数据库字段。关键函数在于originalIsEquivalent:
1 | protected function originalIsEquivalent($key, $current) |
可以看到,对于数据库可以转化的属性都要先进行转化,然后再开始对比。比较出的结果,就是我们需要 update 的字段。
执行更新的时候,除了 getDirty 函数获得的待更新字段,还会有 UPDATED_AT 这个字段:
1 | protected function performUpdate(Builder $query) |
执行更新的时候,where 条件只有一个,那就是主键 id:
1 | protected function setKeysForSaveQuery(Builder $query) |
最后会调用 EloquentBuilder 的 update 函数:
1 | public function update(array $values) |
performInsert
关于数据库对象的插入,如果数据库的主键被设置为increment,也就是自增的话,程序会调用 insertAndSetId,这个时候不需要给数据库模型对象手动赋值主键id。若果数据库的主键并不支持自增,那么就需要在插入前,为数据库对象的主键 id 赋值,否则数据库会报错。
1 | protected function performInsert(Builder $query) |
laravel 默认数据库的主键支持自增属性,程序调用的也是函数 insertAndSetId 函数:
1 | protected function insertAndSetId(Builder $query, $attributes) |
插入后,会将插入后得到的主键 id 返回,并赋值到模型的属性当中。
如果数据库主键不支持自增,那么我们在数据库类中要设置:
1 | public $incrementing = false; |
每次进行插入数据的时候,需要手动给主键赋值。
update 函数
save 函数仅仅支持手动的属性赋值,无法批量赋值。laravel 的 Eloquent Model 还有一个函数: update 支持批量属性赋值。有意思的是,Eloquent Builder 也有函数 update,那个是上一小节提到的 performUpdate 所调用的函数。
两个 update 功能一致,只是 Model 的 update 函数比较适用于更新从数据库取回的数据库对象:
1 | $flight = App\Flight::find(1); |
而 Builder 的 update 适用于多查询条件下的更新:
1 | App\Flight::where('active', 1) |
make 函数
同样的,save 的插入也仅仅支持手动属性赋值,如果想实现批量属性赋值的插入可以使用 make 函数:
1 | $model = App\Flight::make(['name' => 'New Flight Name','desc' => 'test']); |
make 函数实际上仅仅是新建了一个 Eloquent Model,并批量赋予属性值:
1 | public function make(array $attributes = []) |
create 函数
如果想要一步到位,批量赋值属性与插入一起操作,可以使用 create 函数:
1 | App\Flight::create(['name' => 'New Flight Name','desc' => 'test']); |
相比较 make 函数,create 函数更进一步调用了 save 函数:
1 | public function create(array $attributes = []) |
实际上,属性值是否可以批量赋值需要受 fillable 或 guarded 来控制,如果我们想要强制批量赋值可以使用 forceCreate:
1 | public function forceCreate(array $attributes) |
findOrNew 函数
laravel 提供一种主键查询或者新建数据库对象的函数:findOrNew:
1 | public function findOrNew($id, $columns = ['*']) |
值得注意的是,当查询失败的时候,会返回一个全新的数据库对象,不含有任何 attributes。
firstOrNew 函数
laravel 提供一种自定义查询或者新建数据库对象的函数:firstOrNew:
1 | public function firstOrNew(array $attributes, array $values = []) |
值得注意的是,如果查询失败,会返回一个含有 attributes 和 values 两者合并的属性的数据库对象。
firstOrCreate 函数
类似于 firstOrNew 函数,firstOrCreate 函数也用于自定义查询或者新建数据库对象,不同的是,firstOrCreate 函数还进一步对数据进行了插入操作:
1 | public function firstOrCreate(array $attributes, array $values = []) |
updateOrCreate 函数
在 firstOrCreate 函数基础上,除了对数据进行查询,还会对查询成功的数据利用 value 进行更新:
1 | public function updateOrCreate(array $attributes, array $values = []) |
firstOr 函数
如果想要自定义查找失败后的操作,可以使用 firstOr 函数,该函数可以传入闭包函数,处理找不到数据的情况:
1 | public function firstOr($columns = ['*'], Closure $callback = null) |
删除模型
删除模型也会分为两种,一种是针对 Eloquent Model 的删除,这种删除必须是从数据库中取出的对象。还有一种是 Eloquent Builder 的删除,这种删除一般会带有多个查询条件。我们这一小节主要讲 model 的删除:
1 | public function delete() |
删除模型时,模型对象必然要有主键。performDeleteOnModel 函数执行具体的删除操作:
1 | protected function performDeleteOnModel() |
所以实际上,Model 调用的也是 builder 的 delete 函数。
软删除
如果想要使用软删除,需要使用 Illuminate\Database\Eloquent\SoftDeletes 这个 trait。并且需要定义软删除字段,默认为 deleted_at,将软删除字段放入 dates 中,具体用法可参考官方文档: 软删除
1 | class Flight extends Model |
我们先看看这个 trait:
1 | trait SoftDeletes |
如果使用了软删除,在 model 的启动过程中,就会启动软删除的这个函数。可以看出来,软删除是需要查询作用域来合作发挥作用的。我们看看这个 SoftDeletingScope :
1 | class SoftDeletingScope implements Scope |
apply 函数是加载全局域调用的函数,每次进行查询的时候,调用 get 函数就会自动加载这个函数,whereNull 这个查询条件会被加载到具体的 where 条件中。deleted_at 字段一般被设置为 null,在执行软删除的时候,该字段会被赋予时间格式的值,标志着被删除的时间。
在加载全局作用域的时候,还会调用 extend 函数,extend 函数为 model 添加了四个函数:
WithTrashed
1 | protected function addWithTrashed(Builder $builder) |
withTrashed 函数取消了软删除的全局作用域,这样我们查询数据的时候就会查询到正常数据和被软删除的数据。
withoutTrashed
1 | protected function addWithoutTrashed(Builder $builder) |
withTrashed 函数着重强调了不要获取软删除的数据。
onlyTrashed
1 | protected function addOnlyTrashed(Builder $builder) |
如果只想获取被软删除的数据,可以使用这个函数 onlyTrashed,可以看到,它使用了 whereNotNull。
restore
1 | protected function addRestore(Builder $builder) |
如果想要恢复被删除的数据,还可以使用 restore,重新将 deleted_at 数据恢复为 null。
performDeleteOnModel
SoftDeletes 这个 trait 会重载 performDeleteOnModel 函数,它将不会调用 Eloquent Builder 的 delete 方法,而是采用更新操作:
1 | protected function performDeleteOnModel() |
删除操作不仅更新了 deleted_at,还更新了 updated_at 字段。