【Laravel系列4.4】模型Eloquent ORM的使用(二)

2023-03-03 13:15:54 浏览数 (3)

模型Eloquent ORM的使用(二)

对于模型的探索我们还将继续。上篇文章中,只是简单地通过模型操作了一下数据库,并且学习了一下关联操作的知识。今天,我们继续学习模型中别的一些好玩的东西,不过,我们不会继续深入地学习模型中别的相关技巧。因为这些东西,都已经写在了官方文档中,而对于这个系列的文章来说,入个门,然后搞清楚原理才是最重要的,对于怎么使用这个事,大家自己好好研究就好了。而且,关于使用的内容,网上也有很多文章以及视频教程了,我也就不走别人的老路咯。

集合操作

其实这个集合操作并不是模型特有的,还记得在 查询构造器 中,我们查询列表的时候,总会在最后加一个 toArray() 吗?这个 toArray() 并不是 Builder 中的方法,如果不加这个 toArray() ,返回的是什么大家有没有注意过?

代码语言:javascript复制
Route::get('model/test/collection', function () {
    $where = [];
    if(request()->name){
        $where[] = ['name', 'like', '%' . request()->name . '%'];
    }
    if(request()->sex){
        $where[] = ['sex', '=', request()->sex];
    }

    $list = AppModelsMTest::where($where)
        ->orderBy('id', 'desc')
        ->limit(10)
        ->offset(0)
        ->get();

    dd($list);

// IlluminateDatabaseEloquentCollection Object
// (
//     [items:protected] => Array
//         (
//             [0] => AppModelsMTest 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() 的函数只获取数据中的两个字段组成键值对形式的数据。

代码语言:javascript复制
$list = AppModelsMTest::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 = AppModelsMTest::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 ,也就是会和我们指定的模型类关联上,获得的结果都是一个 AppModelsMTest Object 对象。而我们在日常的操作中,其实最习惯的是使用数组那种形式的操作,除开我们后面会讲的直接从配置入手来修改 PDO FETCH 属性之外,我们还可以用上面这个 map() 函数配合模型对象的 attributesToArray() 方法来将模型对象转换成数组格式。

当然,这个集合类相关的操作函数还有很多,这里我们只是演示了两个,具体的内容大家自行查阅一下官方手册。而源码呢?我也只给出具体的文件,大家自己去看看,里面的数组各种操作功能都非常经典。laravel/framework/src/Illuminate/Collections/Collection.php 是集合类,里面的方法大部分都调用的是 laravel/framework/src/Illuminate/Collections/Arr.php 里面的方法。

与路由绑定

对于一些获取单个信息的操作来说,模型是可以直接绑定到路由上的,比如下面这样:

代码语言:javascript复制
Route::get('model/test/bindroute/{mTest}', function(AppModelsMTest $mTest){
    dump($mTest);
    dump($mTest->name);
});

通过在回调函数中注入模型对象,就可以实现路由与模型的绑定。这里路由的 mTest 参数实际上就是我们查询数据的主键 ID ,然后模型就会自动为我们查询相应的数据并注入到 $mTest 参数中。除了直接绑定路由外,通过控制器实现也是一样的,我们只需要将回调函数变成指定的控制器方法即可。

代码语言:javascript复制
Route::get('model/test/bindroute/controller/{mTest}', [AppHttpControllersMTestController::class, 'show']);

class MTestController extends Controller
{
    public function show(MTest $mTest){
        dump($mTest);
        dump($mTest->name);
    }
}

快速序列化

对于模型的序列化来说,有两种形式的序列化,一是序列化为数组,二是序列化为 JSON 格式字符串。我们先看看第一种。

代码语言:javascript复制
Route::get('model/test/ser/array', function(){
    $mTest = AppModelsMTest::find(1);
    dump($mTest->toArray());
    dump($mTest->attributesToArray());
});

这个其实没有什么多说的,因为 toArray() 和 attributesToArray() 都是我们之前用过的,但是要注意的是,它们两个是不同的概念。toArray() 方法是一个递归方法,它会将所有的属性和关联(包括关联的关联)都转化成数组。而 attributesToArray() 只会将当前模型的属性转化为数组。

对于 JSON 格式,其实也只是调用一个 toJson() 方法就可以方便地实现。

代码语言:javascript复制
Route::get('model/test/ser/json', function(){
    $mTest = AppModelsMTest::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() 方法。

代码语言:javascript复制
protected function newBaseQueryBuilder()
{
    return $this->getConnection()->query();
}

这个似乎就已经非常明显了。getConnection() 会返回一个之前讲过的工厂方法创建的 Connection 对象,而 query() 方法则会根据 Connection 创建一个 QueryBuilder 对象。剩下的还需要我们细讲吗?我觉得到这里真的已经非常清晰了。

然后我们来看一下这个 Model 基类中的其它方法,貌似没有发现 get() 、find() 之类的方法呀?这是怎么回事。别急,get() 、find() 不都是在 查询构造器 中的方法嘛。我们来看看 Model 中的 __call() 这个方法。

代码语言:javascript复制
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 对象。但 EloquentBuilder 的内部持有的一个query 属性依然是 QueryBuilder 对象,也就是说在底层,它依然是调用的我们熟悉的那个 查询构造器 来进行工作的。但是,这里划重点了,EloquentBuilder 中有些方法是没有的,比如说 insert()、insertGetId() ,在模型中,使用 save() 就可以代替这两个方法的操作。说白了,直接 mTest->insert() 是会报错的,不过也有方法解决,只不过那样就完全像是使用一个 查询构造器 了,大家自己找找解决方案哦。

关于模型的内容还有很多,在这里我们就不一一讲解了。相关的源码也都在上面的源码文件路径中都给出了,其它有意思功能的源码大家可以自己尝试去分析一下,毕竟我们也学习了一段时间了,相信很多东西大家自己也能找到了。最主要的还是那句话,看框架真的就是在考验你的基础水平,找不到方法了怎么办?找 __call() 或者 __callStatic() ;找不到属性了怎么办?找 __set()、__get() ;来回调用看着好晕怎么办?Debug工具与编辑器的配置一定要配好,设计模式一定要理解透。相信有了这些,后面的内容你也可以写出来了,期待大家的分享哦!

参考文档:

https://learnku.com/docs/laravel/8.x/eloquent/9406

0 人点赞