あらすじ
こちらを参照
調べてみた
- 年末の終業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の関数のラッパーメソッドを利用する際に数値型であるかを考慮する必要がある
- firstOr*系のメソッドを利用するために不正な
まとめ
- 下記を叩いて、
v5.2.44
の場合はとっととアップデートしましょう
$ grep VERSION /path/to/your/project/vendor/src/Illuminate/Foundation/Application.php | grep const
const VERSION = '5.2.45';