モデルを使う
完成したモデルを実際に使ってみましょう。
この章では以下の内容について学習できます。
- スナップショットの使い方
伝票処理
追加
2点の売上と入金をします。
try
{
$cust = Customer::find(1);
$inv = new Invoice;
$inv->customer()->associate($cust);
$inv->addSalesLine('APPLE1', 12);
$inv->addSalesLine('ORANGE1', 2);
$inv->addPaymentLine(4579, 'Cash');
$inv->saveWithTransaction();
// 残高の表示
echo $cust->name . '様の残高は \\' . $inv->amount->balance . ' です。' . PHP_EOL;
} catch(Exception $e) {
echo 'エラーが発生しました。詳細は「' . $e->message() . '」です。' . PHP_EOL;
}
Yamaha様の残高は \0 です。
読み取り
Invoice
レコードは明細の合計金額などの値を含んでいます。 Invoice
とInvoiceItem
は、トランザクションで書き込んだ一式を読み取ることで、矛盾の無いものとなります。
トランザクションで書き込んだ一式を読み取るには、以下のようにスナップショットを使って読み取る必要があります。
try
{
DB::beginSnapshot(); // CONSISTENT_READの開始
$inv = Invoice::find(1); // ここでInvoiceの読み取り
if ($inv === null) {
throw new Exception('伝票番号 1 は作成されていないか削除されました。');
}
$items = $inv->items; // ここでInvoiceItemの読み取り
$inv->endSnapshot();
echo $inv->id . 'の伝票の行数は '. $items->count() . ' 行です。' . PHP_EOL;
} catch(Exception $e) {
$inv->endSnapshot();
echo 'エラーが発生しました。詳細は「' . $e->message() . '」です。' . PHP_EOL;
}
どのようなシチュエーションで矛盾が発生するのか説明しましょう。以下の図を見てください。
伝票を明細付きで読み取ると、Invoice
レコードへのアクセスと、InvoiceItem
へのアクセスの2回の読み取りが発生します。このとき、別のユーザーが図のようなタイミングでその伝票を更新すると、Invoice
とInvoiceItem
でそれぞれ、変更前と変更後を読み取る可能性があります。
スナップショットを使った場合は、InnoDBのMVCC機能を使ってInvoice
とInvoiceItem
をスナップショットが開始された時点の一貫した内容で読み取ることができます。
スナップショットについてのより詳しい内容は、SDKの InnoDBのロックとその制御を参照してください。
Note: 読み取りで矛盾が起こる問題はTransactd PHP ORMに限った話ではありません。 SQLでのアクセスでもInvoice
とInvoiceItem
が個別のSQL文で読み取られる場合は同様です。 SQLにはbeginSnapshot
はないので、BEGIN
もしくはSTART TRANSACTION
を使用します。
更新
先ほどの伝票の2番目の行を削除して確定します。
try
{
$inv = Invoice::find(1);
if ($inv === null) {
throw new Exception('伝票番号 1 は作成されていないか削除されました。');
}
$inv->items->remove(1); // 2番目の行を削除
$inv->saveWithTransaction();
echo $inv->customer->name . '様の残高は \\' . $inv->amount->balance . 'です。' . PHP_EOL;
} catch(Exception $e) {
echo 'エラーが発生しました。詳細は「' . $e->message() . '」です。' . PHP_EOL;
}
Yamaha様の残高は \-691 です。
削除
伝票番号1を削除します。
try
{
$inv = Invoice::find(1);
if ($inv === null) {
throw new Exception('伝票番号 1 は作成されていないか削除されました。');
}
$inv->deleteWithTransaction();
$echo '伝票番号 1 を削除しました。' . PHP_EOL;
} catch(Exception $e) {
echo 'エラーが発生しました。詳細は「' . $e->message() . '」です。' . PHP_EOL;
}
伝票番号 1 を削除しました。
残高確認
この例では、customer_id = 3
の日付順で最後の伝票を探します。
keyValue
に1つ大きなcustomer_id
を指定し、direction
で後方から前方方向に検索することで最後の伝票を探します。
try
{
$customer_id = 3;
$invs = Invoice::index(1)->keyValue($customer_id + 1)
->where('customer_id', $customer_id)
->limit(1)->direction(Nstable::findBackForword)
->get();
$inv = count($invs) ? $invs[0] : null;
if ($inv === null) {
throw new Exception('顧客番号 1 はまだ取引がありません。');
}
echo $inv->customer->name . '様の残高は \\' . $inv->amount->balance . 'です。' . PHP_EOL;
} catch(Exception $e) {
echo 'エラーが発生しました。詳細は「' . $e->message() . '」です。' . PHP_EOL;
}
Yamaha様の残高は \0です。
得意先の1ヶ月分の取引を集計する
customer_id = 3
の月間売上金額を集計してみましょう。
try
{
$customer = Customer::find(3);
$invs = $customer->transactions('2016-12-01', '2016-12-31');
$total = 0;
if (count($invs) > 0) {
foreach($invs as $inv) {
$total += $inv->amount->sales;
}
}
echo $customer->name . '様の月間売上金額は \\' . $total . ' です。' . PHP_EOL;
} catch(Exception $e) {
echo 'エラーが発生しました。詳細は「' . $e->message() . '」です。' . PHP_EOL;
}
Yamaha様の月間売上金額は \4240 です。
1日の売上と回収金額を求める
2016-12-02の売上と回収金額を求めます。
DailySummary
があるので、たった1レコードのアクセスで求めることができます。
$date = '2016-12-02';
$summary = DailySummary::find($date);
if ($summary === null) {
echo $date . 'の取引はありません。' . PHP_EOL;
} else {
echo $date . ' 売上 : \\' . $summary->sales . ' 回収 : \\' . $summary->payment . PHP_EOL;
}
2016-12-02 売上 : \4240 回収 : \4579
特定の日付の伝票リスト
2016-12-02のすべての伝票をリストアップします。 DailySummary
のリレーションを使ってみます。
$date = '2016-12-02';
$summary = DailySummary::find($date);
if ($summary !== null) {
$invoices = $summary->invoices;
}
foreach($invoices as $invoice) {
// (省略)
}
指定した期間の売上合計を求める
2016-12の売上合計を求めてみます。 DailySummary
によって最大31レコードを読み取るだけで1ヶ月分がわかります。
$startDate = '2016-12-01';
$endDate = '2016-12-31';
$summaries = DailySummary::keyValue($startDate)->where('date', '<=', $endDate)->get();
$total = 0;
foreach($summaries as $summary) {
$total += $summary->sales;
}
echo $startDate . ' 〜 ' . $endDate . ' の合計売上金額は \\' . $total . ' です。' . PHP_EOL;
2016-12-02 〜 2016-12-31 の合計売上金額は \4240 です。
価格確認
APPLE1
の価格を調べます。
$code = 'APPLE1';
$product = Product::find('APPLE1');
echo $code . 'の価格は \\' . $product->price . ' です。' . PHP_EOL;
APPLE1の価格は \300 です。
在庫数確認
APPLE1
の在庫数を調べます。
$code = 'APPLE1';
$stock = Stock::find('APPLE1');
if ($stock === null) {
echo $code . 'の在庫はありません。' . PHP_EOL;
} else {
echo $code . 'の在庫数は ' . $stock->quantity . '個です。' . PHP_EOL;
}
APPLE1の在庫数は 108個です。
在庫一覧
在庫一覧表を表示します。
$stocks = Stock::all();
foreach($stocks as $stock) {
echo sprintf('%-15s : %8d', $stock->code, $stock->quantity) . PHP_EOL;
}
APPLE1 : 108
APPLE2 : 240
BIKE650 : 5
CAR123 : 1
ORANGE1 : 52
WATCH777 : 10
商品在庫一覧
商品在庫一覧を表示します。with
メソッドを使って高速に処理します。
function showProductList($products)
{
echo sprintf('%-15s %-15s %8s %8s', 'product_code', 'description',
'price', 'quantity') . PHP_EOL;
echo '---------------------------------------------------' . PHP_EOL;
foreach($products as $product) {
echo sprintf('%-15s %-15s %8d %8d',
$product->code,
$product->description,
$product->price,
($product->stock === null) ? 0 : $product->stock->quantity)
. PHP_EOL;
}
$products = Product::with('stock')->all();
showProductList($products);
product_code description price quantity
---------------------------------------------------
CAR123 CAR 123 5000000 1
BIKE650 BIKE 650 320000 5
WATCH777 WATCH 777 198000 10
APPLE1 APPLE FUJI 300 108
APPLE2 APPLE SHINANO 280 240
ORANGE1 ORANGE UNSYUU 320 52
JSONへの変換
伝票番号2をJSONにします。
$inv = Invoice:find(2);
$inv->items; // items のLazy load
$json = $inv->toJson();
echo $json;
フォーマットされたJSON
{
"id": 2,
"date": "2016-12-18",
"update_at": "2016-12-19 16:35:24.627492",
"amount": {
"oldBlance": null,
"sales": 198000,
"tax": 15840,
"payment": 10000,
"balance": 203840,
"className": "InvoiceAmount"
},
"className": "Invoice",
"customer_id": 3,
"note": "",
"items": {
"saveOprions": 6,
"className": "Transactd\\Collection",
"array": {
"0": {
"className": "InvoiceItem",
"invoice_id": 2,
"row": 1,
"line_type": 0,
"product_code": "WATCH777",
"product_description": "WATCH 777",
"price": 198000,
"quantity": 1,
"amount": 198000,
"tax": 15840,
"note": "",
"update_at": "2016-12-19 16:35:24.630626"
},
"1": {
"className": "InvoiceItem",
"invoice_id": 2,
"row": 2,
"line_type": 1,
"product_code": "PAYMENT",
"product_description": "Cash",
"price": 0,
"quantity": 0,
"amount": 10000,
"tax": 0,
"note": "",
"update_at": "2016-12-19 16:35:24.630626"
}
}
}
}
JSONからオブジェクトへの変換
上記で取得したJSON文字列からオブジェクトにし保存します。
$inv = Invoice::fromJson($json);
$inv->saveWithTransaction();