書き込みオペレーション

目次

新規モデルの生成

データベースへの挿入後、自動更新タイムスタンプは自動ではインスタンスに反映されません。 auto-incrementの値は反映されます。タイムスタンプを読み取りたい場合は、refreshメソッドを使用してください。

create

引数からモデルを生成します。 createメソッドは新しいインスタンスを生成し、データベースに挿入します。

$attr = ['id' => 0, 'name' => 'abc'];
$customer = Customer::create($attr);

firstOrCreate

与えられた引数でオブジェクトを検索して返します。もしオブジェクトを見つけられなかった場合、新しいインスタンスを生成し、データベースに挿入します。

$attr = ['id' => 1, 'name' => 'abc'];
// インデックス番号が指定されていない場合、primary keyが使用されます
$customer = Customer::firstOrCreate($attr);

firstOrNew

与えられた引数でオブジェクトを検索して返します。もしオブジェクトを見つけられなかった場合、新しいインスタンスを生成します(データベースには挿入しません)。

$attr = ['id' => 1, 'name' => 'abc'];
// インデックス番号が指定されていない場合、primary keyが使用されます
$customer = Customer::firstOrNew($attr);

引数のチェック

HTTPリクエストから受け取った値をそのままcreateメソッドの引数に使用すると、不正な値を渡してしまう原因になります。これは深刻なセキュリティホールになりかねません。

そのため、事前に決められた引数だけを使うようにする必要があります。ホワイトリストもしくはブラックリストを使用して、引数を事前に決めておくことができます。

ホワイトリストが指定された場合、ホワイトリストにない引数は無視されます。

class Customer extends Model
{
    protected static $fillable = ['id', 'name'];
}

ブラックリストが指定された場合、ブラックリストにある引数は無視されます。

class Customer extends Model
{
    protected static $guarded = ['note'];
}

保存と削除

保存

モデルをデータベースに保存します。自動更新タイムスタンプは自動ではインスタンスに反映されません。 auto-incrementの値は反映されます。

$attr = ['id' => 1, 'name' => 'abc'];
$customer = Customer::firstOrNew($attr);
$customer->save();

saveはまずprimary keyでのレコード更新を試みます。それに失敗した場合、レコードを挿入します。

belongs-toにオブジェクトを関連付ける

belongs-toにオブジェクトを関連付けるにはRelationオブジェクトのassociateメソッドを使用します。

$group = Group::find(1);
$customer = new Customer();
$customer->id = 0;
$customer->name = 'John';
$customer->group()->associate($group); // $group->idを$customer->group_idにコピー
$customer->save();

削除

データベースからモデルを削除します。モデルのインスタンスはunsetされません。

$customer = Customer::find(1);
$customer->delete();

削除にはprimary keyが使用されます。

関連オブジェクトも一緒に保存する

通常、saveもしくはdeleteメソッドは関連オブジェクトには影響しません。 saveもしくはdeleteメソッドの$optionModel::SAVE_WITH_RELATIONSを指定すると、関連オブジェクトも一緒に保存することができます。

Note:

saveでは、まずメインオブジェクトが保存され、次に関連オブジェクトが保存されます。

$customer = Customer::find(1);
$customer->name = 'Akio';
$customer->address->street = '3-1-5 igawazyo';
// 顧客と住所の両方を保存
$customer->save(Model::SAVE_WITH_RELATIONS);

コレクション

Collectionsaveメソッドを使って、モデルを一度に保存することができます。

$grp = new Group();
$grp->id = 0;
$grp->name = 'Tokyo';

$customer1 = new Customer();
$customer1->id = 0;
$customer1->name = 'John';
$grp->customers->add($customer1);

$customer2 = new Customer();
$customer2->id = 0;
$customer2->name = 'Mike';
$grp->customers->add($customer2);

// $grpと顧客すべてを一度に保存
$grp->save(Model::SAVE_WITH_RELATIONS);

多対多の関連オブジェクト

関連オブジェクトのみを保存するには、Relationオブジェクトのsaveメソッドを使用します。

$customer = Customer::find(1);
$tag = Tag::find(1);
$customer->tags()->save($tag);

コレクションを保存する場合、関連中間オブジェクトが自動的に中間テーブルに保存されます。

$customer = Customer::find(1);
$tag = Tag::find(1);
$tags = $customer->tags;
// 現在$tagsにはタグが2つある
$tags->add($tag);
DB::beginTransaction();
$tags->save(); // 3つのタグが保存される
DB::commit();

