Define model
Define models after preparing the database.
In this chapter, you can learn about the following contents:
- Connect to database
- Define models
- Specify table name
- Set alias for fields
- Define relationship
- Implement serializable object
- How to solve impedance mismatch
At application startup
Let's start coding. Create salesdb.php
in salesdb
directory.
First, define require
, use
and database 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;
// Set "DB" alias for DatabaseManager
class_alias('Transactd\DatabaseManager', 'DB');
// Database URI (the master and the slave are same host)
// Authentication by Transactd host does not require user name and password
CONST masterUri = 'tdap://localhost/salesdb';
CONST slaveUri = 'tdap://localhost/salesdb';
Connect to database
If you know URI, connecting is very easy.
// Connect to database
DB::connect(masterUri, slaveUri);
Define basic models and relationships
First, define models and relationships for each tables.
Customer model
Models inherit Model
class.
Customer
has invoices. So define invoices
relationship as hasMany
. However, this relationship is not realistic. If you use this application for many years, the number of invoices become large, and reading all the invoices become useless. there is little point in the operation of reading all the slips at once.
Therefore, we are going to define transactions
method which read invoices with specific period.
In addition, the error will be occurred if the property read by relationship is not found. Define it to be readable from new
object.
class Customer extends Model
{
// $fillable or $guarded is required.
protected static $guarded = ['id'];
// It will be used by invoices().
public $id = 0;
// Do not use this method. Use `transactions` instead.
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 model
Product
has stocks. So define stock
relationship as hasOne
.
class Product extends Model
{
protected static $guarded = ['id'];
public function stock()
{
// stocks::index 0, Product::code
return $this->hasOne('Stock', 0, 'code');
}
}
Stock model
Define the opposite of Product::stock
relationship.
class Stock extends Model
{
protected static $guarded = [];
public function product()
{
// Product::index 1, Stock::code
return $this->belongsTo('Product', 'code', 1);
}
}
AmountTrait trait
Both of invoices
and daily_summaries
have common fields, slaes_amount
, tax_amount
and payment_amount
. To aggregate the calculation processes, define AmountTrait
trait.
Remove amount
from property names. daily_summaries
does not use $balance
, but define it because it is required by 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;
}
}
This trait has three simple methods reset
, sum
and total
. We will use these methods later.
Invoice model
In the Invoice
model, let's create an instance and delegate it, without using AmountTrait
trait directly. First, define InvoiceAmount
class to create instance. In addition, add use JsonSerializable
to support this class for serialization and deserialization.
class InvoiceAmount
{
use AmountTrait;
use JsonSerializable;
public function __construct()
{
$this->className = get_class($this); // Set the class name for serialization.
}
}
To use JsonSerializable
, set the class name for serialization in the constructor.
With this, the prototype of InvoiceAmount
with serialization is completed.
Create its instance in the constructor of Invoice
.
class Invoice extends Model
{
public $amount = null;
public function __construct()
{
parent::__construct();
$this->amount = new InvoiceAmount;
}
}
Define aliases to absorb the name difference.
public static $aliases = ['sales_amount' => 'sales', 'tax_amount' => 'tax',
'payment_amount' => 'payment', 'balance_amount' => 'balance'];
Then define delegation map for ORM. Set $transferMap
to the value will be read from or written to $this->amount
property.
public static $transferMap = ['sales' => 'amount', 'tax' => 'amount',
'payment' => 'amount', 'balance' => 'amount'];
Invoice
has items. So define items
relationship as hasMany
. In addition, define the opposite of Customer::invoice
relationship.
public static $guarded = ['id'];
public function items()
{
return $this->hasMany('InvoiceItem');
}
public function customer()
{
return $this->belongsTo('Customer');
}
So far Invoice
model is as follows:
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; // Pre-define for relationship
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 model
Transactd PHP ORM does not support model-table name converting with snake_case
. Specify table name directly.
class InvoiceItem extends Model
{
protected static $table = 'invoice_items';
}
InvoiceItem
has product_code
. Define relationship between Product
and Stock
with it. You can easily get product details or inventory quantity, which is not in InvoiceItem
.
In addition, define the opposite of Invoice::items
relationship.
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 model
DailySummary
model uses AmountTrait
trait with use
.
Define invoices
relationship as hasMany
, to get invoices with DailySummary::date
. Define aliases and table name too.
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');
}
}
Source code
The source code of this chapter can be downloaded from GitHub Gist.