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

文章目录
    分类:PHP 标签:Laravel,PHP框架

    模型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

    视频链接

    B站视频地址:https://www.bilibili.com/video/BV19A4y1d7hj

    微信视频地址:http://mp.weixin.qq.com/s?__biz=MzIxODQyNTU1MA==&mid=2247486996&idx=1&sn=3d960563721f600ba11459781f8d4b68&chksm=97ebffb5a09c76a339bf6365dbc82d18c084c0a27de19e2aeaedbfd3e24378ac3da64c803ca0&scene=27#wechat_redirect

    微信文章地址:http://mp.weixin.qq.com/s?__biz=MzIxODQyNTU1MA==&mid=2247486596&idx=1&sn=674e7e9047d05fe10dfdacd1c502c50a&chksm=97ebfd25a09c74334055dbaa9a78c8617a797c51bb727c7f43b4cc9e110e46a8a9891f09ede5&scene=27#wechat_redirect

    搜索
    关注