Laravel系列4.4模型Eloquent ORM的使用

Posted 码农老张Zy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于Laravel系列4.4模型Eloquent ORM的使用的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别