[TOC]
# ->relationship();与->relationship;的区别
## 对比
| $user->roles(); | $user->roles; |
| --- | --- |
| Builder 对象 | Collection(数据集) 或 Model(单条) |
* $user->roles()返回的是一个Builder的对象,后面可以跟进任何Builder方法,所以跟进Builder下的create/update/delete也就不奇怪了。
* $user->roles返回的是已经查询结束的Collection结果集,即使后面跟着的函数名字相同(比如:first、count等),但其实现的原理是完全不一样的,能在sql中处理的就在sql中处理,给数据集处理增加开销。
比如:
| $user->roles()->count(); | $user->roles->count(); |
| --- | --- |
| DB::exec('SELECT COUNT(*) FROM role_user WHERE uid = 1'); | count(DB::exec('SELECT * FROM role_user WHERE uid = 1')); |
很明显前者的性能要优秀的多。
# 善用->relationship();
在日常开发之中,以下语句可以参考:
创建时间倒序
~~~
function posts() {
return $this->hasMany('App\\Post', 'uid', 'id')->orderBy('created_at', 'DESC');
}
~~~
取10条
`$user->posts()->limit(10)->get();`
取1条
`$user->posts()->first();`
下面这条语句,除非你的确需要得到所有合集。
不然,SQL会先取出所有数据,再使用PHP的array_slice函数读取10个,这样浪费了大量性能。
~~~
$user->posts->slice(0, 10);
~~~
# with load
with,load一个更大的性能提升
## with 对比
不使用with
~~~
$posts = Post::where('created_at', '>' ,Carbon::today())->get(); //取出今天的所有帖子
foreach($posts as $post)
echo '作者', $post->user->username;
~~~
使用with
~~~
$posts = Post::with(['user'])-$posts = Post::with(['user'])->where('created_at', '>' ,Carbon::today())->get(); //取出今天的所有帖子
foreach($posts as $post)
echo '作者', $post->user->username;
~~~
可能看不出来两者的区别,此处我们换算成PHP、SQL语句看一下
~~~
// $posts = SELECT * FROM `posts` WHERE `created_at` > Carbon::today()
$posts = Post::where('created_at', '>' ,Carbon::today())->get();
foreach($posts as $post)
// $post->user = SELECT * FROM `users` WHERE `id` = $post->uid
echo '作者', $post->user->username;
~~~
使用with的情况
~~~
// $posts = SELECT * FROM `posts` WHERE `created_at` > Carbon::today()
// $uid_list = $posts->pluck('uid');
// $users = SELECT * FROM `users` WHERE `uid` IN ($uid_list)
// foreach($posts as $post) $post->user = $users[$post->uid];
$post = Post::with(['user'])->where('created_at', '>' ,Carbon::today())->get();
foreach($posts as $post)
// 此处并未查询数据库
echo '作者', $post->user->username;
~~~
从上例可以看出,
* 没有使用with时,对user的访问,循环多少次,就SQL请求多少次;
* 使用with时,只对user查询一次,性能提升了非常明显。
## with下级relationship
使用.查询下级relationship$user->posts()->comments()
~~~
User::with('posts.comments');
~~~
## with,Builder对象
取出所有用户,更新时间倒序;以及返回对应的用户组
~~~
$users = User::with(['posts' => function($query) {
$query->orderBy('updated_at', 'DESC');
}, 'roles'])->get();
~~~
以下误区需要注意:
下例开发者的本意是每个用户取出5条,其实系统是整个posts只取出5条,最后分配到每个User的就更少了,这很明显不符合开发者初愿,但是就目前来说并无优秀的解决办法。
~~~
$users = User::with(['posts' => function($query) {
$query->limit(5);
}])->get();
~~~
## load,延迟加载
load,和with的区别是:
* with是在组合Builder语句时就开始执行relationship
* load在Builder已经执行之后的relationship加载
**注意这个load(),这是对collection用的;**
~~~
$users = User::all();
if (someCondition) {
$users->load(['posts' => function($query) {
$query->limit(5);
}, 'roles']);
}
~~~
# has whereHas orWhereHas withCount
> haswhereHasorWhereHas其实是对JOIN语句的一个简化封装
## 评论数量> 0的帖子
~~~
$posts = Post::has('comments')->get();
~~~
## 评论数量> 3的帖子
~~~
$posts = Post::has('comments', '>=', 3)->get();
~~~
## 使用.查询下级relationship
比如关系是:$post->comment()->votes();
~~~
$posts = Post::has('comments.votes')->get();
~~~
## 指定条件的评论的数量> 0的帖子
~~~
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->orWhereHas(...)->get();
~~~
## 返回relationship的数量,字段为:RELATIONNAME_count
~~~
$posts = Post::withCount(['votes', 'comments' => function ($query) {
$query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
~~~
# empty 与 isset
## empty
我们以调取用户avatar为例
~~~
if (empty($user->avatar)) //常规判断
~~~
程序员希望是用empty测试avatar是否有记录,但是他却忽略了empty并不是函数,而是php的一种特殊语法结构。
empty只能检查变量是否为空,而不是函数(PHP 7.0以下),
$user->avatar在从未读取的情况下,会先去读取__get方法实现,__get会依次调取getAttribute()、getRelationValue()来实现,此时程序已经终止了继续,然后返回true,
除非:
~~~
$user->avatar; // 先读取一次,Model中的$relations['avatar']已经被填充数据
if (empty($user->avatar)) //此时才会有true/false
~~~
## isset
isset 的实现逻辑与empty类似,但是
~~~
# laravel\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php 3405 行
public function __isset($key) {
if (isset($this->attributes[$key]) || isset($this->relations[$key])) {
return true;
}
if (method_exists($this, $key) && $this->$key && isset($this->relations[$key])) {
return true;
}
return $this->hasGetMutator($key) && ! is_null($this->getAttributeValue($key));
}
~~~
Model中已经实现了__isset方法,并检查了$this->relations
所以,请使用isset代替empty
- 配置
- composer安装
- composer用法
- composer版本约束表达
- phpstorm
- sftp文件同步
- php类型约束
- laradock
- 配置文件缓存详解
- git
- 自定义函数
- 核心概念
- IOC
- 服务提供者
- Facade
- 契约
- 生命周期
- 路由
- 请求
- 命名路由
- 路由分组
- 资源路由
- 控制器路由
- 响应宏
- 响应
- Command
- 创建命令
- 定时任务
- console路由
- 执行用户自定义的定时任务
- artisan命令
- 中间件
- 创建中间件
- 使用中间件
- 前置和后置
- 详细介绍
- 访问次数限制
- 为 VerifyCsrfToken 添加过滤条件
- 单点登录
- 事件
- 创建
- ORM
- 简介
- DB类
- 配置
- CURD
- queryScope和setAttribute
- 查看sql执行过程
- 关联关系
- 一对一
- 一对多
- 多对多
- 远程关联
- 多态一对多
- 多态多对多
- 关联数据库的调用
- withDefault
- 跨模型更新时间戳
- withCount,withSum ,withAvg, withMax,withMin
- SQL常见操作
- 模型事件
- 模型事件详解
- 模型事件与 Observer
- deleted 事件未被触发
- model validation
- ORM/代码片段
- Repository模式
- 多重where语句
- 中间表类型转换
- Collection集合
- 新增的一些方法
- 常见用法
- 求和例子
- 机场登机例子
- 计算github活跃度
- 转化评论格式
- 计算营业额
- 创建lookup数组
- 重新组织出表和字段关系并且字段排序
- 重构循环
- 其他例子
- 其他问题一
- 去重
- 第二个数组按第一个数组的键值排序
- 搜索ES
- 安装
- 表单
- Request
- sessiom
- Response
- Input
- 表单验证
- 简介
- Validator
- Request类
- 接口中的表单验证
- Lumen 中自定义表单验证返回消息
- redis
- 广播事件
- 发布订阅
- 队列
- 守护进程
- redis队列的坑
- beanstalkd
- rabbitmq
- redis队列
- 日志模块
- 错误
- 日志详解
- 数据填充与迁移
- 生成数据
- 数据填充seed
- migrate
- 常见错误
- Blade模板
- 流程控制
- 子视图
- URL
- 代码片段
- Carbon时间类
- 一些用法
- 邮件
- 分页
- 加密解密
- 缓存
- 文件上传
- 优化
- 随记
- 嵌套评论
- 判断字符串是否是合法的 json 字符串
- 单元测试
- 计算出两个日期的diff
- 自定义一个类文件让composer加载
- 时间加减
- 对象数组互转方法
- 用户停留过久自动退出登录
- optional 辅助方法
- 文件下载
- Api
- Dingo api
- auth.basic
- api_token
- Jwt-Auth
- passport
- Auth
- Authentication 和 Authorization
- Auth Facade
- 授权策略
- Gates
- composer包
- debug包
- idehelp包
- image处理
- 验证码
- jq插件
- 第三方登录
- 第三方支付
- log显示包
- 微信包
- xss过滤
- Excel包
- MongoDB
- php操作
- 聚合查询
- 发送带附件邮件
- 中文转拼音包
- clockwork网页调试
- emoji表情
- symfony组件
- swooletw/laravel-swoole
- 常见问题
- 跨域问题
- Laravel队列优先级的一个坑
- cache:clear清除缓存问题
- .env无法读取
- 源码相关基础知识
- __set和__get
- 依赖注入、控制反转和依赖倒置原则
- 控制反转容器(Ioc Container)
- 深入服务容器
- call_user_func
- compact
- 中间件简易实现
- array_reduce
- 中间件实现代码
- Pipeline管道操作
- composer自动加载
- redis延时队列
- 了解laravel redis队列
- cli
- 源码解读
- Facade分析
- Facade源码分析
- IOC服务容器
- 中间件原理
- 依赖注入浅析
- 微信
- 微信公众号
- 常用接收消息
- 6大接收接口
- 常用被动回复消息
- 接口调用凭证
- 自定义菜单
- 新增素材
- 客服消息
- 二维码
- 微信语音
- LBS定位
- 网页授权
- JSSDK
- easywechat
- 小程序
- 小程序配置app.json