【Laravel】ポリモフィック関連のデータの取得

# 0.はじめに
[前回](https://qiita.com/daishiman/items/a612d9cdeb5065a7acb1)に引き続きポリモフィック関連の記事を書きます。

今回はポリモフィック関連のデータの取得の記事になります。

# 1.目次
[0.はじめに](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#0%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)
[1.目次](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#1%E7%9B%AE%E6%AC%A1)
[2.この記事を読んだらわかること](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#2%E3%81%93%E3%82%8C%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E3%82%89%E3%82%8F%E3%81%8B%E3%82%8B%E3%81%93%E3%81%A8)
[3.DB の確認](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#3db-%E3%81%AE%E7%A2%BA%E8%AA%8D)
[4.子に条件を与えた親のデータを取得](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#4%E5%AD%90%E3%81%AB%E6%9D%A1%E4%BB%B6%E3%82%92%E4%B8%8E%E3%81%88%E3%81%9F%E8%A6%AA%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97)
[5.親に条件を与えた子のデータの取得](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#5%E8%A6%AA%E3%81%AB%E6%9D%A1%E4%BB%B6%E3%82%92%E4%B8%8E%E3%81%88%E3%81%9F%E5%AD%90%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%8F%96%E5%BE%97)
[6.さいごに](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#6%E3%81%95%E3%81%84%E3%81%94%E3%81%AB)
[7.参考](https://qiita.com/daishiman/items/ec490f27e0870ccb3ab6#7%E5%8F%82%E8%80%83)

# 2.この記事を読んだらわかること

:::note info
条件をもたせたポリモフィック関連のデータの取得
  - 子供側のデータの取得方法
  - 親側のデータの取得方法
:::

# 3.DB の確認
### テーブルの関係性

どのようなデータを使用するかは、[前回](https://qiita.com/daishiman/items/a612d9cdeb5065a7acb1#1%E5%AF%BE%E5%A4%9A%E3%81%AE%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E9%96%A2%E9%80%A3%E3%81%AE%E5%A0%B4%E5%90%88)の記事をご確認頂けますと幸いです。

ざっくり言うと、
commnet（子）が、video（親）と post（親）をもっている関係となります。

ER図で表すとこんな感じです

```plantuml
@startuml PolymorphicOneToManyTable
entity posts as "posts\n[投稿（親）]" {
  + id [PK]
  --
  title [タイトル]
  body [本文]
}

entity videos as "videos\n[動画（親）]" {
  + id [PK]
  --
  title [タイトル]
  url [URL]
}

entity comments as "comments\n[コメント（子）]" {
  + id [PK]
  --
  body [本文]
  commentable_id
  commentable_type
}

posts ||-d-o{ comments
videos ||-d-o{ comments
@enduml

```


次に、DB の中はこのようになっています。


### posts のデータ

| id  | title                | body             |
|:---:| -------------------- | ---------------- |
|  1  | コメントなしタイトル | コメントなし本文 |
|  2  | post_title1          | post_body1       |
|  3  | タイトル1            | 投稿本文1        |
|  4  | post_title2          | post_body2       |



### videos のデータ

| id  | title                | url                        |
|:---:| -------------------- | -------------------------- |
|  1  | コメントなしタイトル | http://video_title/video/1 |
|  2  | video_title1         | http://video_title/video/1 |
|  3  | タイトル1            | http://video_title/video/2 |
|  4  | video_title2         | http://video_title/video/3 |


### comments のデータ

| id  | body                | commentable_id | commentable_type |
|:---:| ------------------- |:--------------:| ---------------- |
|  1  | コメント作成1 |       2        | App\Models\Post  |
|  2  | コメント作成2 |       3        | App\Models\Post  |
|  3  | コメント作成3 |       2        | App\Models\Video |
|  4  | コメント作成4 |       3        | App\Models\Video |
|  5  | コメント作成5 |       4        | App\Models\Post  |
|  6  | コメント作成6 |       4        | App\Models\Video |



# 4.子に条件を与えた「親のデータを取得」

以下の内容は[公式ドキュメント](https://readouble.com/laravel/8.x/ja/eloquent-relationships.html?header=%25E3%2582%25AB%25E3%2582%25B9%25E3%2582%25BF%25E3%2583%25A0%25E3%2583%259D%25E3%2583%25AA%25E3%2583%25A2%25E3%2583%25BC%25E3%2583%2595%25E3%2582%25A3%25E3%2583%2583%25E3%2582%25AF%25E3%2582%25BF%25E3%2582%25A4%25E3%2583%2597#many-to-many-polymorphic-relations)を参考にしております。

簡単なデータの取得からいってみたいと思います。

### comment（子） を持っている post（親） を取得する

:::note info
まずは、親（Post）が子（comment）持っているデータを取得
:::

```php:
use App\Models\Post;

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

取得したデータは、
```php:
=> Illuminate\Database\Eloquent\Collection {#4602
     all: [
       App\Models\Post {#4607
         id: 2,
         title: "post_title1",
         body: "post_body1",
       },
       App\Models\Post {#4350
         id: 3,
         title: "タイトル1",
         body: "投稿本文1",
       },
       App\Models\Post {#3990
         id: 4,
         title: "post_title2",
         body: "post_body2",
       },
     ],
   }

```

となます。

ここで、注意しなといけないのは、
**id が 1 のデータがない**ことです。

これは、`Post::has('comments')` の通り、  
comment のデータを持っている post のデータを取得しているためです。

**comment（子） を持っていない post（親） は取得できません。**

### comment（子） に条件を与えて post（親） を取得する

:::note info
comments テーブルの id が 1 を持っている post のデータを取得
:::

```php:
use App\Models\Post;
use Illuminate\Database\Eloquent\Builder;

$posts = Post::whereHas(
        'comments',
        function (Builder $query) {
            $query->where('comments.id', '=', 1);
        }
    )->get();
```

取得したデータは、
```php
=> Illuminate\Database\Eloquent\Collection {#4616
     all: [
       App\Models\Post {#4613
         id: 2,
         title: "post_title1",
         body: "post_body1",
       },
     ],
   }
```
となります。

comments テーブルの `id が 1` を持っている post のデータを取得するコードとなります。

`whereHas` メソッドを使うと複雑な条件を定義することができます。
 
:::note info
`whereHas` メソッドの説明
- 第1引数には、`リレーション先のテーブル名`  
- 第2引数には、`リレーション先の取得したいデータの条件のコールバック関数` 
:::

を記述します。


それでは、comments テーブルの comment の id が 1 のデータを確認してみます。

| id  | body          | commentable_id | commentable_type |
|:---:| ------------- |:--------------:| ---------------- |
|  1  | コメント作成1 |       2        | App\Models\Post  |

`commentable_id が 2`、
`commentable_type が App\Models\Post` 
となっています。 


これは、親が何かを表しています。

- `commentable_id` は、親の id
- `commentable_type` は、親のタイプ（※ Model の情報）

を示しています。

なので、 

**comments テーブルの id が 1 、post テーブル の id が 4 
のデータを持っているということを示しています。**


posts の `id が４` のデータは、

| id  | title                | body             |
|:---:| -------------------- | ---------------- |
|  2  | post_title1          | post_body1       |

となっており、取得したデータとテーブルが一致していることがわかるかと思います。


# 5.親に条件を与えた「子のデータの取得」

それでは、次に子側のデータの取得に方法に入りたいと思います。

### post（親） や video（親） に条件を与えた comment（子） を取得する

:::note info
ポリモフィック関連の親（post や video）に条件を与えた子（comment）のデータの取得
:::

早速、コードを見てみましょう。

```php:

use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Builder;

$comments = Comment::whereHasMorph(
        'commentable',
        [Post::class, Video::class],
        function (Builder $query) {
            $query->where('title', '=', 'タイトル1');
        }
    )->get();
```

取得したデータは、

```php
=> Illuminate\Database\Eloquent\Collection {#4613
     all: [
       App\Models\Comment {#4597
         id: 2,
         body: "コメント作成2",
         commentable_id: 3,
         commentable_type: "App\Models\Post",
       },
       App\Models\Comment {#4611
         id: 4,
         body: "コメント作成4",
         commentable_id: 3,
         commentable_type: "App\Models\Video",
       },
     ],
   }
```

となります。

これは、
親の Post と Video の `title カラムが タイトル1` を持っている子の Comment を取得しています。

ポリモフィック関連で親に条件を持たすことができているのは、
`whereHasMorph` メソッドになります。

:::note info
`whereHasMorph` メソッドの説明
- 第1引数には、CommentModel で定義した`ポリモフィック先のデータを取得するメソッド`  
（[Laravel でポリモフィック関連の Factory を作成してみた](https://qiita.com/daishiman/items/a612d9cdeb5065a7acb1#commentmodel-%E5%AD%90)参照）
- 第2引数には、`ポリモフィック関連の親のクラス`  
※ 今回は、複数の親のデータを取得しているが、単数の親でも問題ありません。
　 ただ単数の場合は、array 型ではなく、string 型でOKです。
- 第3引数には、`リレーション先の取得したいデータの条件のコールバック関数`
:::

を記述しています。

`title カラムが タイトル1` を持っている Post::class, Video::class は、下記のとおりです。

Post::class 

| id  | title                | body             |
|:---:| -------------------- | ---------------- |
|  3  | タイトル1            | 投稿本文1        |

Video::class

| id  | title                | url                        |
|:---:| -------------------- | -------------------------- |
|  3  | タイトル1            | http://video_title/video/2 |


上記のデータを持っている comment は

| id  | body                | commentable_id | commentable_type |
|:---:| ------------------- |:--------------:| ---------------- |
|  2  | コメント作成2 |       3        | App\Models\Post  |
|  4  | コメント作成4 |       3        | App\Models\Video |
 

となっており、取得したデータとテーブルが一致していることがわかるかと思います。

`commentable_id` が 親の Post::class, Video::class の id 、  
`commentable_type` が Post::class, Video::class を示しています。




# 6．さいごに

ポリモフィック関連先の条件にコールバック関数をつかっているので複雑に感じましたが、
「どこから」「どんな条件」でデータを取得するかの基本が掴めていたら理解することができるのかなと感じました。

とか言いつつ、私自身もこれを理解するのに時間がかかりました。

今回は、条件は１つしかありませんが、
コールバック関数内で複数の条件をもたせて、もっと複雑な処理をすることができます。


# 7.参考

- [Laravel 公式ドキュメント(ReaDouble)](https://readouble.com/laravel/8.x/ja/eloquent-relationships.html?header=%25E3%2582%25AB%25E3%2582%25B9%25E3%2582%25BF%25E3%2583%25A0%25E3%2583%259D%25E3%2583%25AA%25E3%2583%25A2%25E3%2583%25BC%25E3%2583%2595%25E3%2582%25A3%25E3%2583%2583%25E3%2582%25AF%25E3%2582%25BF%25E3%2582%25A4%25E3%2583%2597#many-to-many-polymorphic-relations)