Laravel v5.2.44利用の方はご注意を

## あらすじ

[こちら](http://qiita.com/s-age/items/cda69175ac8f8a128e21)を参照

## 調べてみた

- 年末の終業2時間前に発生したv5.2.45でのfirstOrNewの雑な対応
 - フィールドが`Model::$fillable`に含まれていない場合は例外を送出するでした
- で、$fillableって何？ということで[公式](https://readouble.com/laravel/5.2/ja/eloquent.html)を確認

> 最初に複数代入したいモデルの属性を指定してください。モデルの$fillableプロパティで指定できます。
> 複数代入する属性を指定したら、新しいレコードをデータベースに挿入するためにcreateが利用できます。

- と書かれています。

## ちょっとよくわからないので5.2のソースを見る

```php
    public static function create(array $attributes = [])
    {
        $model = new static($attributes);

        $model->save();

        return $model;
    }
```
- インスタンスを作って、saveしてます。
    - どこに$fillableの要素があるのか、これだけだとまるでわかりませんね。

## コンストラクタを覗いてみる

```php
    public function __construct(array $attributes = [])
    {
        $this->bootIfNotBooted();

        $this->syncOriginal();

        $this->fill($attributes);
    }
```
- それっぽいメソッド(Model::fill)がキックされているのでそちらを見てみる

```php
    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 be ignored.
            if ($this->isFillable($key)) {
                $this->setAttribute($key, $value);
            } elseif ($totallyGuarded) {
                throw new MassAssignmentException($key);
            }
        }

        return $this;
    }
```
- fillableであれば、`$attributes`にセットするということですね。
- もしくは`$attributes`に`$guarded`に含まれるフィールドが指定されている場合は例外を送出する

> $guardedプロパティーは複数代入したくない属性の配列です。

- わかりにくいですが、INSERTしたくないフィールドは`$guarded`に列挙するよろしということですね

## 続いて`isFillable`の内容を確認

```php
    public function isFillable($key)
    {
        if (static::$unguarded) {
            return true;
        }

        // If the key is in the "fillable" array, we can of course assume that it's
        // a fillable attribute. Otherwise, we will check the guarded array when
        // we need to determine if the attribute is black-listed on the model.
        if (in_array($key, $this->getFillable())) {
            return true;
        }

        if ($this->isGuarded($key)) {
            return false;
        }

        return empty($this->getFillable()) && ! Str::startsWith($key, '_');
```
- かいつまんで書くと
    - `Model::unguarded`がTRUEであれば、なんでも受け入れる
    - `Modell::$fillable`の配列に含まれる値であれば、受け入れる
    - `Model::fillable`が空であり、フィールド名がアンダースコアで開始されていなければ受け入れる
    - それ以外のフィールド指定は受け入れない
- ということになります

## Model::create の動作を簡単にまとめると

- `Model::create`が引数にとる配列に作用する
- `Model::$unguarded`がTRUEであれば、createに指定した配列のキー全てが有効なフィールドとみなされる
- `Model::fillable`が定義されていなければアンダースコア始まりの指定のみ無視される
- `Model::$fillable`が定義されている場合、一致しないフィールド指定は無視される
- `Model::$guarded`が定義されている場合、一致するフィールドは無視される
- つまり、INSERTする際に指定したくない(Auto Increment)等が対象

## Model::$fillable / Model::$guardedを簡単にまとめると

- 下記の`(col1, col2, col3, ..)`に該当するものが`$fillable`
- 逆に`(col1, col2, col3, ..)`に該当しないものが`$guarded`

```sql
INSERT INTO (col1, col2, col3, ..) VALUES (val1, val2, val3, ..);
```

## 改めて、前回の対応

- `Model::$fillable`に対象のフィールドが存在しなければ例外を送出する
    - あかん・・

## これはv5.2.45を採用するしかない

- ということでマイボスに相談する
- ボス「他の変更の確認が必要ですね」
- 当然なので、v5.2.45の変更を確認する
- [CHANGE LOG](https://github.com/laravel/framework/blob/5.3/CHANGELOG-5.2.md#v5245-2016-08-26)
- よかった、2つしかない！

## 差分に関して確認する

- #15018の更新が今回問題になった箇所
- もう1つは何かなと思い、調べて見る
- [プルリク](https://github.com/laravel/framework/pull/14994/files)を見てみる
- numericAggregateというメソッドを使わなくした

## numericAggregateの内容を確認する

```php
    public function numericAggregate($function, $columns = ['*'])
    {
        $result = $this->aggregate($function, $columns);

        if (! $result) {
            return 0;
        }

        if (is_int($result) || is_float($result)) {
            return $result;
        }

        if (strpos((string) $result, '.') === false) {
            return (int) $result;
        }

        return (float) $result;
    }
```
- int/floatに強制的に型変換する
    - ふむふむ、別に問題なさそうだな
- プロジェクト配下で当該メソッドを使っている箇所をgrep。対象は以下の4つ
    - max
    - min
    - avg
    - sum
- 数値しかないだろう。うんうん、うん？
- `max('updated_at')`とかありますね・・はい。。
- ということで、phpコマンドで実行してみる

```php
$ php -r 'echo (float) "2017-01-04 21:35:40";'
2017
```
- はい、死んだ・・
- 実際にDBに入っている値はというと
    - `0000-00-00 00:00:00`
- はい、死んでますね・・

## 結論

- v5.2.44を使い続けると下記のリスクがあります
    - firstOr*系のメソッドを利用するために不正な`Model::$fillable`の指定が必要になる
    - max/min等のRDBMSの関数のラッパーメソッドを利用する際に数値型であるかを考慮する必要がある

## まとめ

- 下記を叩いて、`v5.2.44`の場合はとっととアップデートしましょう

```
$ grep VERSION /path/to/your/project/vendor/src/Illuminate/Foundation/Application.php | grep const
    const VERSION = '5.2.45';
```