5.8.27 버전에 whereHasMorph() 추가됨 free

2019-07-03

벨 5.8.27이 나왔습니다. 오늘은 5.8.27에 새로 추가된 기능 중 whereHasMorph()를 알아보겠습니다. 어제자 1 1 벨에도 다형성 관계가 잠깐 나왔었는데 오늘도 다형성 관계 관련 내용이네요!


5.8.27 버전에 whereHasMorph() 추가됨


whereHasMorph()는 다형성 관계인 연관 모델이 특정 조건을 만족하는 것들만 조회하는 기능이다. whereHas()가 강화된 셈인데, 이해를 돕기 위해 has()로 거슬러 올가보겠다.


has


has는 관련 모델이 존재하는 것만 조회하는 기능이다.


$posts = Post::has('comment')->get();

위와 같이 하면 전체 Post 중 Comment를 하나도 가지고 있는 것들만 가져온다.


whereHas


whereHas()는 단순 존재 여부가 아닌 특정 조건 조건을 만족하는 관련 모델을 갖고 있는 것만 조회하는 기능이다.


$posts = Post::whereHas('comments', function($query){
$query->where('content', 'like', 'foo%');
});

위 코드는 ‘foo’로 시작하는 Comment를 가진 Post만 가져온다.


하지만 다형성 관계 때 whereHas는 반쪽만 작동한다. 예를 들어, Post와 Video 모두에 Comment를 할 수 있어서 다음과 같이 다형성 관계를 갖는다고 가정해보자.


class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}

class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}

class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}

여전히 whereHas를 이용해서 특정 조건을 만족하는 Comment를 가진 Post만 조회하는 건 가능하다.


$posts = Post::whereHas('comments', function($query){
$query->where('content', '%foo%');
});

하지만 반대로 특정 조건을 만족하는 Post를 갖는 Comment를 조회하는 건 불가능하다.


$comments = Comment::whereHas('post', function($query){
$query->where('content', '%foo%');
});

위와 같은 으로 해야할텐데, 이렇게 하면 Comment 모델에서 post() 메소드를 찾지 못해 에러가 난다. post(), video() 같이 모델별로 메소드를 만드는 대신 commentable() 메소드 하나만 쓰는게 대다 다형성 관계이기 때문에 이제와서 post(), video() 등을 넣는건 넌센스다. 더 나아가, 타입에 관계없이 특정 조건을 만족하는 연관 모델을 갖는 Comment를 조회하는 건 더더욱 불가능하다. 이러한 들을 가능케 하는게 이번에 새로 추가된 whereHasMorph()다.


whereHasMorph


whereHasMorph()를 쓰면 다형성 관계하에서 조건에 맞는 연관 모델이 있는 것들만 조회할 수 있다.


$comments = Comment::whereHasMorph('commentable', [Post::class, Comment::class], function($query){
$query->where('content', '%foo%');
});

위 코드는 content 필드에 ‘foo’가 포함된 Post나 Video를 갖는 Comment를 조회한다. 쿼리로 치면 다음과 같다고 한다.


select * from "comments"
where (
(
"commentable_type" = 'App\Post' and exists (
select * from "posts" where "comments"."commentable_id" = "posts"."id" and "title" = 'foo'
)
) or (
"commentable_type" = 'App\Video' and exists (
select * from "videos" where "comments"."commentable_id" = "videos"."id" and "title" = 'foo'
)
)
)


특정 모델로 제한하지 않고 어떤 모델이든 다 검색하고자 하면 ‘*’를 쓰면 된다.


$comments = Comment::whereHasMorph('commentable', '*', function($query){
$query->where('content', '%foo%');
});

타입마다 필드명이 다를 수 있다. 혹은 타입마다 다른 조건을 주고 싶을 수도 있다. 세번째 인자인 익명함수의 두번째 인자로 타입을 넘겨주어 타입마다 다른 조건을 걸 수 있다.


$comments = Comment::whereHasMorph('commentable', [Post::class, Comment::class], function($query, $type){
if ($type === Post::class) {
// $query->
}

if ($type === Video::class) {
// $query->
}
});

참고자료



유료 구독자 전용 레터입니다.

한 달 1만원으로 매일 라라벨 관련 메일 받아보시고 과거 메일도 열람하세요. 일반 구독으로 공개글만 받아보실 수도 있습니다.

구독하기 버튼을 눌러주시면 구독과 동시에 xly에도 가입됩니다.

이현석

바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서를 쓰고, 클린 아키텍처 인 PHP를 번역했습니다. 2020년에 출간될 Laravel Up & Running 2nd Edition을 번역하고 있습니다.