モデルの定義
データベースが準備できたところで、モデルを定義します。
この章では以下の内容について学習できます。
- データベースへの接続
- モデルの定義方法
- テーブル名の変更方法
- フィールド名の変更方法(エイリアス)
- リレーションの定義
- シリアライズに対応したクラスの作成
- テーブルとモデルのインピーダンスミスマッチの解決方法
アプリケーション開始時の処理
それではいよいよコードを記述していきましょう。 salesdb
直下にsalesdb.php
ファイルを作成して記述していきます。
はじめにrequire
、use
とデータベースのURIを定義します。
<?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;
// DatabaseManagerのエイリアスをDBにする
class_alias('Transactd\DatabaseManager', 'DB');
// データベースアクセスURI マスターとスレーブは同じホスト
// Transactdのホスト認証であればユーザー名パスワードは不要
CONST masterUri = 'tdap://localhost/salesdb';
CONST slaveUri = 'tdap://localhost/salesdb';
データベースへの接続
URIが決まれば接続はとても簡単です。
// データベースに接続する
DB::connect(masterUri, slaveUri);
基本的なモデルとリレーションの定義
まずは各テーブルのモデルとリレーション定義します。
Customerモデル
モデルはModel
を継承します。
Customer
は伝票を持つのでhasMany
のinvoices
リレーションを定義します。ただ、このリレーションは現実的ではありません。長年使用して伝票が大量になると、すべての伝票を一度に読み取る操作にはほとんど意味がありません。
そこで、取引期間を指定して伝票を取得するtransactions
という関数を定義します。
また、リレーションで読み取られるプロパティは、存在しないとエラーになります。 new
したオブジェクトからも関連が読み取れるよう事前に定義しましょう。
class Customer extends Model
{
// Modelには$fillableまたは$guardedが必須なので$guardedを定義します。
protected static $guarded = ['id'];
// invoices()で使われるので事前定義
public $id = 0;
// このメソッドは使用しない。期間を指定したtransactionsを使う。
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();
}
}
Productモデル
Product
は在庫を持つのでhasOne
のstock
リレーションを定義します。
class Product extends Model
{
protected static $guarded = ['id'];
public function stock()
{
// stocks::index 0, Product::code
return $this->hasOne('Stock', 0, 'code');
}
}
Stockモデル
Product::stock
リレーションの逆リレーションを定義します。
class Stock extends Model
{
protected static $guarded = [];
public function product()
{
// Product::index 1, Stock::code
return $this->belongsTo('Product', 'code', 1);
}
}
AmountTraitトレイト
invoices
とdaily_summaries
は共通のslaes_amount
、tax_amount
、payment_amount
フィールドを持つので、計算処理を集約するためにAmountTrait
を定義します。
プロパティ名からはamount
を省きます。$balance
は残高であり、daily_summaries
では使用しませんが、invoices
で必要なので加えておきます。
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;
}
}
このトレイトには3つの簡単なメソッドreset
、sum
、total
を追加しておきます。これらは後で使用します。
Invoiceモデル
Invoice
モデルではAmountTrait
トレイトを直接use
せず、インスタンスを作成して委譲してみましょう。まず、インスタンスを作成するためにトレイトからInvoiceAmount
クラスを定義します。さらに、このクラスをシリアライズとデシリアライズに対応させるため、use JsonSerializable
を加えます。
class InvoiceAmount
{
use AmountTrait;
use JsonSerializable;
public function __construct()
{
$this->className = get_class($this); // シリアライズ用のクラス名をセット
}
}
JsonSerializable
を使う際は、コンストラクタでシリアライズ用のクラス名をセットします。
これで、シリアライズに対応したInvoiceAmount
の原型が出来上がりました。
Invoice
のコンストラクタでこのInvoiceAmount
のインスタンスを生成します。
class Invoice extends Model
{
public $amount = null;
public function __construct()
{
parent::__construct();
$this->amount = new InvoiceAmount;
}
}
次にORMのための委譲マップを定義しますが、その前に名前の違いを吸収するエイリアスを定義します。
public static $aliases = ['sales_amount' => 'sales', 'tax_amount' => 'tax',
'payment_amount' => 'payment', 'balance_amount' => 'balance'];
$this->amount
のプロパティに値が読み書きされるよう、$transferMap
を定義します。
public static $transferMap = ['sales' => 'amount', 'tax' => 'amount',
'payment' => 'amount', 'balance' => 'amount'];
Invoice
は明細を持つのでhasMany
のitems
リレーションを定義します。また、Customer::invoice
の逆リレーションなどを定義します。
public static $guarded = ['id'];
public function items()
{
return $this->hasMany('InvoiceItem');
}
public function customer()
{
return $this->belongsTo('Customer');
}
ここまででInvoice
モデルは以下のようになります。
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 function __construct()
{
parent::__construct();
$this->amount = new InvoiceAmount;
}
public function items()
{
return $this->hasMany('InvoiceItem');
}
public function customer()
{
return $this->belongsTo('Customer');
}
}
InvoiceItemモデル
Transactd PHP ORMはモデル名からテーブル名への変換でsnake_case
をサポートしないため、テーブル名を指定します。
class InvoiceItem extends Model
{
protected static $table = 'invoice_items';
}
InvoiceItem
はproduct_code
を持つので、そこからProduct
とStock
の関連を定義しましょう。 InvoiceItem
にない、商品の詳細や在庫数などを簡単に取得できます。
Invoice::items
の逆リレーションも定義します。
class InvoiceItem extends Model
{
protected static $table = 'invoice_items';
protected static $guarded = [];
public function invoice()
{
return $this->belongsTo('Invoice');
}
public function stock()
{
// Stock index 0, InvoiceItem::product_code
return $this->hasOne('Stock', 0, 'product_code');
}
public function product()
{
// Product index 1, InvoiceItem::product_code
return $this->hasOne('Product', 1, 'product_code');
}
}
DailySummaryモデル
DailySummary
モデルは、先ほどのAmountTrait
トレイトをuse
して使用します。
DailySummary
のdate
でinvoice
を取得できるように、hasMany
のinvoices
リレーションを定義します。また、テーブル名とエイリアスも定義します。
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()
{
// Invoice index 2, DailySummary::date
return $this->hasMany('Invoice', 2, 'date');
}
}
ソースコード
ここまでのソースコードはGitHub Gistからダウンロードできます。