💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[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