あなたが顧客のタグを読み取った後に、他のユーザーが同じ顧客にタグを追加した場合は、問題が起こる可能性があります。

Collection::saveメソッドは、関連条件に従って、コレクション内のアイテムを保存する前にデータベースからアイテムを削除することができます。この方法で完璧に保存することができます。

アイテムを削除する必要がない場合は、$saveOprionsSAVE_BEFOERE_NO_DELETEを指定します。これによりパフォーマンスが改善します。

$tags->saveOprions = Collection::SAVE_BEFOERE_NO_DELETE;
$tags->save();

更新の衝突チェック (Update Conflict Check)

更新のたびに自動更新されるタイムスタンプフィールドがある場合、 Transactd PHP ORMは複数ユーザーによる更新の衝突を検出することができます。

$customer = Customer::find(1);
if ($customer->setUpdateConflictCheck(true) === false) {
    echo 'このテーブルにはtimestampフィールドがありません。';
} else {
    // (省略)
    // もし他のユーザーが同じ顧客(id = 1)を更新していた場合は...
    try {
        $customer->delete(); // IOExceptionがスローされる
    }
    catch(Transactd\IOException $e) {
        echo $e->getCode() . ':' . $e->getMessage() . PHP_EOL;
    }
}

Note: MySQL/MariaDBの自動更新タイムスタンプは2016-12-06 11:49:26.466757のようにmicrosecondまで記録されます。ロックによってシリアライズされた2回以上の更新の時刻が同じになることはほぼないので、変更されたかどうかを検出することができます。

ただし、これは100%確実な方法ではありません。タイムスタンプはシステム時刻を元にしますが、システム時刻はユーザーやNTPなどによって変更されます。また、OSやマシンの分解能に依存します。

100%の確実性が必要な場合は、unsigned integerversionといったカラムを設けて、更新の度にインクリメントし、update_atの代わりにversionを比較するようにしてください。サンプルアプリケーションの変更競合の検出に例があります。

ServerCursor

モデルからその位置のサーバーカーソルを取得できます。

queryExecuterserverCursorではkeyValueを指定していましたが、モデルの場合それらはモデルそのものから取得できるため不要です。indexの指定は必要ですが、メソッドではなくserverCursorの最初のパラメータに加えられています。デフォルト値のnullが指定された場合は自動でprimary keyに設定されます。

プロトタイプと使用例は以下の通りです。

serverCursor($index = null, $op = QueryExecuter::SEEK_EQUAL, $lockBias = Transactd::LOCK_BIAS_DEFAULT, $forword = true)
$scr = $customer->serverCursor();
$customer->name = 'akio';
$scr->update($customer);

ServerCursorの詳細はQueryExecuterのServerCursorを参照してください。

トランザクション

トランザクションを使用するためには、使用するテーブルすべてを事前にオープンしておく必要があります。これらのテーブルをオープンするためにはprepareTableメソッドを使用します。

$grp = new Group();
$grp->id = 0;
$grp->name = 'Tokyo';

$customer1 = new Customer();
$customer1->id = 0;
$customer1->name = 'John';
$grp->customers->add($customer1);

$customer2 = new Customer();
$customer2->id = 0;
$customer2->name = 'Mike';
$grp->customers->add($customer2);

// テーブルの準備
Customer::prepareTable();
Group::prepareTable();

DB::beginTransaction();
$grp->save(Model::SAVE_WITH_RELATIONS);
DB::commit();

Note: トランザクションは1つのコネクション内でのみ有効です。

書き込みオペレーションのイベント

書き込みプロセスにおいて、書き込み前と書き込み後の2つのイベントをハンドルすることができます。イベントハンドラは以下の名前のstaticな関数です:

saveオペレーションはcreateupdateのイベントを発生させません。saveのイベントのみを発生させます。

イベントハンドラの定義

creatingcreatedを定義します。

class Customer extends Model
{
    public static function creating($customer)
    {
        echo '作成開始 customer id = ' . $customer->id . PHP_EOL;
        return true; // 処理続行
    }

    public static function created($customer)
    {
        echo '作成完了 customer id = ' . $customer->id . PHP_EOL;
    }
}

書き込み前のハンドラがfalseを返した場合、書き込み処理は中止されます。