1、更高效率地查询:使用批量查询代替 foreach 查询(多次 io 操作转换为一次 io操作)

如果想要查看更详尽的介绍,可以看看这篇文章 什么是 N+1 问题,以及如何解决 Laravel 的 N+1 问题?

在维护的项目中, 我发现了有不少需要查询关联数据的时候是这样做的:先查询出列表,然后 foreach 列表去查询列表每一条记录的关联数据,好比如这样:

$products = \DB::table('product')->where('category_id', 1)->paginate();
foreach ($products as $key => $product) {$product['some_other_info'] = \DB::table('some_other_info')->where('product_id', $product['id'])->get();$products[$key] = $product;
}

  解决方法:使用一次查询代替多次查询

    a. 定义模型关联,使用 Model 的 with 进行查询。如:

AdminUser::with('admin_user_info')->where('id', '>', 1)->get();

    b. 获取查询结果集的 id 列(根据实际情况而定),使用 whereIn 一次查询关联数据。对于那些不能定义关联的才用这种方法,因为这种写法有点啰嗦

$products = \DB::table('product')->where('category_id', 1)->paginate();
$product_ids = $products->pluck('id')->toArray(); // 获取所有 id , 下面第二点有说到这个用法
$some_other_infos = \DB::table('some_other_info') // 根据 id 数组一次查询所有关联数据->whereIn('product_id', $product_ids)->get();
$some_other_infos = array_column($some_other_infos, null, 'product_id'); // 使用 id 把结果集转换为关联数组,这样下面可以更高效地操作,否则我们只能两次 foreach 了
foreach ($products as $key => $product) {$product['some_other_info'] = array_get($some_other_infos, $product['id']);$products[$key] = $product;
}

  先说说 关联查询:我们在 Model 类里定义的关联,我们不一定在第一次查询就全部查出来,我们可以在需要的时候再去查询 ,使用 load 方法,可以实现这个目标,但是这个 load 只是针对单个 model 对象的,如果我们 Model::xxx()->xx() 链式操作最后是 get(),我们得到的结果将会是一个 Collection 实例,最后调用的方法是 first() 或其他类似的 firstOrFail() 的时候,返回的才是一个 Model 实例。也就是说 load 方法只针对 Model 实例。如下:

我们假设有如下模型定义:

AdminUser

<?phpnamespace App\Model;class AdminUser extends BaseModel
{public function admin_user_info(){return $this->hasOne(AdminUserInfo::class);}
}

  

AdminUserInfo

<?phpnamespace App\Model;class AdminUserInfo{}

$admin_users = \App\Model\AdminUser::where('id', '>', 1)->get();
$admin_users->load('admin_user_info'); // 错误, 因为 $admin_users 是一个 Collection 实例

$admin_user->load('admin_user_info'); // 可以 load 的,返回的是 EloquentCollection
$admin_user = \App\Model\AdminUser::first();
$admin_user->load('admin_user_info'); // 正确, $admin_user 是一个 Model 实例

  

  好像有点跑题了,而我们使用 with 的话,效果相当于一次性给结果集 load 完了所有的关联。如果我们有使用 laravel-debugbar 来,就会发现一对多关联使用 with 时候执行的 sql 都是使用了 whereIn 进行查询的。

  

2、更优雅地处理你的 paginate() 结果(利用 Collection 里的方法)

  这里应该有一小点,我们想要实现某些功能的时候可以先看看框架有没有提供相关的支持,如命名在驼峰式、下划线式转换;字符串是否以某些子串开始、结束;数组排序、过滤等等,其实很多常用的功能 laravel 都有现成的轮子,最常用的一些功能可以查看 Arr、Str、Collection 的源码,利用好的话可以把代码写得简洁一些。

  这个其实最近才发现的,最近脑洞开了一下,因为想从 paginate() 返回的对象中获取结果集的 id 列,所以就想能不能  $paginator->pluck('id')->toArray() 这样操作,最后发现真的可以,然后去看了源码发现 AbstractPaginator 这个抽象类里有一个 __call 的方法,如下:

