Laravel4、テーブル操作の戻り値

Tags : 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();
}