【Laravel】1対多リレーションのコードリーディング

以下のレコード、コードで実行します。

```mysql:userテーブル
+----+--------+
| id | name   |
+----+--------+
|  1 | admin  |
|  2 | user01 |
+----+--------+
```
email、email_verified_at、password、remember_token、created_at、updated_atカラムは省略します。

```mysql:postsテーブル
+----+--------+-------+---------------------+---------------------+---------+
| id | title  | body  | created_at          | updated_at          | user_id |
+----+--------+-------+---------------------+---------------------+---------+
|  1 | title1 | body1 | 2019-04-19 03:33:35 | 2019-04-20 02:10:58 |       1 |
|  2 | title2 | body2 | 2019-04-19 03:33:35 | 2019-04-20 02:10:58 |       1 |
|  3 | title3 | body3 | 2019-04-19 03:33:35 | 2019-04-20 02:10:58 |       1 |
|  4 | title4 | body4 | 2019-04-19 03:33:35 | 2019-04-19 03:33:35 |       2 |
|  5 | title5 | body5 | 2019-04-19 03:33:35 | 2019-04-19 03:33:35 |       2 |
+----+--------+-------+---------------------+---------------------+---------+
```

```php:app/User.php
<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function posts()
    {
        /**
         * @return \Illuminate\Database\Eloquent\Relations\HasMany
         */
        return $this->hasMany('App\Post')->latest();
    }
}
```

```php:app/Post.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}
```

```php
<?php
use App\User;

$admin_user = User::find(1);

foreach($admin_user->posts as $post) {
    echo "title: $post->title\n";
    echo "body: $post->body\n";
}

/*
title: title1
body: body1
title: title2
body: body2
title: title3
body: body3
*/
```

`User::find(1)`はusersテーブルの`id = 1`のデータを入れた、Userオブジェクトを返します。

`$admin_user->posts`は`user_id = 1`のデータを入れた、Postオブジェクトを入れた、
`Illuminate\Database\Eloquent\Collection`オブジェクトを返します。

`User`オブジェクトは`posts`プロパティを持っていないので`__get()`メソッドが呼ばれます。

```php:Illuminate/Database/Eloquent/Model.php
/**
 * Dynamically retrieve attributes on the model.
 *
 * @param  string  $key
 * @return mixed
 */
public function __get($key)
{
    return $this->getAttribute($key);
}
```

```php:
/**
 * Get an attribute from the model.
 *
 * @param  string  $key
 * @return mixed
 */
public function getAttribute($key)
{
    if (! $key) {
        return;
    }

    // If the attribute exists in the attribute array or has a "get" mutator we will
    // get the attribute's value. Otherwise, we will proceed as if the developers
    // are asking for a relationship's value. This covers both types of values.
    if (array_key_exists($key, $this->attributes) ||
        $this->hasGetMutator($key)) {
        return $this->getAttributeValue($key);
    }

    // Here we will determine if the model base class itself contains this given key
    // since we don't want to treat any of those methods as relationships because
    // they are all intended as helper methods and none of these are relations.
    if (method_exists(self::class, $key)) {
        return;
    }

    return $this->getRelationValue($key);
}
```

`$this->attributes`に`posts`はありません。
selfは`Model`クラスなので、`posts`は定義されていません。
よって、$this->getRelationValue('posts')を返します。

```php:Illuminate/Database/Eloquent/Concerns/HasAttributes.php
/**
 * Get a relationship.
 *
 * @param  string  $key
 * @return mixed
 */
public function getRelationValue($key)
{
    // If the key already exists in the relationships array, it just means the
    // relationship has already been loaded, so we'll just return it out of
    // here because there is no need to query within the relations twice.
    if ($this->relationLoaded($key)) {
        return $this->relations[$key];
    }

    // If the "attribute" exists as a method on the model, we will just assume
    // it is a relationship and will load and return results from the query
    // and hydrate the relationship's value on the "relationships" array.
    if (method_exists($this, $key)) {
        return $this->getRelationshipFromMethod($key);
    }
}
```

```php:Illuminate/Database/Eloquent/Concerns/HasRelationships.php
/**
 * Determine if the given relation is loaded.
 *
 * @param  string  $key
 * @return bool
 */
public function relationLoaded($key)
{
    return array_key_exists($key, $this->relations);
}
```

$this->relationsに`'posts'`キーがあるので`$this->relations['post']`を返します。

```php
var_dump($admin_user);

/*
object(App\User)#365 (27) {

（中略）

["relations":protected]=>
  array(1) {
    ["posts"]=>
    object(Illuminate\Database\Eloquent\Collection)#398 (1) {
      ["items":protected]=>
      array(3) {
        [0]=>
        object(App\Post)#399 (26) {
          ["connection":protected]=>
          string(5) "mysql"
          ["table":protected]=>
          string(5) "posts"
          ["primaryKey":protected]=>
          string(2) "id"
          ["keyType":protected]=>
          string(3) "int"
          ["incrementing"]=>
          bool(true)
          ["with":protected]=>
          array(0) {
          }
          ["withCount":protected]=>
          array(0) {
          }
          ["perPage":protected]=>
          int(15)
          ["exists"]=>
          bool(true)
          ["wasRecentlyCreated"]=>
          bool(false)
          ["attributes":protected]=>
          array(6) {
            ["id"]=>
            int(1)
            ["title"]=>
            string(6) "title1"
            ["body"]=>
            string(5) "body1"
            ["created_at"]=>
            string(19) "2019-04-19 03:33:35"
            ["updated_at"]=>
            string(19) "2019-04-20 02:10:58"
            ["user_id"]=>
            int(1)
          }
          ["original":protected]=>
          array(6) {
            ["id"]=>
            int(1)
            ["title"]=>
            string(6) "title1"
            ["body"]=>
            string(5) "body1"
            ["created_at"]=>
            string(19) "2019-04-19 03:33:35"
            ["updated_at"]=>
            string(19) "2019-04-20 02:10:58"
            ["user_id"]=>
            int(1)
          }
          （中略）
        }
        [1]=>
        object(App\Post)#400 (26) {
            （中略）
        }
        [2]=>
        object(App\Post)#401 (26) {
            （中略）
        }
      }
    }
  }
  （中略）
}
*/
```

`Post`オブジェクトの`$this->attributes`には`title`キー、`body`キーが存在するので`getAttribute`メソッドは`$this->getAttributeValue($key)`を返します。

あとは長くなるので省略しますが、`getAttributeValue($key)`は`$this->attributes`に`$key`が存在すれば、`$key`と同じ名前のカラムからデータを取得し、なれれば`NULL`を返します。