Laravelのfillableって何で書くの？

# はじめに
何となくで使っている`fillable`。

`fill()`を使う時に書く認識でしたが、かなり曖昧な理解だなと思ったので改めてどういう時に定義するのかを調べていきたいと思います。

# なぜfillableを設定する必要があるのか？
結論から言うと、`fillable`を設定する理由は**複数代入の脆弱性から守る**ためである。
<br/>

例えば、usersテーブルに以下の5つのカラムがあったとする。
- `id`
- `name`
- `email`
- `password`
- `is_admin`

フォームには`name`、`email`、`password`の3つが入力できるようになっていて、保存処理として以下のようなコードを書いたとする。

もしリクエストの中に`is_admin: true`のようなデータが入っていると、悪意あるユーザーが管理者になってしまう危険性がある。
```php
public function store(Request $request)
{
    $data = $request->all();
    User::create($data);
}
```

ここで、`fillable`に以下のように設定をしておくと、予期せぬデータが登録されることを防げる。
```php
class User extends Model
{
    protected $fillable = ['name', 'email', 'password'];
}
```

# 具体的に見てみる
とはいってもどうやってそれが実現しているのかがイメージがつかないので、クエリビルダの`create()`を例に処理を追いつつ見ていく。

vendorの中身を探りに行くと、`vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php`に`create()`が定義されている。

ここでの処理はモデルインスタンスを作成・保存をし、インスタンスを返している。

```php:vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php
/*
 * Save a new model and return the instance.
 *
 * @param  array  $attributes
 * @return \Illuminate\Database\Eloquent\Model|$this
 */
public function create(array $attributes = [])
{
    return tap($this->newModelInstance($attributes), function ($instance) {
        $instance->save();
    });
}
```

`newModelInstance()`の定義を見てみると以下のようになっている。

ここでの処理は指定された属性を持つ新しいモデルインスタンスを作成し、そのインスタンスにクエリビルダが使用しているデータベース接続を設定して返している。

```php:vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php
/*
 * Create a new instance of the model being queried.
 *
 * @param  array  $attributes
 * @return \Illuminate\Database\Eloquent\Model
 */
public function newModelInstance($attributes = [])
{
    return $this->model->newInstance($attributes)->setConnection(
        $this->query->getConnection()->getName()
    );
}
```

`newInstance()`の定義を見てみると以下のようになっている。

```php:vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/*
 * Create a new Eloquent model instance.
 *
 * @param  array  $attributes
 * @return void
 */
public function __construct(array $attributes = [])
{
    $this->bootIfNotBooted();

    $this->syncOriginal();

    $this->fill($attributes);
}

~ 省略 ~

/*
 * Create a new instance of the given model.
 *
 * @param  array  $attributes
 * @param  bool  $exists
 * @return static
 */
public function newInstance($attributes = [], $exists = false)
{
    // This method just provides a convenient way for us to generate fresh model
    // instances of this current model. It is particularly useful during the
    // hydration of new objects via the Eloquent query builder instances.
    $model = new static((array) $attributes);

    $model->exists = $exists;

    $model->setConnection(
        $this->getConnectionName()
    );

    return $model;
}
```

このメソッド内にある`new static((array) $attributes);`で`fill()`が呼ばれている。

`fill()`内で呼ばれている`$this->fillableFromArray($attributes)`によって、`fillable`に書かれた属性のみを抽出して返しているため、先述の脆弱性対策をすることが可能となる。

```php:vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/*
 * Fill the model with an array of attributes.
 *
 * @param  array  $attributes
 * @return $this
 *
 * @throws \Illuminate\Database\Eloquent\MassAssignmentException
 */
public function fill(array $attributes)
{
    $totallyGuarded = $this->totallyGuarded();

    foreach ($this->fillableFromArray($attributes) as $key => $value) {
        $key = $this->removeTableFromKey($key);

        // The developers may choose to place some attributes in the "fillable" array
        // which means only those attributes may be set through mass assignment to
        // the model, and all others will just get ignored for security reasons.
        if ($this->isFillable($key)) {
            $this->setAttribute($key, $value);
        } elseif ($totallyGuarded) {
            throw new MassAssignmentException($key);
        }
    }

    return $this;
}
```

```php:vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php
/*
 * Get the fillable attributes of a given array.
 *
 * @param  array  $attributes
 * @return array
 */
protected function fillableFromArray(array $attributes)
{
    if (count($this->getFillable()) > 0 && ! static::$unguarded) {
        return array_intersect_key($attributes, array_flip($this->getFillable()));
    }

    return $attributes;
}
```

# まとめ
`fillable`は悪意ある複数代入から守るために設定する必要がある。