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という項目があれば、以下のようにアクセスできます。

  1. $apple = Apple::find( 7 );
  2. echo $apple->name;
  3. // もしくは
  4. echo $apple['name'];

この場合、IDが7のレコードが存在するならば、上記のようにアクセスできます。しかし、存在しない場合は、例外が発生します。

get()とall()は、条件により0件以上のレコードが存在します。foreachによりループ処理で処理するのが基本です。

  1. $apples = Apple::all();
  2. foreach ($apples as $apple)
  3. {
  4. echo $apple->name;
  5. }

get()、all()、first()、find()の戻り値は、count()を使用することで、件数を簡単にチェックできます。

  1. $apples = Apple::all();
  2. echo count($apple); // 全レコード件数表示
  3.  
  4. $apple = Apple::find( $id );
  5. // フィールドにアクセスする前に、実用的には通常必ずチェックが必要
  6. if (count($apple) != 1) App::abort(404, 'ページが見つかりません');
  7. echo $apple->name;
  8.  
  9. // もしくは例外を発生させ、グローバルに処理する
  10. // try catchしても良い
  11. $apple = Apple::findORFail( $id ); // レコードに一致しなければ、例外発生
  12. echo $apple->name;
  13.  
  14.  
  15. // 通常、start/globle.phpの中などで例外処理を書く
  16. use Illuminate\Database\Eloquent\ModelNotFoundException;
  17. App::error(function(ModelNotFoundException $e)
  18. {
  19. return Response::make('見つかりません', 404);
  20. });

クエリービルダー

具体的にはDBクラスにより、テーブルにアクセスする方法です。

insert

insert()の戻り値は、挿入の成否を論理値で返されます。

update

update()の戻り値は、影響を受けたレコード件数です。

delete

delete()の戻り値は、影響を受けたレコード件数です。

Eloquent ORM

Eloquentは戻り値がバラエティーに富んでいますが、論理的に考えれば納得できるでしょう。

insert

挿入には2つの方法があります。ORMでは、一件ずつ挿入します。なぜなら、レコードとモデルは対応しているわけで、モデルオブジェクトは一度に一つだけしか、生成できないからです。

create()で挿入する場合、戻り値は新しく生成した、オブジェクトです。

  1. $apple = Apple::create(array('name'=>'ふじ'));
  2. echo $apple->id; // 今回作成されたレコードのid
  3. echo $apple->name; // 'ふじ'

save()で挿入する場合、戻り値は成否フラグです。

  1. $apple = new Apple(array('name' => 'ふじ'));
  2. $result = $apple->save();
  3. if ( ! $result ) return View::make('error');
update

更新処理は、一度レコードを取得し、その内容を書き換え、save()する手順となります。save()を使用するので、先の挿入処理と同様に、成否フラグが返ってきます。

delete

削除はその方法により、戻り値が異なっています。

一番基本となるのは、一度レコードを取得し、削除する方法です。戻り値は成否フラグです。

  1. $apple = Apple::find( $id );
  2. $result = $apple->delete();

アクティブレコードと同様に、whereで条件を指定し、該当するレコードを複数削除できます。その場合、影響を受けたレコード数が戻ってきます。

  1. $affectedRecode = Apple::where('name', '紅玉')->delete();

キー指定による複数レコード削除も可能です。この場合、返り値は戻って来ません。

  1. Apple::destroy( 1, 2 );

このdesttoy()は削除したいレコードのid値を複数指定できます。テーブルに存在する場合は削除し、存在しない場合は何もしません。存在しなくても、エラーになりません。

検証テストコード

