Example with Transactd PHP ORM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
require_once(__DIR__ . "/vendor/autoload.php"); | |
use BizStation\Transactd\Transactd; | |
use BizStation\Transactd\Database; | |
use BizStation\Transactd\Tabledef; | |
use BizStation\Transactd\Benchmark; | |
use BizStation\Transactd\Nstable; | |
use Transactd\QueryExecuter; | |
use Transactd\IOException; | |
use Transactd\Model; | |
use Transactd\Collection; | |
use Transactd\JsonSerializable; | |
class_alias('Transactd\DatabaseManager', 'DB'); | |
CONST masterUri = 'tdap://localhost/salesdb'; | |
CONST slaveUri = 'tdap://localhost/salesdb'; | |
class Customer extends Model | |
{ | |
protected static $guarded = ['id']; | |
public $id = 0; | |
public function invoices() | |
{ | |
return $this->hasMany('Invoice'); | |
} | |
public function transactions($startDate, $endDate) | |
{ | |
return Invoice::index(1)->keyValue($this->id, $startDate) | |
->where('customer_id', $this->id)->where('date', '<=', $endDate)->get(); | |
} | |
} | |
class Product extends Model | |
{ | |
protected static $guarded = ['id']; | |
public function stock() | |
{ | |
return $this->hasOne('Stock', 0, 'code'); | |
} | |
} | |
class Stock extends Model | |
{ | |
protected static $guarded = []; | |
public function product() | |
{ | |
return $this->belongsTo('Product', 'code', 1); | |
} | |
} | |
Trait AmountTrait | |
{ | |
public $sales = 0; | |
public $tax = 0; | |
public $payment = 0; | |
public $balance = 0; | |
public function reset() | |
{ | |
$this->sales = 0; | |
$this->tax = 0; | |
$this->payment = 0; | |
} | |
public function sum($amount) | |
{ | |
$this->sales += $amount->sales; | |
$this->tax += $amount->tax; | |
$this->payment += $amount->payment; | |
} | |
public function total() | |
{ | |
return $this->sales + $this->tax - $this->payment; | |
} | |
} | |
class InvoiceAmount | |
{ | |
use AmountTrait; | |
use JsonSerializable; | |
private $oldBlance = null; | |
public function __construct() | |
{ | |
$this->className = get_class($this); | |
} | |
public function difference() | |
{ | |
return $this->balance - $this->oldBlance; | |
} | |
public function calc(Collection $rows, $base) | |
{ | |
if ($this->oldBlance === null) { | |
$this->oldBlance = $this->balance; | |
} | |
$this->reset(); | |
$ar = $rows->getNativeArray(); | |
foreach($ar as $row) { | |
$this->increment($row); | |
} | |
$this->balance = $base + $this->total(); | |
} | |
public function increment(InvoiceItem $row) | |
{ | |
if ($row->line_type === InvoiceItem::SALES) { | |
$this->sales += $row->amount; | |
$this->tax += $row->tax; | |
} elseif ($row->line_type === InvoiceItem::PAYMENT) { | |
$this->payment += $row->amount; | |
} | |
} | |
public function decrement(InvoiceItem $row) | |
{ | |
if ($row->line_type === InvoiceItem::SALES) { | |
$this->sales -= $row->amount; | |
$this->tax -= $row->tax; | |
} elseif ($row->line_type === InvoiceItem::PAYMENT) { | |
$this->payment -= $row->amount; | |
} | |
} | |
} | |
class InvoiceItemSaveHandler | |
{ | |
private $date; | |
public function __construct($date) | |
{ | |
$this->date = $date; | |
} | |
public function onStart() | |
{ | |
$this->amount = new InvoiceAmount; | |
} | |
public function onEnd() | |
{ | |
$it = DailySummary::index(0)->keyValue($this->date)->serverCursor(); | |
if ($it->valid()) { | |
$daily = $it->current(); | |
$daily->sum($this->amount); | |
$it->update($daily); | |
} else { | |
$daily = new DailySummary(['date' => $this->date]); | |
$daily->sum($this->amount); | |
$it->insert($daily); | |
} | |
DailySummary::resetQuery(); | |
Stock::resetQuery(); | |
} | |
public function onSaveRow(InvoiceItem $row) | |
{ | |
$this->amount->increment($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(); | |
} | |
} | |
public function onDeleteRow(InvoiceItem $row) | |
{ | |
$this->amount->decrement($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(); | |
} | |
} | |
} | |
class Invoice extends Model | |
{ | |
public static $aliases = ['sales_amount' => 'sales', 'tax_amount' => 'tax', 'payment_amount' => 'payment', 'balance_amount' => 'balance']; | |
public static $transferMap = ['sales' => 'amount', 'tax' => 'amount', 'payment' => 'amount', 'balance' => 'amount']; | |
public static $guarded = ['id']; | |
public $id = 0; | |
public $amount = null; | |
public $date; | |
protected $update_at; | |
private $baseBalance = 0; | |
public function __construct() | |
{ | |
parent::__construct(); | |
$this->amount = new InvoiceAmount; | |
$this->date = date("Y/m/d"); | |
} | |
public function items() | |
{ | |
return $this->hasMany('InvoiceItem'); | |
} | |
public function customer() | |
{ | |
return $this->belongsTo('Customer'); | |
} | |
private function assignInvoiceNumber() | |
{ | |
$it = Invoice::serverCursor(0, QueryExecuter::SEEK_LAST); | |
if ($it->valid()) { | |
$inv = $it->current(); | |
$this->id = $inv->id + 1; | |
} else { | |
$this->id = 1; | |
} | |
} | |
private function readBaseBalance() | |
{ | |
$this->baseBalance = 0; | |
$it = $this->serverCursor(1, QueryExecuter::SEEK_LESSTHAN, Transactd::ROW_LOCK_S); | |
if ($it->valid()) { | |
$inv = $it->current(); | |
if ($inv->customer_id === $this->customer_id) { | |
$this->baseBalance = $inv->amount->balance; | |
} | |
} | |
return $it; | |
} | |
private function updateBalanceAmount($difference) | |
{ | |
if ($difference !== 0) { | |
$it = $this->serverCursor(1, QueryExecuter::SEEK_GREATER); | |
try { | |
$it->setTimestampMode(Transactd::TIMESTAMP_VALUE_CONTROL); | |
foreach($it as $inv) { | |
if ($inv->customer_id !== $this->customer_id) { | |
break; | |
} | |
$inv->amount->balance += $difference; | |
$it->update($inv); | |
$inv->updateCache(); | |
} | |
$it->setTimestampMode(Transactd::TIMESTAMP_ALWAYS); | |
} catch (Exception $e) { | |
$it->setTimestampMode(Transactd::TIMESTAMP_ALWAYS); | |
throw $e; | |
} | |
} | |
} | |
private function conflictFail($inv) | |
{ | |
if ($this->update_at !== $inv->update_at) { | |
throw new Exception('This invoice is already changed by other user.'); | |
} | |
} | |
public function save($options = 0, $forceInsert = false) | |
{ | |
InvoiceItem::$handler = new InvoiceItemSaveHandler($this->date); | |
InvoiceItem::$handler->onStart(); | |
if ($this->id === 0) { | |
$this->assignInvoiceNumber(); | |
$forceInsert = true; | |
} | |
$it = $this->readBaseBalance(); | |
$this->amount->calc($this->items, $this->baseBalance); | |
if ($forceInsert) { | |
$this->items->renumber('row'); | |
$it->insert($this); | |
} else { | |
$it = $this->serverCursor(1, QueryExecuter::SEEK_EQUAL); | |
$it->validOrFail(); | |
$this->conflictFail($it->current()); | |
$it->update($this); | |
} | |
$this->items->save(); | |
$this->updateCache(); | |
$this->updateBalanceAmount($this->amount->difference()); | |
InvoiceItem::$handler->onEnd(); | |
} | |
public function delete($options = null) | |
{ | |
InvoiceItem::$handler = new InvoiceItemSaveHandler($this->date); | |
InvoiceItem::$handler->onStart(); | |
$it = $this->serverCursor(1, QueryExecuter::SEEK_EQUAL); | |
$it->validOrFail(); | |
$this->conflictFail($it->current()); | |
$it->delete(); | |
$this->items->delete(); | |
$this->updateCache(true); | |
$this->updateBalanceAmount(0 - $this->amount->difference()); | |
InvoiceItem::$handler->onEnd(); | |
} | |
public function addSalesLine($code, $qty) | |
{ | |
$prod = Product::index(1)->findOrFail($code); | |
$item = new InvoiceItem; | |
$item->assignSales($prod, $qty); | |
$this->items->add($item); | |
return $item; | |
} | |
public function addPaymentLine($amount, $desc) | |
{ | |
$item = new InvoiceItem; | |
$item->assignPayment($amount, $desc); | |
$this->items->add($item); | |
return $item; | |
} | |
private function prepareTables() | |
{ | |
Stock::prepareTable(); | |
DailySummary::prepareTable(); | |
InvoiceItem::prepareTable(); | |
} | |
public function saveWithTransaction() | |
{ | |
try { | |
$this->prepareTables(); | |
DB::beginTrn(Transactd::MULTILOCK_GAP); | |
$this->save(); | |
DB::commit(); | |
} catch (Exception $e) { | |
DB::rollBack(); | |
throw $e; | |
} | |
} | |
public function deleteWithTransaction() | |
{ | |
try { | |
$this->prepareTables(); | |
DB::beginTrn(Transactd::MULTILOCK_GAP); | |
$this->delete(); | |
DB::commit(); | |
} catch (Exception $e) { | |
DB::rollBack(); | |
throw $e; | |
} | |
} | |
} | |
class InvoiceItem extends Model | |
{ | |
const SALES = 0; | |
const PAYMENT = 1; | |
const TAX_RATE = 0.08; | |
public static $handler = null; | |
protected static $table = 'invoice_items'; | |
protected static $guarded = []; | |
public function invoice() | |
{ | |
return $this->belongsTo('Invoice'); | |
} | |
public function stock() | |
{ | |
return $this->hasOne('Stock', 0, 'product_code'); | |
} | |
public function product() | |
{ | |
return $this->hasOne('Product', 1, 'product_code'); | |
} | |
public function assignSales($prod, $qty) | |
{ | |
$this->product_code = $prod->code; | |
$this->product_description = $prod->description; | |
$this->line_type = self::SALES; | |
$this->price = $prod->price; | |
$this->quantity = $qty; | |
$this->amount = $prod->price * $qty; | |
$this->tax = (int)(($this->amount * self::TAX_RATE) + 0.5); | |
} | |
public function assignPayment($amount, $desc) | |
{ | |
$this->product_code = 'PAYMENT'; | |
$this->product_description = $desc; | |
$this->line_type = self::PAYMENT; | |
$this->amount = $amount; | |
} | |
public static function deleting(InvoiceItem $row) | |
{ | |
self::$handler->onDeleteRow($row); | |
return true; | |
} | |
public static function saving(InvoiceItem $row) | |
{ | |
self::$handler->onSaveRow($row); | |
return true; | |
} | |
} | |
class DailySummary extends Model | |
{ | |
use AmountTrait; | |
protected static $guarded = []; | |
protected static $table = 'daily_summaries'; | |
static protected $aliases = ['slaes_amount' => 'sales', 'tax_amount' => 'tax', 'payment_amount' => 'payment']; | |
public function invoices() | |
{ | |
return $this->hasMany('Invoice', 2, 'date'); | |
} | |
} | |
// Please `connect` at the beginning of the application. | |
// DB::connect(URI, URI); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment