MeWrite Docs

Laravel whereHas() not working with MorphTo

LaravelでwhereHas()がMorphToリレーションで動作しない問題の原因と解決策

概要

LaravelのEloquentでwhereHas()を使用してMorphTo(ポリモーフィック)リレーションをフィルタリングしようとすると、期待通りに動作しない問題です。これはMorphToの特性上、関連先のテーブルが動的に決まるため発生します。

エラーメッセージ

Call to undefined method Illuminate\Database\Eloquent\Relations\MorphTo::getRelationExistenceQuery()
BadMethodCallException: Call to undefined method MorphTo::getQualifiedParentKeyName()
Query returns no results when using whereHas with MorphTo

原因

  1. MorphToの仕様: MorphToは関連先が動的に決まるため、whereHas()が直接サポートされていない
  2. 逆方向の制約: MorphToは「親」側の関係であり、子側からの制約が難しい
  3. テーブル名が不明: クエリ生成時に関連テーブルが特定できない

解決策

1. whereHasMorph()を使用(Laravel 5.8.12+)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Laravel 5.8.12以降で使用可能
use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;

// 特定のモデルタイプに対してwhereHasを実行
$comments = Comment::whereHasMorph(
    'commentable',  // MorphToリレーション名
    [Post::class, Video::class],  // 対象モデル
    function ($query) {
        $query->where('published', true);
    }
)->get();

// すべてのモデルタイプに対して実行
$comments = Comment::whereHasMorph(
    'commentable',
    '*',  // すべてのタイプ
    function ($query) {
        $query->where('created_at', '>', now()->subDays(7));
    }
)->get();

2. モデルタイプごとに条件を変える

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$comments = Comment::whereHasMorph(
    'commentable',
    [Post::class, Video::class],
    function ($query, $type) {
        // モデルタイプごとに異なる条件
        if ($type === Post::class) {
            $query->where('status', 'published');
        } elseif ($type === Video::class) {
            $query->where('is_public', true);
        }
    }
)->get();

3. 古いLaravelバージョンでの回避策

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Laravel 5.8.12未満の場合
use Illuminate\Database\Eloquent\Builder;

// 方法1: サブクエリを使用
$postIds = Post::where('published', true)->pluck('id');
$videoIds = Video::where('is_public', true)->pluck('id');

$comments = Comment::where(function ($query) use ($postIds, $videoIds) {
    $query->where(function ($q) use ($postIds) {
        $q->where('commentable_type', Post::class)
          ->whereIn('commentable_id', $postIds);
    })->orWhere(function ($q) use ($videoIds) {
        $q->where('commentable_type', Video::class)
          ->whereIn('commentable_id', $videoIds);
    });
})->get();

// 方法2: マクロを定義
Builder::macro('whereMorphedTo', function ($relation, $model) {
    return $this->where($relation . '_type', get_class($model))
                ->where($relation . '_id', $model->getKey());
});

4. orWhereHasMorph()の使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// OR条件での検索
$comments = Comment::whereHasMorph(
    'commentable',
    Post::class,
    fn ($query) => $query->where('category', 'tech')
)->orWhereHasMorph(
    'commentable',
    Video::class,
    fn ($query) => $query->where('duration', '>', 300)
)->get();

5. スコープとして定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/Models/Comment.php
class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo();
    }

    public function scopeWithPublishedCommentable($query)
    {
        return $query->whereHasMorph(
            'commentable',
            [Post::class, Video::class],
            function ($query, $type) {
                $column = $type === Post::class ? 'published' : 'is_public';
                $query->where($column, true);
            }
        );
    }
}

// 使用
$comments = Comment::withPublishedCommentable()->get();

6. withWhereHas()との組み合わせ(Laravel 9+)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// リレーションをEager Loadしながらフィルタリング
$comments = Comment::withWhereHas('commentable', function ($query) {
    // 注意: MorphToでは制限あり
})->get();

// 代わりにwhereHasMorphと組み合わせる
$comments = Comment::whereHasMorph(
    'commentable',
    [Post::class],
    fn ($q) => $q->where('published', true)
)->with(['commentable' => fn ($q) => $q->where('published', true)])
->get();

モデル定義例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// app/Models/Comment.php
class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo();
    }
}

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

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

よくある間違い

  • whereHas()を直接MorphToリレーションに使おうとする
  • Laravel 5.8.12未満でwhereHasMorph()を使おうとする
  • モデルタイプの指定を忘れて*を使い、意図しないテーブルも検索される
  • ポリモーフィックテーブルの*_typeカラムに保存されるクラス名の形式を誤解する

関連エラー

参考リンク

Laravel の他のエラー

最終更新: 2025-12-14