Laravelのwithメソッドがなにをしているのかを知るためにソースコードを読んでみる



## with

withメソッドがどんなことをしているかをなんとなく知るための記事です。
ネスト記法でクロージャも渡したときにどこに制約が付与されるのか知りたかっただけです。

- laravel5.8

### string

```php
User::with('comments')->get();
```

staticな呼び出しでModelの`with`メソッドが呼ばれます。

```php:Illuminate/Database/Eloquent/Model.php
public static function with($relations)
{
    return static::query()->with(
        is_string($relations) ? func_get_args() : $relations
    );
}
```

`query()`は`\Illuminate\Database\Eloquent\Builder`インスタンスを生成しています。
引数がStringなら`func_get_args()`で連想配列にして、それ以外ならそのまま渡します。
今回なら`[0 => 'comments']`という形になります。

https://www.php.net/manual/ja/function.func-get-args.php

```php:\Illuminate\Database\Eloquent\Builder
public function with($relations)
{
    $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);

    $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);

    return $this;
}
```


`parseWithRelations()`で引数に渡したものをいい感じになんやかんやしてくれます。


```php
protected function parseWithRelations(array $relations)
{
    $results = [];

    foreach ($relations as $name => $constraints) {

        if (is_numeric($name)) {
            $name = $constraints;

            [$name, $constraints] = Str::contains($name, ':')
                        ? $this->createSelectWithConstraint($name)
                        : [$name, function () {
                            //
                        }];
        }

        $results = $this->addNestedWiths($name, $results);

        $results[$name] = $constraints;
    }

    return $results;
}
```

`foreach`で回していくのですが、`[0 -> 'comments']`なので`$name = 0`、`$constraints = 'comments'`になります。
今回は`$name`が数値なのでif文の中を通ります。

`$name`に`$constraints`を代入しているのでどちらも同じ値を指します。

`with()`では`with('comment:id, body')`のように`:`を使ってとってきたいカラム名を指定できます。
そのようなときに呼ばれるのが`createSelectWithConstraint()`ですが、今回は関係ないので`$name = $name`、`$constraints = function () {}`になります。

今回はネスト記法で呼んでないので`addNestedWiths()`はスルーします。

最終的にこのような形で返されます。

```php
$results = ['comments' => function () {}]
```

`with()`に戻ります。

```php
public function with($relations)
{
    $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);

    $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);

    return $this;
}
```

`$this->eagerload`と`array_merge()`してeager loadをセットしています。

`with()`は`$eagerload`に`[$name => $constraints]`のように値を設定するメソッドということがわかりました。

### constraints

```php
User::with(['comments' => function ($q) { $q->something() } ])-get();
```

連想配列で制約をつけて渡す場合はシンプルです。流れは一緒なのでいきなり`parseWithRelations()`から見ていきます。

```php
protected function parseWithRelations(array $relations)
{
    $results = [];

    foreach ($relations as $name => $constraints) {

        $results[$name] = $constraints;
    }

    return $results;
}
```

ただ`$results`に入れ直しているだけです。
なので最終的に`$this->eagerload`に`['comments' => function ($q) { $q->something() }]`とセットされます。

### Nested

リレーションが連なるときにドット記法を使えば一気にデータを取ってくることができます。

```php
User::with(['comments.hoge' => function ($q) {$q->something()}])->get();
```

途中までの流れは上記と一緒です。

`parseWithRelations()`内の`addNestedWiths()`でネスト記法で書かれたものの処理をしています。

```php
['comments' => function () {}, 'comments.hoge' => function() {}]
```

こんな風にして返してくれます。

この時点では指定した制約は処理していません。`$results[$name] = $constraints`で処理をします。

```php
protected function parseWithRelations(array $relations)
{
    $results = [];

    foreach ($relations as $name => $constraints) {

        $results = $this->addNestedWiths($name, $results);

        $results[$name] = $constraints;
    }

    return $results;
}
```

結果的に返される値は以下になります。

```php
['comments' => function () {}, 'comments.hoge' => function($q) {$q->something()}]
```

ここで重要なのはネスト記法で制約も指定したときは、一番深いネストにしか制約が付与されないということです。

## get

`get()`では`$eagerload`を使用してSQL文を組み立てて発行します。

```php
$relation = $name();

$constraints($relation);
```

`get()`における今回の関連部分を簡単に抜き出すとこんな感じです。

`$name()`でメソッドを実行してクエリが発行できるオブジェクトを生成して、そのオブジェクトを`$constraints`のクロージャに渡す流れになります。

ネストしたリレーションも`get()`内でパースされて、再帰的に`with()`が呼ばれることになります。