僕がLaravelのEloquentに$fillableでなく$guardedを指定する理由

## はじめに
みなさん、Laravel捗っていますか？
僕が日々Laravelのコードを書いている上で困っている点の1つが、**Eloquent**の**$fillable**指定です。
基本的に**$guarded**を使う方が良いと思っているのでそれについて簡単にまとめます。
もし「**$fillable**を使うとこういう点で便利だよ！」というのがあればコメントで教えてくださると助かります。

## **$fillable** と **$guarded**
### **$fillable**
所謂ホワイトリストです。**$fillable**に指定したカラムのみ、**create()**や**fill()**、**update()**で値が代入されます。

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

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $fillable = [
        'name',
        'sex'
    ];
}
```

```php:app/Http/Controllers/UsersController.php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    public function update(Request $request, User $user)
    {
        // $fillableに指定したもの以外は入らない
        $user->update($request->all());

        return redirect()->route('user.edit', $user);
    }
}
```

### **$guarded**
所謂ブラックリストです。**$guarded**に指定したカラムのみ、**create()**や**fill()**、**update()**で値が代入されません。

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

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $guarded = [
        'id',
        'email',
        'password'
    ];
}
```

```php:app/Http/Controllers/UsersController.php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    public function update(Request $request, User $user)
    {
        // $guardedに指定していないものは全て入り得る
        $user->update($request->all());

        return redirect()->route('user.edit', $user);
    }
}
```

## **Pros & Cons**
### **$fillable**
#### **Pros**
* 指定したもの以外は入り得ない

#### **Cons**
* カラムが増えるたびに追加する必要がある
    * 特に横に長いテーブルだとツラい ← これで困ったので今回このPOSTを書きました

### **$guarded**
#### **Pros**
* 基本的に指定は増えない
    * **id** や **email**、 **password** などだけ

#### **Cons**
* 指定していないものは全て入り得る

## 上記を踏まえた上で **$guarded** を選んだ理由
そもそも、Modelに指定する**$fillable**や**$guarded**は、**Model・DB単位で予期せぬ代入が起こると困るもの**を書くべきだと考えました。というのも、複数代入されることを期待しているデータは、**モデルごとにあるのではなく**、**アクションごとにあり**ます。

**$fillable**に指定した全てのカラムが、一度のリクエストで代入されるとは限らず、例えばページごとに、複数代入されるカラムが違う可能性もあります。すると、**$fillableには入れているが、このアクションで代入されることは考えていないデータ**が悪意あるユーザーによって渡されるかもしれません。

しかし、**$guarded**でもこれは起こり得ます。

### ではどのようにするか
フィルタリングの役割分担をします。具体的に言うと、

* **Model・DB**単位で書き換えられると困るものや、個別の更新ロジックがあるものは**Model**の**$guarded**に指定する
    * **id**や**email**は**unique**などの制約がかかっているでしょうし、**password**も専用の変更アクションがあるでしょう
* アクションごとに書き換えられるべきもののみを、**FormRequest**側で**抽出**する
    * **$request->all()**を使わない

こうすることで、**指定していないものは全て入り得る**現象を回避できます。

### コード

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

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $guarded = [
        'id',
        'email',
        'password'
    ];
}
```

```php:app/Http/Controllers/UsersController.php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Requests\User\UpdateUserRequest;

class UsersController extends Controller
{
    public function update(UpdateUserRequest $request, User $user)
    {
        // UpdateUserRequest@userAttributesで必要なものだけ返す
        $user->update($request->userAttributes());

        return redirect()->route('user.edit', $user);
    }
}
```

```php:app/Http/Requests/User/UpdateUserRequest.php
<?php

use Illuminate\Foundation\Http\FormRequest;

class UpdateUserRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required|string',
            'sex' => 'required|in:1,2,3',
        ];
    }

    public function userAttributes()
    {
        return $this->only([
            'name',
            'sex'
        ]);
    }
}
```

こうすることで、いくつかの利点が得られます。

* 予期せぬ代入を防げる
* View側を見なくても、どのカラムが更新されるか分かる
* バリデーションなども**FormRequest**に書くので、バリデーションチェックする値と、代入される値が同じクラスに存在していて保守性が上がる

もちろん**FormRequest**側をこのようにした上で**$fillable**を指定しても構いませんが、先述した通り、**カラムが増えるたびに追加する必要がある**ことと、役割分担があいまいになってしまうため、**$guarded**を使うようにしました。

(もともと横長のテーブルが根本的原因であるのかもしれませんが……)

## まとめ
* **\$model->fill($request->all())**を止めよう
* **Model**と**FormRequest**のそれぞれで期待したデータ以外が入らないようにしよう
* **$fillable**でも問題はないが、テーブルが横長になるほどツラくなる

## 参考サイト
* [Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans](https://laravel.com/docs/5.5/eloquent#mass-assignment)
* [HTTP Requests - Laravel - The PHP Framework For Web Artisans](https://laravel.com/docs/5.5/requests)
* [Validation - Laravel - The PHP Framework For Web Artisans](https://laravel.com/docs/5.5/validation#form-request-validation)