リレーションはメソッドで定義したはずが、プロパティアクセスで動作する謎を解明する（Laravel）

## 前提
Bookというモデルには、detail()というメソッドが定義してあるとする。この定義によりBookモデルとBookdetailモデルが、「bookdetail_idとid」を通して、1対1に結合（hasOne）していることになる。

```php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
  public function detail(){
    return $this->hasOne('\App\Bookdetail','id','bookdetail_id');
  }
}
```

下記は、Bookモデルからid=1であるもののbookdetail_idを通して、Bookdetailモデルからisbnを出力するコードである。

```php
class TestController extends Controller
{
  public function test(){
    $book = \App\Book::find(1);
    echo $book->detail->isbn;
  }
}
```

## 疑問点
先ほどのBookモデルでは、detail()とメソッドとして定義したはずだ。しかし、このコードでは、->detailとプロパティアクセスの形になっている。どこにもメソッドとして実行する記述はない。いったい何が起こっているのだろう？


# Laravelのコアファイルを深掘りする
それでは、Laravelのコアファイルを一つずつ辿っていきたい。
本記事の動作前提となるファイルは下記に用意してあるので、自分でサンプルデータを作るのが面倒な方は、適宜利用いただきたい。
https://github.com/3tomcha/qiita


## ->detailとはつまり何をしているのか
まずはアクセスされる側の$bookの型を調べて見よう。dd()を使って出力するとBook型とわかる。（これは大方の予想通りであろう）
つまり->detailの正体を知るには、\App\Bookを調べるのが良さそうだと検討がつく。

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestController extends Controller
{
  public function test(){
    $book = \App\Book::find(1);
    dd($book);
    echo $book->detail->isbn;
  }
}
```

![スクリーンショット 2019-03-24 12.09.19.png](https://qiita-image-store.s3.amazonaws.com/0/247390/a332f66d-822d-7636-248a-91524ebe90f0.png)

さてApp\Bookをみてみると、直接関係ありそうな記述はない。よって、Illuminate\Database\Eloquent\Modelを辿ってみる。

```php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
  public function detail(){
    return $this->hasOne('\App\Bookdetail','id','bookdetail_id');
  }
}

```

Illuminate\Database\Eloquent\Model　をみると、気になる記述が見つかる。_____getはマジックメソッドと呼ばれるもので、アクセスできないプロパティにアクセスした時に呼ばれるものだ。これが働いているのではと検討がつく。実際にdump()を使ってみると、_____getは２回行われており、１回目はBookdetailオブジェクト、２回目は結果のisbnである"3333333333333"を返している。

```php

    /**
     * Dynamically retrieve attributes on the model.
     *
     * @param  string  $key
     * @return mixed
     */
    public function __get($key)
    {
        return $this->getAttribute($key);
    }
```
![スクリーンショット 2019-03-24 12.18.05.png](https://qiita-image-store.s3.amazonaws.com/0/247390/bf23215f-0b3a-9e06-f8e2-56bf4f5cdfe5.png)


次に__getで行われている　getAttribute()を辿って見よう。
Illuminate\Database\Eloquent\Concerns\HasAttrubute.php　に定義されている。
今回は、hasOneというリレーションを定義しているため、最後の　return $this->getRelationValue($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);
    }
```
getRelationValueは同じく　Illuminate\Database\Eloquent\Concerns\HasAttrubute.php　に定義されている。最下部にあるgetRelationshipFromMethod を辿って見よう。

```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);
        }
    }
```

getRelationshipFromMethodは同じく　Illuminate\Database\Eloquent\Concerns\HasAttrubute.php　に定義されている。
ここで、今まで辿ってきたことに確信が持てるはずだ。$relation = $this->$method();　の記述によって、なぜ->detailとアクセスしたのに、detail()が動いているのだろうという疑問が解決された。


```php
/**
     * Get a relationship value from a method.
     *
     * @param  string  $method
     * @return mixed
     *
     * @throws \LogicException
     */
    protected function getRelationshipFromMethod($method)
    {
        $relation = $this->$method();

        if (! $relation instanceof Relation) {
            throw new LogicException(sprintf(
                '%s::%s must return a relationship instance.', static::class, $method
            ));
        }

        return tap($relation->getResults(), function ($results) use ($method) {
            $this->setRelation($method, $results);
        });
    }
```

$methodと$relation　をそれぞれdumpしてみると、予想通り$methodはdetailであり、$relationにはBookモデルのdetail()が行われた結果であるHasOneオブジェクトが入っていることがわかった。

![スクリーンショット 2019-03-24 13.00.48.png](https://qiita-image-store.s3.amazonaws.com/0/247390/b93d3369-6162-56b5-a064-a6d6c0aef527.png)

```php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
  public function detail(){
    return $this->hasOne('\App\Bookdetail','id','bookdetail_id');
  }
}
```


# 結論
プロパティアクセスの形でしか書いていないのに、モデルのメソッドが動く理由は、コアの処理の途中で$method()として実行しているため。
<p style="font-size:5%">修正ご意見お待ちしています</p>