在庫の増減
伝票の作成と保存で伝票を作成して保存できるようにはなりましたが、要件を満たしていない機能があるので、それらを順に追加していきましょう。
この章では以下の内容について学習できます。
save
やdelete
時のイベントハンドラの使い方- サーバーカーソルの使い方
save
、delete
メソッドのオーバーライド
増減の仕組み
在庫は、商品が売り上げられると減り、入荷や返品があると増えます。このサンプルでは入荷処理は行わないので、売上があると減り、返品や伝票削除があると増えるようにします。
商品ごとの処理なので、InvoiceItem
の保存と削除に連動させます。まずは処理を書いてみましょう。そして後で、InvoiceItem
の保存と削除のイベントハンドラから呼び出すようにします。
InvoiceItem
保存の補助クラスとしてInvoiceItemSaveHandler
クラスを作成します。
class InvoiceItemSaveHandler
{
}
サーバーカーソルによる安全で高速な処理
保存時に行が売上なら在庫を減らすメソッドonSaveRow
を作成します。このメソッドでは、すでにその商品の在庫レコードがあれば更新し、なければ新規作成して保存します。また、オブジェクトのキャッシュも更新します。
高速な検索と保存にはserverCursor
を使用します。
class InvoiceItemSaveHandler
{
public function onSaveRow(InvoiceItem $row)
{
if ($row->line_type === InvoiceItem::SALES) {
// サーバーカーソルの取得
$it = Stock::keyValue($row->product_code)->serverCursor();
// 読み取れたか確認する
if ($it->valid()) {
// その商品の在庫レコードがある場合は、在庫オブジェクトを取得
// このオブジェクトはトランザクションによってサーバーでロックされた最新のもの
$stock = $it->current();
$stock->quantity -= $row->quantity;
$it->update($stock);
} else {
// その商品の在庫レコードがない場合は、新規作成する
$stock = new Stock(['code' => $row->product_code]);
$stock->quantity = 0 - $row->quantity;
$it->insert($stock);
}
// 在庫オブジェクトのキャッシュを更新
$stock->updateCache();
}
}
}
Note: サーバーカーソルは非常に強力で簡単です。
トランザクション内であれば、サーバーカーソルのカレントレコードはロックされるため、最新の情報であることが保証されます。値の増減などはこの値を元に行えば安全に正しく更新できます。
トランザクション外の場合は、$lockBias
パラメータを指定しないかぎり、読み取りでのロックはされません。
同じように行が削除されたときのメソッドonDeleteRow
を作成します。
class InvoiceItemSaveHandler
{
// (省略)
public function onDeleteRow(InvoiceItem $row)
{
if ($row->line_type === InvoiceItem::SALES) {
$it = Stock::keyValue($row->product_code)->serverCursor();
// 伝票が削除される際には、在庫レコードは必ず存在するはずである。
// レコードが無い場合は即時例外をスローする。
$it->validOrFail();
// このオブジェクトはトランザクションによってサーバーでロックされた最新のもの
$stock = $it->current();
$stock->quantity += $row->quantity;
$it->update($stock);
$stock->updateCache();
}
}
}
ハンドラの組み込み
InvoiceItem
の保存と削除時のハンドラを定義して、先ほどのメソッドを呼び出すようにしましょう。
ハンドラの定義
ハンドラはsaving
とdeleting
を使用します。以下のメソッドを定義すると、保存と削除時に自動で呼び出されます。
class InvoiceItem extends Model
{
// (省略)
public static function deleting(InvoiceItem $row)
{
return true;
}
public static function saving(InvoiceItem $row)
{
return true;
}
}
処理の追加
これらのハンドラはスタティックなメソッドなので、InvoiceItemSaveHandler
のインスタンスを参照できるように、スタティックな変数を用意して事前にそれをセットしておきます。そしてそれぞれでonDeleteRow
、onSaveRow
を呼び出します。
class InvoiceItem extends Model
{
// (省略)
public static $handler = null;
public static function deleting(InvoiceItem $row)
{
self::$handler->onDeleteRow($row);
return true;
}
public static function saving(InvoiceItem $row)
{
self::$handler->onSaveRow($row);
return true;
}
}
Note: deleting
やsaving
のハンドラ内に直接在庫の増減を書いてもよいのですが、今回はInvoiceItem
とStock
の2つのクラスにまたがる特定のイベントでの処理なので、別のクラスにしました。
このようにすると、明細保存時にさらに別のクラスに関わる処理を追加したくなったときにも便利です。
仕上げ
仕上げにInvoice
のsave
とdelete
をオーバーライドして、 InvoiceItemSaveHandler
の生成とInvoiceItem::$handler
へのセットを行います。
class Invoice extends Model
{
// (省略)
public function save($options = 0, $forceInsert = false)
{
InvoiceItem::$handler = new InvoiceItemSaveHandler();
return parent::save($options, $forceInsert);
}
public function delete($options = null)
{
InvoiceItem::$handler = new InvoiceItemSaveHandler();
return parent::delete($options);
}
わざわざハンドラのインスタンスを保存の都度生成するのは、この後の処理でハンドラにメンバ変数を持つようにするためです。メンバ変数の初期化バグなどを防止するために都度生成にしています。
これで$invoice->saveWithTransaction()
を呼び出すと在庫も増減されるようになりました。
ソースコード
ここまでのソースコードはGitHub Gistからダウンロードできます。