Laravel4、テーブル操作の戻り値
タグ: Laravel4
今回のLaravel4のドキュメントは、不親切な部分があります。Laravel3の知識を前提にしているのかどうかわかりませんが、初めてLaravelを使用する人は、基本的な部分が抜けてしまいがちです。
今回は、そうした中から、テーブル操作の戻り値について、多少解説します。
Eloquentの戻り値
EloquentはORMです。テーブル上の1レコードをPHPのオブジェクト一つに対応させ、オブジェクトの操作や指定により、テーブル操作をやってしまおうというものです。
クエリーの結果を全部受け取るall()、クエリーの結果が複数の場合はget()、最初の一件だけを受け取るfirst()、レコードのID番号を指定し、一件受け取るfind()が基本となります。
find()とfirst()では、結果のレコード件数は0か1件です。0件の場合を例外で処理したい場合は、Laravel4で追加された、findOrFail()、firstOrFail()を使用します。
結果が一件の場合は、該当するレコードの値に$return->フィールド名で、アクセスできます。例えばAppleクラスはapplesテーブルに対するマッパークラスですが、テーブルのフィールドにnameという項目があれば、以下のようにアクセスできます。
$apple = Apple::find( 7 ); echo $apple->name; // もしくは echo $apple['name'];
この場合、IDが7のレコードが存在するならば、上記のようにアクセスできます。しかし、存在しない場合は、例外が発生します。
get()とall()は、条件により0件以上のレコードが存在します。foreachによりループ処理で処理するのが基本です。
$apples = Apple::all(); foreach ($apples as $apple) { echo $apple->name; }
get()、all()、first()、find()の戻り値は、
count()
を使用することで、件数を簡単にチェックできます。
$apples = Apple::all(); echo count($apple); // 全レコード件数表示 $apple = Apple::find( $id ); // フィールドにアクセスする前に、実用的には通常必ずチェックが必要 if (count($apple) != 1) App::abort(404, 'ページが見つかりません'); echo $apple->name; // もしくは例外を発生させ、グローバルに処理する // try catchしても良い $apple = Apple::findORFail( $id ); // レコードに一致しなければ、例外発生 echo $apple->name; // 通常、start/globle.phpの中などで例外処理を書く use Illuminate\Database\Eloquent\ModelNotFoundException; App::error(function(ModelNotFoundException $e) { return Response::make('見つかりません', 404); });
クエリービルダー
具体的にはDBクラスにより、テーブルにアクセスする方法です。
insert
insert()の戻り値は、挿入の成否を論理値で返されます。
update
update()の戻り値は、影響を受けたレコード件数です。
delete
delete()の戻り値は、影響を受けたレコード件数です。
Eloquent ORM
Eloquentは戻り値がバラエティーに富んでいますが、論理的に考えれば納得できるでしょう。
insert
挿入には2つの方法があります。ORMでは、一件ずつ挿入します。なぜなら、レコードとモデルは対応しているわけで、モデルオブジェクトは一度に一つだけしか、生成できないからです。
create()で挿入する場合、戻り値は新しく生成した、オブジェクトです。
$apple = Apple::create(array('name'=>'ふじ')); echo $apple->id; // 今回作成されたレコードのid echo $apple->name; // 'ふじ'
save()で挿入する場合、戻り値は成否フラグです。
$apple = new Apple(array('name' => 'ふじ')); $result = $apple->save(); if ( ! $result ) return View::make('error');
update
更新処理は、一度レコードを取得し、その内容を書き換え、save()する手順となります。save()を使用するので、先の挿入処理と同様に、成否フラグが返ってきます。
delete
削除はその方法により、戻り値が異なっています。
一番基本となるのは、一度レコードを取得し、削除する方法です。戻り値は成否フラグです。
$apple = Apple::find( $id ); $result = $apple->delete();
アクティブレコードと同様に、whereで条件を指定し、該当するレコードを複数削除できます。その場合、影響を受けたレコード数が戻ってきます。
$affectedRecode = Apple::where('name', '紅玉')->delete();
キー指定による複数レコード削除も可能です。この場合、返り値は戻って来ません。
Apple::destroy( 1, 2 );
このdesttoy()は削除したいレコードのid値を複数指定できます。テーブルに存在する場合は削除し、存在しない場合は何もしません。存在しなくても、エラーになりません。
検証テストコード
上記を検証するために、記述したテストコードです。
<?php class TabelOperationResultTest extends TestCase { public function setUp() { parent::setUp(); Artisan::call( 'migrate' ); // Artisan::call( 'migrate:refresh' ); $this->seed(); } public function test全レコード件数カウント() { $apple = Apple::all(); $this->assertSame( count( $apple ), 2 ); } public function testGetによるレコード件数カウント() { $apple = Apple::get(); $this->assertSame( count( $apple ), 2 ); } public function testFirstによるレコード件数カウント() { $apple = Apple::whereNotNull( 'name' )->first(); $this->assertSame( count( $apple ), 1 ); } public function testFindによるレコード件数カウント() { $apple = Apple::find( 1 ); $this->assertSame( count( $apple ), 1 ); } public function testAllによるレコード未取得時件数カウント() { DB::table( 'apples' )->delete(); // 全件削除 $apple = Apple::whereNull( 'name' )->get(); $this->assertSame( count( $apple ), 0 ); } public function testGetによるレコード未取得時件数カウント() { $apple = Apple::whereNull( 'name' )->get(); $this->assertSame( count( $apple ), 0 ); } public function testFirstによるレコード未取得時件数カウント() { $apple = Apple::whereNull( 'name' )->first(); $this->assertSame( count( $apple ), 0 ); } public function testFindによるレコード未取得時件数カウント() { $apple = Apple::find( 99999 ); $this->assertSame( count( $apple ), 0 ); } public function testクエリービルダーのinsertの戻り値が成否フラグ() { $result = DB::table( 'apples' ) ->insert( array( 'name' => '紅玉', 'weight' => 100 ), array( 'name' => 'BigApple', 'weight' => 150 ), array( 'name' => '小りんご', 'weight' => 20 ) ); $this->assertSame( $result, true ); } public function testEloquentによるレコード追加の戻り値が成否フラグ() { $apple = new Apple( array('name' => 'ゴールド', 'weight' => 130 ) ); $ret = $apple->save(); $this->assertSame( $ret, true ); } public function testクエリービルダーのupdateによる複数レコード更新戻り値が影響行数() { $affectedRecodeCount = DB::table( 'apples' ) ->update( array('name' => '紅玉', 'weight' => 100) ); $this->assertSame( $affectedRecodeCount, 2 ); } public function testクエリービルダーのupdateによる1レコード更新戻り値が影響行数() { $affectedRecodeCount = DB::table( 'apples' ) ->where('name', 'ふじ') // 一件抽出 ->update( array('name' => '紅玉', 'weight' => 100) ); $this->assertSame( $affectedRecodeCount, 1 ); } public function testクエリービルダーのupdateによるレコード更新0件時の戻り値が影響行数() { $affectedRecodeCount = DB::table( 'apples' ) ->whereNull('name') ->update( array('name' => '紅玉', 'weight' => 100) ); $this->assertSame( $affectedRecodeCount, 0 ); } public function testEloquentによるレコード更新の戻り値が成否フラグ() { $apple = Apple::find( 1 ); $apple->name = '椎名林檎'; $result = $apple->save(); $this->assertSame( $result, true ); } public function testクエリービルダーによる複数行削除の戻り値が影響行数() { $affectedRecodeCount = DB::table( 'apples' )->delete(); // 全件削除 $this->assertSame( $affectedRecodeCount, 2 ); } public function testクエリービルダーによる0件削除の戻り値が影響行数() { $affectedRecodeCount = DB::table( 'apples' ) ->whereNull( 'name' ) ->delete(); $this->assertSame( $affectedRecodeCount, 0 ); } public function testEloquentによる1件削除の戻り値が成否フラグ() { $apple = Apple::find(1); $result = $apple->delete(); $this->assertSame( $result, true ); } public function testEloquentによる複数削除の戻り値が影響行数() { $affectedRecodeCount = Apple::whereNotNull('name')->delete(); $this->assertSame( $affectedRecodeCount, 2 ); } public function testEloquentのキー指定削除による戻り値は無し() { $affectedRecodeCount = Apple::destroy( 1, 2 ); $this->assertNull( $affectedRecodeCount ); } public function testEloquentのキー指定削除による削除は存在するキーのみ削除する() { $affectedRecodeCount = Apple::destroy( 1, 9999 ); // 1件目のみ削除 $apples = Apple::all(); $this->assertSame(count($apples), 1); $this->assertEquals($apples->first()->name, '女医ゴールド'); // 2件目の名前 } }
データベースはSQLiteでメモリー上に配置しており、毎回自動的に消去されます。これを使用するのは、テストスピードあげるためです。
テスト本にはsetUP()の中で、
Artisan::call( 'migrate:refresh' );
を呼び出すサンプルになっています。最近試したところ、私の環境では、Artisan::call('migrate');
にする必要がありました。以前は確かに、DBはメモリ上にあっても、電源を切るまで、しばらく持続していました。そのため、マイグレーション用のテーブルは残っており、migrate:refreshで、良かったのです。今回は、マイグレーションテーブルも逐一消去されているようなので、毎回マイグレーションを実行するように変更しました。
app/config/testing/database.php
<?php return array( 'default' => 'sqlite', 'connections' => array( 'sqlite' => array( 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ) ) );
マイグレーションは一つだけです。名前は適当に。
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; class CreateApplesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('apples', function(Blueprint $table) { $table->increments('id'); $table->string('name'); $table->integer('weight')->unsigned(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('apples'); } }
シードクラスはapp/database/seed/ApplesTableSeeder.phpです。これをDatabaseSeederクラスから、呼び出しています。
<?php class ApplesTableSeeder extends Seeder { public function run() { //DB::table('apples')->delete(); $apples = array( array( 'name' => 'ふじ', 'weight' => 150 ), array( 'name' => '女医ゴールド', 'weight' => 200 ) ); DB::table( 'apples' )->insert( $apples ); } }
最後に、Appleモデルです。
<?php class Apple extends Eloquent { protected $guarded = array(); }