上記を検証するために、記述したテストコードです。

  1. <?php
  2.  
  3. class TabelOperationResultTest extends TestCase
  4. {
  5.  
  6. public function setUp()
  7. {
  8. parent::setUp();
  9.  
  10. Artisan::call( 'migrate' );
  11. // Artisan::call( 'migrate:refresh' );
  12. $this->seed();
  13. }
  14.  
  15. public function test全レコード件数カウント()
  16. {
  17. $apple = Apple::all();
  18. $this->assertSame( count( $apple ), 2 );
  19. }
  20.  
  21. public function testGetによるレコード件数カウント()
  22. {
  23. $apple = Apple::get();
  24. $this->assertSame( count( $apple ), 2 );
  25. }
  26.  
  27. public function testFirstによるレコード件数カウント()
  28. {
  29. $apple = Apple::whereNotNull( 'name' )->first();
  30. $this->assertSame( count( $apple ), 1 );
  31. }
  32.  
  33. public function testFindによるレコード件数カウント()
  34. {
  35. $apple = Apple::find( 1 );
  36. $this->assertSame( count( $apple ), 1 );
  37. }
  38.  
  39. public function testAllによるレコード未取得時件数カウント()
  40. {
  41. DB::table( 'apples' )->delete(); // 全件削除
  42. $apple = Apple::whereNull( 'name' )->get();
  43. $this->assertSame( count( $apple ), 0 );
  44. }
  45.  
  46. public function testGetによるレコード未取得時件数カウント()
  47. {
  48. $apple = Apple::whereNull( 'name' )->get();
  49. $this->assertSame( count( $apple ), 0 );
  50. }
  51.  
  52. public function testFirstによるレコード未取得時件数カウント()
  53. {
  54. $apple = Apple::whereNull( 'name' )->first();
  55. $this->assertSame( count( $apple ), 0 );
  56. }
  57.  
  58. public function testFindによるレコード未取得時件数カウント()
  59. {
  60. $apple = Apple::find( 99999 );
  61. $this->assertSame( count( $apple ), 0 );
  62. }
  63.  
  64.  
  65.  
  66.  
  67. public function testクエリービルダーのinsertの戻り値が成否フラグ()
  68. {
  69. $result = DB::table( 'apples' )
  70. ->insert(
  71. array( 'name' => '紅玉', 'weight' => 100 ),
  72. array( 'name' => 'BigApple', 'weight' => 150 ),
  73. array( 'name' => '小りんご', 'weight' => 20 )
  74. );
  75. $this->assertSame( $result, true );
  76. }
  77.  
  78. public function testEloquentによるレコード追加の戻り値が成否フラグ()
  79. {
  80. $apple = new Apple( array('name' => 'ゴールド', 'weight' => 130 ) );
  81. $ret = $apple->save();
  82. $this->assertSame( $ret, true );
  83. }
  84.  
  85. public function testクエリービルダーのupdateによる複数レコード更新戻り値が影響行数()
  86. {
  87. $affectedRecodeCount = DB::table( 'apples' )
  88. ->update(
  89. array('name' => '紅玉', 'weight' => 100)
  90. );
  91. $this->assertSame( $affectedRecodeCount, 2 );
  92. }
  93.  
  94. public function testクエリービルダーのupdateによる1レコード更新戻り値が影響行数()
  95. {
  96. $affectedRecodeCount = DB::table( 'apples' )
  97. ->where('name', 'ふじ') // 一件抽出
  98. ->update(
  99. array('name' => '紅玉', 'weight' => 100)
  100. );
  101. $this->assertSame( $affectedRecodeCount, 1 );
  102. }
  103.  
  104. public function testクエリービルダーのupdateによるレコード更新0件時の戻り値が影響行数()
  105. {
  106. $affectedRecodeCount = DB::table( 'apples' )
  107. ->whereNull('name')
  108. ->update(
  109. array('name' => '紅玉', 'weight' => 100)
  110. );
  111. $this->assertSame( $affectedRecodeCount, 0 );
  112. }
  113.  
  114. public function testEloquentによるレコード更新の戻り値が成否フラグ()
  115. {
  116. $apple = Apple::find( 1 );
  117. $apple->name = '椎名林檎';
  118. $result = $apple->save();
  119. $this->assertSame( $result, true );
  120. }
  121.  
  122. public function testクエリービルダーによる複数行削除の戻り値が影響行数()
  123. {
  124. $affectedRecodeCount = DB::table( 'apples' )->delete(); // 全件削除
  125. $this->assertSame( $affectedRecodeCount, 2 );
  126. }
  127.  
  128. public function testクエリービルダーによる0件削除の戻り値が影響行数()
  129. {
  130. $affectedRecodeCount = DB::table( 'apples' )
  131. ->whereNull( 'name' )
  132. ->delete();
  133. $this->assertSame( $affectedRecodeCount, 0 );
  134. }
  135.  
  136. public function testEloquentによる1件削除の戻り値が成否フラグ()
  137. {
  138. $apple = Apple::find(1);
  139. $result = $apple->delete();
  140. $this->assertSame( $result, true );
  141. }
  142.  
  143. public function testEloquentによる複数削除の戻り値が影響行数()
  144. {
  145. $affectedRecodeCount = Apple::whereNotNull('name')->delete();
  146. $this->assertSame( $affectedRecodeCount, 2 );
  147. }
  148.  
  149. public function testEloquentのキー指定削除による戻り値は無し()
  150. {
  151. $affectedRecodeCount = Apple::destroy( 1, 2 );
  152. $this->assertNull( $affectedRecodeCount );
  153. }
  154.  
  155. public function testEloquentのキー指定削除による削除は存在するキーのみ削除する()
  156. {
  157. $affectedRecodeCount = Apple::destroy( 1, 9999 ); // 1件目のみ削除
  158. $apples = Apple::all();
  159. $this->assertSame(count($apples), 1);
  160. $this->assertEquals($apples->first()->name, '女医ゴールド'); // 2件目の名前
  161. }
  162.  
  163. }

