LaravelでクエリビルダーやEloquentを使ったupdateの戻り値の違い
背景
Laravelアプリケーションでレコードをupdateするやり方が色々あり、戻り値の違いについて調べた。
結論
表にまとめた。
書き方 | 戻り値の型 | 更新対象がない時の戻り値 |
---|---|---|
\DB::table(‘samples’)->where($where)->update($values) | int(件数) | 0 |
\App\Sample::where($where)->update($values) | int(件数) | 0 |
\App\Sample::where($where)->first()->update($values) | bool(成否) | 実行不可 |
\App\Sample::find($id)->update($values) | bool(成否) | 実行不可 |
詳細
それぞれのupdate
呼び出し結果をvar_dump
してみる。
<?php // 初期データ \DB::table('samples')->insert([ 'id' => 1, 'name' => 'alice' ]); echo PHP_EOL; echo "レコードと差分がない値で更新する場合" . PHP_EOL; $exists_where = [ 'id' => 1 ]; $same_values = [ 'name' => 'alice' ]; // クエリビルダーで更新(変更なし) var_dump(\DB::table('samples')->where($exists_where)->update($same_values)); // Modelのwhere->updateで更新(変更なし) var_dump(\App\Sample::where($exists_where)->update($same_values)); // Modelのwhere->first->updateで更新(変更なし) -> bool(true) var_dump(\App\Sample::where($exists_where)->first()->update($same_values)); // Modelのfind->updateで更新(変更なし) -> bool(true) var_dump(\App\Sample::find($exists_where['id'])->update($same_values)); echo "レコードが存在しない場合" . PHP_EOL; $not_exists_where = [ 'id' => 999 ]; // クエリビルダーで更新(変更なし) var_dump(\DB::table('samples')->where($not_exists_where)->update($same_values)); // Modelのwhere->updateで更新(変更なし) var_dump(\App\Sample::where($not_exists_where)->update($same_values)); // Modelのfirstおよびfindはnullが返るので実行不可
実行すると冒頭の表の通り、int(件数)を返すものとbool(成否)を返すものがある。
何故違うかは、それぞれの実行クラスを見ると分かる。
<?php var_dump(get_class(\DB::table('samples')->where($exists_where))); // => string(33) "Illuminate\Database\Query\Builder" var_dump(get_class(\App\Sample::where($exists_where))); // => string(36) "Illuminate\Database\Eloquent\Builder" var_dump(get_class(\App\Sample::where($exists_where)->first())); // => string(10) "App\Sample" var_dump(get_class(\App\Sample::find($exists_where['id']))); // => string(10) "App\Sample"
つまりDB::table
経由だろうとモデル経由だろうと、firstなりfindなりで一旦モデルを取得したかどうかで処理が異なる。
なおIlluminate\Database\Eloquent\Builder
は内部でIlluminate\Database\Query\Builder
に委譲している。
- 余談1: 試していないが
\App\Sample::where($where)->update($values)
の場合は各種フックが走らないはず - 余談2:
\DB::table('samples')->where($where)->first()
はモデルではなくstdClassが返る
Illuminate\Database\Query\Builder
このクラスのupdate
は最終的にPDOStatement::rowCountを返す。
なので常に更新件数が返る(はず)。
<?php /** * Run an SQL statement and get the number of rows affected. * * @param string $query * @param array $bindings * @return int */ public function affectingStatement($query, $bindings = []) { return $this->run($query, $bindings, function ($query, $bindings) { if ($this->pretending()) { return 0; } // For update or delete statements, we want to get the number of rows affected // by the statement and return that back to the developer. We'll first need // to execute the statement and then we'll use PDO to fetch the affected. $statement = $this->getPdo()->prepare($query); $this->bindValues($statement, $this->prepareBindings($bindings)); $statement->execute(); return $statement->rowCount(); }); }
Illuminate\Database\Eloquent\Model
App\Sample
が継承しているModelのupdate
は以下のようになっている。
<?php /** * Update the model in the database. * * @param array $attributes * @param array $options * @return bool */ public function update(array $attributes = [], array $options = []) { if (! $this->exists) { return false; } return $this->fill($attributes)->save($options); }
save
はEloquentのフックの処理を色々したりするが、最終的には必ずboolを返す。
件数を返すことはないように見える。
なお$this->exists
で見ているexists
はレコードをチェックしているわけではなく、論理上のフラグである。
モデルのコンストラクタで指定するか、insertに成功したりするとフラグが立つ。