/**
* Make dynamic calls into the collection.
*
* @param  string  $method
* @param  array  $parameters
* @return mixed
*/
public function __call($method, $parameters)
{return $this->getCollection()->$method(...$parameters);
}

  我们会发现,这个魔术方法调用的是 Collection 里面的方法,也就是说,我们可以把 paginate() 返回的结果当作一个 Collection 实例来操作。所有的 Collection 方法我们都可以使用,什么 filter、map、sort 等等等等。而我之前获取 id 列都是使用 array_column($paginator->items(), 'id'),两种写法差不多,但是对于另外一些如根据某个字段排序等操作,利用 Collection 的 sort 方法明显就简洁多了。

  

3、利用 Macroable Trait 更优雅地对框架某些功能进行扩展

  我们想想,如果有一天,我们需要自定义一些 Query Builder 的方法的时候,我们会怎么做?当然我们可以利用 Global Scopes 这个特性来实现某些类似的功能,但是如果我们想要 DB 类也用上这个扩展的方法的话,这样就行不通了。也不是完全没有办法,laravel 的开发者很早开始就考虑到了扩展性的问题,我们去查看 Query Builder 的源码就会发现,里面有以下的 trait 调用:

use BuildsQueries, Macroable {__call as macroCall;
}

  而这个 Macroable 是 Illuminate\Support\Traits\Macroable,我们可以从这个 trait 中发现,其实这个 trait 提供了一个很强大的扩展功能,对于所有 use Macroable 的类,我们都可以通过 XXX::macro('xx', function (){}) 的方式来对这个类进行扩展。

  我们全局搜索这个 Macroable,会发现框架不少地方都有使用:如 Cache、Console、Eloquent、Schema 等等,如果我们想要对框架中某些类进行扩展的时候,不妨先看看这个类有没有 use Macroable, 如果有,我们就可以省去一大部分功夫。

  当然,我们利用 macro 方法来扩展的功能,IDE 不会有任何提示,除非,我们像 ide-helper 那样处理。具体可参照另外一篇文章:laravel query builder/ Eloquent builder 添加自定义方法,或者

4、利用 array_get、data_get  方法代替多个 isset 判断

  最常用到的地方是,我们利用 with 关联查询出多层关联,但是我们不确定所有层级关联是否存在,所以我们可能就要一层层地 isset 来判断。

  假设有一个这样的关联 User -> HasOne -> Article -> HasOne -> Comment,这个假设不太妥当,但可以说明问题

  我们查询的时候按如下方式查询:

// 获取一个用户下文章的一条评论
$user = User::with(['article.comment'])->first();
$content = isset($user->article) ? (isset($user->article->comment) ? $user->article->comment : '') : ''; // 传统写法
$content = data_get($user, 'article.comment.content'); // 评论内容

  上面的例子中,我们可以发现 data_get 写出的代码更简洁、可读性也更强,array_get 功能类似,但是 array_get 不能针对对象操作。利用 array_get 可以更方便完成地进行对多维数组的操作,因为 laravel 把多维数组抽象成了点号分隔的一维数组,不得不说,说 laravel 优雅不是没有道理的。

5、多对多关联更方便的操作方法 attach、detach、sync

// 附加多对多关系
$user->roles()->attach($roleId);
$user->roles()->attach($roleId, ['expires' => $expires]);
// 从用户上移除单一身份...
$user->roles()->detach($roleId);
// 从用户上移除所有身份...
$user->roles()->detach();
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);// 任何不在给定数组中的 IDs 将会从中介表中被删除。
$user->roles()->sync([1, 2, 3]);
// 你也可以传递中介表上该 IDs 额外的值:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);

  使用 sync 方法的时候需要注意,如果第二个参数不设置 false,会把不在给定数组中的 ID 的关联数据全部删除。

  上面的关联是 User -> belongsToMany -> Role,通过多对多表的关联,具体可查看另一篇文章:laravel5.1 使用中间表的多对多关联,我们通过下面的例子来说明一下 attach 的用法,其他几个方法也是类似的功能,

  我们查看以下数据库中间表记录:

  我们可以发现,user 关联了 id 为 1 和 2 的两个 role,同时 attach 第二个参数可以让我们传递一些额外数据保存到中间表。

  sync 的功能和 attach 类似,但是 sync 还有个功能是,可以把不在指定数组的关联数据删除(如果我们不需要删除可以传递第二个参数 false,或者直接使用 attach)。

6、使用 debugbar 尽早发现性能问题

  这个其实使用 laravel 的人基本上都知道,但是可能我们没有用到其中一些功能,其实 laravel-debugbar 可以通过配置获取更加详情的信息(如 sql 的堆栈信息、是否 explain 等等非常多实用的功能), 具体功能还是得查看其官方文档,有很多实用的功能,可以让我们对我们的代码了解更多。

