标题:【Laravel系列4.4】模型Eloquent ORM的使用(二)
模型Eloquent ORM的使用(二)
对于模型的探索我们还将继续。上篇文章中,只是简单地通过模型操作了一下数据库,并且学习了一下关联操作的知识。今天,我们继续学习模型中别的一些好玩的东西,不过,我们不会继续深入地学习模型中别的相关技巧。因为这些东西,都已经写在了官方文档中,而对于这个系列的文章来说,入个门,然后搞清楚原理才是最重要的,对于怎么使用这个事,大家自己好好研究就好了。而且,关于使用的内容,网上也有很多文章以及视频教程了,我也就不走别人的老路咯。
集合操作
其实这个集合操作并不是模型特有的,还记得在 查询构造器 中,我们查询列表的时候,总会在最后加一个 toArray() 吗?这个 toArray() 并不是 Builder 中的方法,如果不加这个 toArray() ,返回的是什么大家有没有注意过?
Route::get('model/test/collection', function () {
$where = [];
if(request()->name){
$where[] = ['name', 'like', '%' . request()->name . '%'];
}
if(request()->sex){
$where[] = ['sex', '=', request()->sex];
}
$list = \App\Models\MTest::where($where)
->orderBy('id', 'desc')
->limit(10)
->offset(0)
->get();
dd($list);
// Illuminate\Database\Eloquent\Collection Object
// (
// [items:protected] => Array
// (
// [0] => App\Models\MTest Object
// (
// [table:protected] => m_test
// [timestamps] =>
// [connection:protected] => mysql
// [primaryKey:protected] => id
// [keyType:protected] => int
// [incrementing] => 1
// [with:protected] => Array
// (
// )
// [withCount:protected] => Array
// (
// )
// [perPage:protected] => 15
// [exists] => 1
// [wasRecentlyCreated] =>
// [attributes:protected] => Array
// (
// [id] => 20
// [name] => Jim
// [sex] => 1
// )
// [original:protected] => Array
// (
// [id] => 20
// [name] => Jim
// [sex] => 1
// )
// ………………
// ………………
// ………………
});
打印出来,我们会发现,它返回的是一个 laravel/framework/src/Illuminate/Database/Eloquent/Collection.php 对象,然后这个对象里面有个 items 属性,是一个数组。这个对象就是我们的模型组件中的集合对象,它包含很多集合操作的方法,如果以最简单的角度理解的话,其实它就是帮我们封装了很多数组操作函数。
这个集合对象有什么作用呢?其实很明显了,它提供了各种数组操作函数,就是有很多数组操作我们可以以对象的形式提供。比如说我们可以使用类似于 array_map() 的函数把集合中的对象全部转换成数组,还可以用一个类似于 array_column() 的函数只获取数据中的两个字段组成键值对形式的数据。
$list = \App\Models\MTest::where($where)
->orderBy('id', 'desc')
->limit(10)
->offset(0)
->get()
->pluck('name', 'id')
->toArray();
print_r($list);
// Array
// (
// [20] => Jim
// [19] => Mary
// [18] => Susan
// [17] => Tom
// [16] => Peter
// [15] => Jim
// [14] => Mary
// [13] => Susan
// [12] => Tom
// [11] => Peter
// )
$list = \App\Models\MTest::where($where)
->orderBy('id', 'desc')
->limit(10)
->offset(0)
->get()
->map(function($item){ return $item->attributesToArray();})
->toArray();
print_r($list);
// Array
// (
// [0] => Array
// (
// [id] => 20
// [name] => Jim
// [sex] => 1
// )
// [1] => Array
// (
// [id] => 19
// [name] => Mary
// [sex] => 2
// )
// ………………
// ………………
// ………………
// )
上面的 plucks() 就是类似于 array_column() 的函数操作,用于获取数组元素指定的列值,这样生成的列表对于一些下拉框的接口非常友好。而另外一个 map() 函数就不用多说了,之前我们说过,Laravel 的 PDO 在默认查询构造器的情况下,走的是 PDO::FETCH_OBJ ,获得的集合结果中的每个数据都是一个 stdClass 对象,而在 Model 下,走的则是 PDO::FETCH_CLASS ,也就是会和我们指定的模型类关联上,获得的结果都是一个 App\Models\MTest Object 对象。而我们在日常的操作中,其实最习惯的是使用数组那种形式的操作,除开我们后面会讲的直接从配置入手来修改 PDO FETCH 属性之外,我们还可以用上面这个 map() 函数配合模型对象的 attributesToArray() 方法来将模型对象转换成数组格式。
当然,这个集合类相关的操作函数还有很多,这里我们只是演示了两个,具体的内容大家自行查阅一下官方手册。而源码呢?我也只给出具体的文件,大家自己去看看,里面的数组各种操作功能都非常经典。laravel/framework/src/Illuminate/Collections/Collection.php 是集合类,里面的方法大部分都调用的是 laravel/framework/src/Illuminate/Collections/Arr.php 里面的方法。
与路由绑定
对于一些获取单个信息的操作来说,模型是可以直接绑定到路由上的,比如下面这样:
Route::get('model/test/bindroute/{mTest}', function(\App\Models\MTest $mTest){
dump($mTest);
dump($mTest->name);
});
通过在回调函数中注入模型对象,就可以实现路由与模型的绑定。这里路由的 mTest 参数实际上就是我们查询数据的主键 ID ,然后模型就会自动为我们查询相应的数据并注入到 $mTest 参数中。除了直接绑定路由外,通过控制器实现也是一样的,我们只需要将回调函数变成指定的控制器方法即可。
Route::get('model/test/bindroute/controller/{mTest}', [\App\Http\Controllers\MTestController::class, 'show']);
class MTestController extends Controller
{
public function show(MTest $mTest){
dump($mTest);
dump($mTest->name);
}
}
快速序列化
对于模型的序列化来说,有两种形式的序列化,一是序列化为数组,二是序列化为 JSON 格式字符串。我们先看看第一种。
Route::get('model/test/ser/array', function(){
$mTest = \App\Models\MTest::find(1);
dump($mTest->toArray());
dump($mTest->attributesToArray());
});
这个其实没有什么多说的,因为 toArray() 和 attributesToArray() 都是我们之前用过的,但是要注意的是,它们两个是不同的概念。toArray() 方法是一个递归方法,它会将所有的属性和关联(包括关联的关联)都转化成数组。而 attributesToArray() 只会将当前模型的属性转化为数组。
对于 JSON 格式,其实也只是调用一个 toJson() 方法就可以方便地实现。
Route::get('model/test/ser/json', function(){
$mTest = \App\Models\MTest::find(1);
dump($mTest->toJson());
dump($mTest->toJson(JSON_PRETTY_PRINT));
});
toJson() 所接收到的参数就是我们日常可以使用的 JSON 系列常量。这个没有什么多说的,大家可以自己尝试一下。
模型调用的是查询构造器?
之前我们就一直在强调,原生查询 操作封装成 查询构造器 ,然后 查询构造器 进一步面向对象化的封装变成了 ORM 类型的 模型 。这是一个连续递进的关系,之前在 查询构造器 的文章中,我们已经看到了它的底层就是调用的 原生查询 操作。那么这回,我们再来看一下 Model 中的方法,在底层是不是调用的是 查询构造器 。
在所有模型都要继承的 laravel/framework/src/Illuminate/Database/Eloquent/Model.php 类中,我们很快就能发现一个 query() 静态方法。一路向下追踪,你马上就会发现它最后会调用到一个 newBaseQueryBuilder() 方法。
protected function newBaseQueryBuilder()
{
return $this->getConnection()->query();
}
这个似乎就已经非常明显了。getConnection() 会返回一个之前讲过的工厂方法创建的 Connection 对象,而 query() 方法则会根据 Connection 创建一个 QueryBuilder 对象。剩下的还需要我们细讲吗?我觉得到这里真的已经非常清晰了。
然后我们来看一下这个 Model 基类中的其它方法,貌似没有发现 get() 、find() 之类的方法呀?这是怎么回事。别急,get() 、find() 不都是在 查询构造器 中的方法嘛。我们来看看 Model 中的 __call() 这个方法。
public function __call($method, $parameters)
{
if (in_array($method, ['increment', 'decrement'])) {
return $this->$method(...$parameters);
}
if ($resolver = (static::$relationResolvers[get_class($this)][$method] ?? null)) {
return $resolver($this);
}
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
当前类中找不到的方法就会进入 __call() 魔术方法中,在这里,我们看到它调用了 forwardCallTo() 方法,然后传递进去的是一个新的 查询构造器 对象和方法名以及参数。不过这里需要注意的是,模型默认生成的 QueryBuilder 是 llaravel/framework/src/Illuminate/Database/Eloquent/Builder.php 对象,而不是我们之前 查询构造器 中的 laravel/framework/src/Illuminate/Database/Query/Builder.php 对象。但 Eloquent\Builder 的内部持有的一个 \$query 属性依然是 Query\Builder 对象,也就是说在底层,它依然是调用的我们熟悉的那个 查询构造器 来进行工作的。但是,这里划重点了,Eloquent\Builder 中有些方法是没有的,比如说 insert()、insertGetId() ,在模型中,使用 save() 就可以代替这两个方法的操作。说白了,直接 $mTest->insert() 是会报错的,不过也有方法解决,只不过那样就完全像是使用一个 查询构造器 了,大家自己找找解决方案哦。
总结
关于模型的内容还有很多,在这里我们就不一一讲解了。相关的源码也都在上面的源码文件路径中都给出了,其它有意思功能的源码大家可以自己尝试去分析一下,毕竟我们也学习了一段时间了,相信很多东西大家自己也能找到了。最主要的还是那句话,看框架真的就是在考验你的基础水平,找不到方法了怎么办?找 __call() 或者 __callStatic() ;找不到属性了怎么办?找 __set()、__get() ;来回调用看着好晕怎么办?Debug工具与编辑器的配置一定要配好,设计模式一定要理解透。相信有了这些,后面的内容你也可以写出来了,期待大家的分享哦!
参考文档:
https://learnku.com/docs/laravel/8.x/eloquent/9406