Define model

Define models after preparing the database.

In this chapter, you can learn about the following contents:

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.

  1. Create database and tables
  2. Create master data