7、使用 chunk 处理表全部数据

  有时候,我们给表新增了一个字段,这个字段是由其他字段算出来的,这时候我们就需要跑一遍该表,进行该字段的更新。一种做法是一次性全表查询出来,foreach 循环处理,这种做法在数据量小的时候问题不大,但是数据量达到一定程度的时候会产生比较严重的问题:内存耗尽,更严重的是把服务器也弄挂。另外一种方法是使用 Builder 的 chunk 方法进行批量处理,可以解决内存占用过大的问题。如下:

DB::table('users')->chunk(100, function($users)
{foreach ($users as $user){//}
});

  

8、关联数据分页、统计或者其他类似操作

  我们先来看看 Relation 的定义(5.6),我们可以发现里面有个 __call 方法,

/**
* Handle dynamic method calls to the relationship.
*
* @param  string  $method
* @param  array   $parameters
* @return mixed
*/
public function __call($method, $parameters)
{if (static::hasMacro($method)) {return $this->macroCall($method, $parameters);}$result = $this->query->{$method}(...$parameters);if ($result === $this->query) {return $this;}return $result;
}

  我们可以发现它会先看 有没有定义相关的 macro,如果没有则去调用 \Illuminate\Database\Eloquent\Builder 里面的方法,而我们继续看 \Illuminate\Database\Eloquent\Builder 里面的源码(5.6)发现,里面也有一个 __call 方法,

/**
* Dynamically handle calls into the query instance.
*
* @param  string  $method
* @param  array  $parameters
* @return mixed
*/
public function __call($method, $parameters)
{if ($method === 'macro') {$this->localMacros[$parameters[0]] = $parameters[1];return;}if (isset($this->localMacros[$method])) {array_unshift($parameters, $this);return $this->localMacros[$method](...$parameters);}if (isset(static::$macros[$method])) {if (static::$macros[$method] instanceof Closure) {return call_user_func_array(static::$macros[$method]->bindTo($this, static::class), $parameters);}return call_user_func_array(static::$macros[$method], $parameters);}if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {return $this->callScope([$this->model, $scope], $parameters);}if (in_array($method, $this->passthru)) {return $this->toBase()->{$method}(...$parameters);}$this->query->{$method}(...$parameters);return $this;
}

  上面是 5.6 版本的 __call,5.1 版本的 __call 相对来说,功能少一些,但是都有的功能就是,先去看 macro 有没有定义,如果前面条件都不满足就去调用 Query Builder 中对应的方法,当然,如果 Query Builder 里面也没有这个方法就会抛出异常。而实现这些功能都得益于 php 的 __call 魔术方法。

  关于 Eloquent Builder 和 Query Builder 的区别:

    a、我们使用 Eloquent Model 类或 Eloquent Model 子类进行操作的时候,如果使用的都是 Eloquent Builder 里面的方法,那么返回的也是一个 Eloquent Builder 或者是最终结果(视操作而定,有可能是 Collection 实例、null、某个 Model 子类的实例等)。虽然 __call 中可能会调用到 Query Builder 的方法,而 Query Builder 的方法里面可能会返回 $this,这时候我们可能会有一种错觉,我们通过 __call 间接调用了 Query Builder 里面方法的时候,返回的是不是一个 Query Builder 的实例。看起来好像的确是这样,但我们实际操作一下就发现,返回的还是 Eloquent Builder 实例。这是因为上面 __call 最后两行,如果写成

return $this->query->{$method}(...$parameters);

    那么返回的就是 Query Builder 了,但实际上,调用 Query Builder 里面的方法和 return 语句是分开的,当然,这里的 $this 肯定是 Eloquent Builder 的实例了。    

    b、Eloquent Builder 可以使用 Query Builder 的方法,但是 Query Builder 不能使用 Eloquent Builder 的方法。所以开发中使用 Model 进行 curd 操作的话,相对来说操控性更强,再加上有模型关联这些强大的功能,可以使用 Model 就没必要用 DB 类了。

  好像又说得有点远了,这一点应该说的是,Relation 的相关操作,因为 Relation 的 __call 里面也用到了 Query Builder,所以我们可以直接在关联上调用 Query Builder 里面的方法。但是这个时候关联使用需要加上括号。如:

$user->roles; // 用户所有角色
$user->roles()->count(); // roles() 调用返回 BelongsToMany 实例,// 可以在此后调用 Query Builder 里面的方法

   分页也类似 $user->roles()->paginate(); 这在我们需要查看某条记录的关联数据时候非常有用。

9、表名使用单数命名时候 Model 类不用定义 protected $table

  laravel 中表名默认是复数形式的,不知道大家有没有用复数做表名,我是没有这种习惯。如果我们想用单数命名,又不想每个 Model 类里面写一个 protected $table;可以重写 Model 类的 getTable 方法

/**
* Get the table associated with the model.
*
* @return string
*/
public function getTable()
{if (isset($this->table)) {return $this->table;}return str_replace('\\', '', Str::snake(class_basename($this)));
}

  把转复数的调用去掉就好了。

10、为复杂表单创建一个 validator

  这个其实也不算是什么技巧,可能一开始写得舒服就一个个 if 判断,但是这样子到最后会发现我们的代码越来越长,然后可读性也会越来越差。我们可以尝试使用一下 validator:

$validator = \Validator::make(request()->all(), ['buyer_id' => 'required','amount' => 'required|numeric|gt:0'
], ['buyer_id.required' => ':attribute不能为空','amount.required' => '请填写:attribute','amount.numeric' => ':attribute必须为数字','amount.gt' => ':attribute必须大于0'
], ['buyer_id' => '客户id','amount' => '金额'
]);
if ($validator->fails()) {return $this->ajaxFail($validator->messages()->first());
}

  有些看起来是没什么用的,如 buyer_id,一般情况下,我们会用一个隐藏域保存这个 id,但是我们还是得防止一些非法的请求,同时也是为了保持数据的完整性,因为这的确是必须的数据,如果有一天某个错误导致这个关联的 id 没有保存到,可能会导致一些神奇的 bug 出现。

11、返回页面的同时返回一些额外的信息(如警告、错误)

return redirect()->back()->withErrors('some error');

  前端获取:

@if(Session::has('error'))toastr.warning('{{Session::get('error')}}', '', options);
@endif

  其实 laravel 在处理表单的时候也有一些类似的处理, 如 redirect('form')->withInput();

12、在控制器以外的地方返回响应

  这种做法可能会导致难以调试的 bug(因为维护的人可能不知道在哪里返回了),不过实在需要的时候,可以用一用。

response()->json(['message' => 'test send response directly'])->send();exit;

  

13. 使用 EloquentCollection 加载关联

我们在使用 User::get() 的时候获取到的是一个 Illuminate\Database\Eloquent\Collection 实例,这个实例继承了 Illuminate\Support\Collection,可以直接使用 Illuminate\Support\Collection 的方法。除此之外,还有一些自身特有的方法,比如 load,我们可以使用 load 方法加载每一项的关联数据,好比如,如果每一个 User 又一个 info 关联,我们可以使用 $users->load('info') 关联,这种方法的好处是,避免了 n+1 的问题

14. 函数、 类方法的依赖注入正确使用姿势

  a.  函数

class TestService
{public $name = 'testService';
}function test(TestService $testService)
{var_dump($testService->name);
}// 通过依赖注入的方式调用这个 test 函数
// test 的参数会通过依赖注入的方式注入
app()->call('test');

  b. 类方法  

class TestService
{public function test(stdClass $stdClass){var_dump($stdClass);}
}// 类方法的依赖注入(非构造方法)
app()->call('TestService@test');

  

最后推荐一个好东西:Laravel 5.1 LTS 速查表

转载于:https://www.cnblogs.com/eleven24/p/9380514.html

个人在 laravel 开发中使用到的一些技巧(持续更新)相关推荐

  1. uni-app看图项目开发中遇到的小问题/知识点(持续更新)

    一.若后台返回的数据带如/d的换行字符,要用<text>标签包裹 二.动态替换url中的id data(){return{ id:0, } url=`/${this.id}/` 三.分页的 ...

  2. React-Native开发中常用的第三方控件持续更新

    2023.03.22更新 React Native ECharts 项目是一个开源的库,用于在 React Native 应用程序中创建交互式图表.通过用户友好的设计和简单的 API,开发者可以创建各 ...

  3. 【STM32开发中遇到的一些问题(会持续更新......)】

    1.STM32添加固件库编译时没有生成对应的.h文件 原因是添加了的固件库,没有使能.在工程中找到py32f0xx_hal_conf.h,将需要的功能取消注释即可. 2.Keil编译出现error: ...

  4. php gridview,PHP编程:yii2-GridView在开发中常用的功能及技巧总结

    <PHP编程:yii2-GridView在开发中常用的功能及技巧总结>要点: 本文介绍了PHP编程:yii2-GridView在开发中常用的功能及技巧总结,希望对您有用.如果有疑问,可以联 ...

  5. VS开发中的代码编写小技巧——避免重复代码编写的几种方法

    原文:VS开发中的代码编写小技巧--避免重复代码编写的几种方法 上一篇文章中程序员的幸福生活--有你的日子,每天都是情人节,收到了大家的很多好评.鼓励和祝福,非常感动,真诚的谢谢大家.也希望每个朋友都 ...

  6. iOS开发UI篇—IOS开发中Xcode的一些使用技巧

    iOS开发UI篇-IOS开发中Xcode的一些使用技巧 一.快捷键的使用 经常用到的快捷键如下: 新建 shift + cmd + n     新建项目 cmd + n             新建文 ...

  7. Laravel 好玩、好用扩展推荐(持续更新)

    以下内容不分排名,都是本人使用.以及体验过非常好用的扩展.本页内容将持续不断更新.添加新内容.移除旧内容 1. Prequel 清晰简洁的数据库管理 composer require protoqol ...

  8. PHP —— 一份前端开发工程师够用的PHP知识点(持续更新)

    PHP -- 一份前端开发工程师够用的PHP知识点(持续更新) <工欲善其事,必先利其器> 本文只是记录本小菜鸡在工作中遇到的知识点,欢迎大家随时补充! 一.初识 PHP 首先,PHP 它 ...

  9. 2021最新HarmonyOS鸿蒙系统应用开发之基础入门教程到实战—持续更新(第三节:鸿蒙的技术特征)

    第三节:鸿蒙的技术特征 每篇内容都有视频讲解,可直接点击观看+关注,持续更新中 2021最新HarmonyOS鸿蒙系统应用开发之基础入门教程到实战-持续更新(第二节:鸿蒙OS系统分布式操作) 硬件互助 ...

最新文章

  1. 各种登录源码来了!基础登录、验证码登录、小程序登录...全都要!
  2. PCL环境配置失败和运行PCL自带例子
  3. [Mysql]查看版本号的五种方式
  4. 桌面图标摆放图案_用图标制作醒目的图案
  5. 信息系html5论文,基于HTML5的智力游戏设计电子信息工程本科学生毕业论文.doc
  6. 带你彻底搞懂高性能网络模式Reactor 和 Proactor
  7. Linux中的权限管理命令-chmod
  8. pytorch learning
  9. ProjectEuler 009题
  10. Spring BeanFactory和FactoryBean的区别
  11. 商城类小程序,拼团、砍价、秒杀、预售,一套源码全搞定
  12. 云莲MK8215_联芸MAS0902固态硬盘开卡教程,量产工具软件下载
  13. 史上最全 ArcGIS 软件安装包分享
  14. 程序员机械键盘选购总结:键盘选的好,Bug一定少
  15. 招聘方眼里的猎聘和Boss直聘直观对比
  16. 我是怎么做到开源系统的角色权限功能的?
  17. word无法打开请去应用商店_抖音去水印 | HTTP Catcher方法全解析
  18. 设计如下类: 1) 建立一个Point类,表示平面中的一个点;建立一个Line类,表示平面中的一条线端, 内含两个Point类的对象;建立Triangle类,表示一个三角形
  19. android指定sim卡拨号,使用双SIM卡设备中的指定SIM拨打电话
  20. springboot的WebMvcConfigurerAdapter学习(现在常用实现webmvcConfigurer接口和继承WebMvcConfigurationSupport类)

热门文章

  1. 全闪存存储时代 NVMe到底是什么?
  2. 782B The Meeting Place Cannot Be Changed(二分)
  3. HBase参数配置及说明
  4. delphi 入门教程
  5. linux下忘记mysql root密码
  6. python3实现zip格式压缩文件夹
  7. ext form验证tip_FormValidator表单验证
  8. react-native 查看对象属性
  9. XamarinEssentials教程清空键值
  10. Visual Studio 2017为Android APK包签名