LaravelのModel周りの補完をできる限り完璧に行えるように解析してみる

## phpstormで定義元にジャンプしたときの参照元

---

### Illuminate\Database\Eloquent\Model

* $this->model::with();

---

### Illuminate\Database\Eloquent\Builder

* $this->model->where();
* $this->model->get();

---

### Illuminate\Database\Concerns\BuildsQueries

* $this->model->find();
* $this->model->first();

---

とりあえず部分的にメソッドを洗い出してみたが、登場人物としては３者洗出せた、はず。

---

各Modelは最終的に`Illuminate\Database\Eloquent\Model`を継承していることになる作りになる。

```php
<?php

namespace App\Http\Models;

use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}
```

---

継承しているのでとりあえずwiまで打てば、withと補完が出てくる。

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use App\Http\Models\User;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    public function __construct(User $user)
    {
        $relations = ['profile', 'Children']
        $user::with(relations)->where('age', '<=', 30 )->first();
    }
}
```

---

withは`\Illuminate\Database\Eloquent\Builder`を返り値としているので、冒頭の　洗い出しの通りそのままメソッドチェーンで->と打てば、findやfirstやgetなどがサジェストされるというわけだ。


```php
/**
     * Begin querying a model with eager loading.
     *
     * @param  array|string  $relations
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public static function with($relations)
    {
        return static::query()->with(
            is_string($relations) ? func_get_args() : $relations
        );
    }
```

---

しかし今のままでは、$user->whereのようにいきなりwhereなどは使えず、
withなどを中継しないといけない作りになっている。

---

`Illuminate\Database\Eloquent\Model`は各Modelが継承しているからpublicなメソッドにはアクセスができるが

`Illuminate\Database\Eloquent\Builder`で使える`where`や`get`や

`Illuminate\Database\Concerns\BuildsQueries`で使える`find`や`first`がまだ使えない状態となっている。

---

使えないといってもサジェスト的に使えないというだけで実際には使えるようになっている。
しかしサジェストしてくれるようになればコーディングも楽になるのは間違いない。

---

その前に、`Illuminate\Database\Eloquent\Builder`と`Illuminate\Database\Concerns\BuildsQueries`の関係だが、
前者はclassだが、後者はtraitだということからわかるように、後者は前者でuseされている。

---

従って、`Illuminate\Database\Eloquent\Builder`を使えるようにすれば`Illuminate\Database\Concerns\BuildsQueries`も使えるようになる。

```php
<?php

namespace Illuminate\Database\Eloquent;

use BadMethodCallException;
use Closure;
use Exception;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Concerns\BuildsQueries;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use ReflectionClass;
use ReflectionMethod;

/**
 * @property-read HigherOrderBuilderProxy $orWhere
 *
 * @mixin \Illuminate\Database\Query\Builder
 */
class Builder
{
    use BuildsQueries, Concerns\QueriesRelationships, ForwardsCalls;
```

---

ここまでわかったらあとはphpdocの@mixinを使う。
各ModelとIlluminate\Database\Eloquent\Modelの間に基底クラスを作ってやれば良い。

```php
<?php

namespace App\Core\Models\Bases;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;

/**
 * 全モデルで共通処理を担うモデルクラス。
 *
 * @property Carbon $created_at 作成日
 * @property Carbon $updated_at 更新日
 * @mixin Builder
 */
class BaseModel extends Model
{
    /**
     * @var array 書き換え禁止カラム
     */
    protected $guarded = ['id'];

    /**
     * BaseModel constructor.
     *
     * @param array $attributes
     */
    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
    }
}
```

```php
<?php

namespace App\Core\Models\Bases;

use Illuminate\Notifications\Notifiable;

/**
 * 対象モデルに継承することで、通知を行うことができる。
 */
class NotifiableModel extends BaseModel
{
    use Notifiable;
}
```

```php
<?php

namespace App\Core\Models\Bases;

use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Auth\Authenticatable;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Auth\MustVerifyEmail;
use Laravel\Passport\HasApiTokens;

/**
 * Illuminate\Foundation\Auth\User のラッパークラス。
 * Authを機能させたいモデルに継承することでテーブル名が連動してAuth認証が働きかける。
 *
 * この基底モデルクラスを継承したモデルクラスは「通知」も処理することができる。(Notifiable)
 * NotifiableトレイトはRoutesNotifications->notify()を扱える。
 *
 * このモデルクラスは
 * 「Auth認証」（Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail）
 * 「通知」（Notifiable）
 * 「API認証」（HasApiTokens）
 * 「ユーザーベースモデル」（BaseModel）
 * これらをラップした「サブ基底モデルクラス」である。
 */
class AuthModel extends NotifiableModel implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail, HasApiTokens;
}
```

```php
<?php

namespace App\Http\Models;

use App\Core\Models\Bases\AuthModel as Authenticatable;

class User extends Authenticatable
{
}
```

```php
<?php

namespace App\Http\Models;

use App\Core\Models\Bases\BaseModel;

class Car extends BaseModel
{
}
```

---

これで、withを中継せずともwhereもサジェストするようになった。

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use App\Http\Models\User;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    public function __construct(User $user)
    {
        $relations = ['profile', 'Children']
        $user::with(relations)->where('age', '<=', 30 )->first();
        $user->where('age', '<=', 30 )->first(); // これができるようになった。
    }
}
```

---

## Modelの継承関係図

![VP5D2i8m48NtEKL15uhq0YwashKYWg1kP4n63DP4aYcsnSFRTcp1MjszVEyZav_84pXARubOMeL948RFMR00Z_LI28vjiyioBZfyd-Uz_50KlNnbIPyrd99cJWUmDK_fmdsLq92MG9KJMqCE9FLfrkdYdnE3sQ17MeFt3-lKBq-qaPkjH_VrADDW81q2eJe2VVBqhjwSjsgAYKUZck7rIVZk4aXRCngIlQ8PYwYqoczv0G00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125469/7d58119e-fd4c-06af-39dc-436ffd329ed7.png)