LoginSignup
3
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-04

あらすじ

こちらを参照

調べてみた

  • 年末の終業2時間前に発生したv5.2.45でのfirstOrNewの雑な対応
    • フィールドがModel::$fillableに含まれていない場合は例外を送出するでした
  • で、$fillableって何?ということで公式を確認

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

  • と書かれています。

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

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

        $model->save();

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

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

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

        $this->syncOriginal();

        $this->fill($attributes);
    }
  • それっぽいメソッド(Model::fill)がキックされているのでそちらを見てみる
    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の内容を確認

    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
INSERT INTO (col1, col2, col3, ..) VALUES (val1, val2, val3, ..);

改めて、前回の対応

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

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

  • ということでマイボスに相談する
  • ボス「他の変更の確認が必要ですね」
  • 当然なので、v5.2.45の変更を確認する
  • CHANGE LOG
  • よかった、2つしかない!

差分に関して確認する

  • #15018の更新が今回問題になった箇所
  • もう1つは何かなと思い、調べて見る
  • プルリクを見てみる
  • numericAggregateというメソッドを使わなくした

numericAggregateの内容を確認する

    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 -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';
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3