データベースはSQLiteでメモリー上に配置しており、毎回自動的に消去されます。これを使用するのは、テストスピードあげるためです。

テスト本にはsetUP()の中で、Artisan::call( 'migrate:refresh' );を呼び出すサンプルになっています。最近試したところ、私の環境では、Artisan::call('migrate');にする必要がありました。以前は確かに、DBはメモリ上にあっても、電源を切るまで、しばらく持続していました。そのため、マイグレーション用のテーブルは残っており、migrate:refreshで、良かったのです。今回は、マイグレーションテーブルも逐一消去されているようなので、毎回マイグレーションを実行するように変更しました。

app/config/testing/database.php

  1. <?php
  2.  
  3. return array(
  4. 'default' => 'sqlite',
  5. 'connections' => array(
  6. 'sqlite' => array(
  7. 'driver' => 'sqlite',
  8. 'database' => ':memory:',
  9. 'prefix' => '',
  10. )
  11. )
  12. );

マイグレーションは一つだけです。名前は適当に。

  1. <?php
  2.  
  3. use Illuminate\Database\Migrations\Migration;
  4. use Illuminate\Database\Schema\Blueprint;
  5.  
  6. class CreateApplesTable extends Migration {
  7.  
  8. /**
  9. * Run the migrations.
  10. *
  11. * @return void
  12. */
  13. public function up()
  14. {
  15. Schema::create('apples', function(Blueprint $table) {
  16. $table->increments('id');
  17. $table->string('name');
  18. $table->integer('weight')->unsigned();
  19. $table->timestamps();
  20. });
  21. }
  22.  
  23. /**
  24. * Reverse the migrations.
  25. *
  26. * @return void
  27. */
  28. public function down()
  29. {
  30. Schema::drop('apples');
  31. }
  32.  
  33. }

シードクラスはapp/database/seed/ApplesTableSeeder.phpです。これをDatabaseSeederクラスから、呼び出しています。

  1. <?php
  2.  
  3. class ApplesTableSeeder extends Seeder
  4. {
  5.  
  6. public function run()
  7. {
  8. //DB::table('apples')->delete();
  9.  
  10. $apples = array(
  11. array( 'name' => 'ふじ', 'weight' => 150 ),
  12. array( 'name' => '女医ゴールド', 'weight' => 200 )
  13. );
  14.  
  15. DB::table( 'apples' )->insert( $apples );
  16. }
  17.  
  18. }

最後に、Appleモデルです。

  1. <?php
  2.  
  3. class Apple extends Eloquent {
  4. protected $guarded = array();
  5. }