イントロダクション

ModelクラスはQueryExecuterクラスを継承しています(マジックメソッドを用いた委譲を行っています)。モデルを理解するにはQueryExecuterについても理解する必要があります。

目次

モデルの定義

1つのモデルクラスは、データベースの1つのテーブルに対応します。 1つのモデルインスタンスは、対応するテーブルの1行を表現します。

モデル名とテーブル名、モデルのプロパティ名とテーブルの列名は、同一もしくは決まったルールで変換されます。 Transactd PHP ORMは基本的にRuby on RailsのActiveRecordと似たルールで名前を変換します。

モデルを定義するには、Modelクラスを継承します。

Customerモデルを定義する

use Transactd\Model;

class Customer extends Model
{
}

モデル名とテーブル名

Transactd PHP ORMはActiverRecordと似た命名規則を使用しています。テーブル名は以下のようになります:

Note: ActiverRecordではsnake_case形式の名前を使用しますが、この形式はサポートされません。 Transactd PHP ORMはデフォルトでsimplelowercase形式の名前を使用します。ルールと異なるテーブル名を使用する場合は、static $tableに直接指定できます。

use Transactd\Model;

class SalesSummary extends Model
{
    protected static $table = 'sales_summary';
}

プロパティ名とフィールド名

モデルのプロパティ名はテーブルのフィールド名と同じになります。別名を付けるにはstatic $aliasesを指定してください。別名を付けた場合、元のフィールド名と同じ名前のプロパティは定義されません。

class SalesSummary extends Model
{
    protected static $table = 'sales_summary';
    protected static $aliases = ['sales_amount' => 'salesAmount', 'tax_amount' => 'taxAmount'];
    // 'salesAmount'と'taxAmount'が定義される。
    // 'sales_amount'と'tax_amount'は定義されない。
}

コネクション名

モデルごとにどのコネクションを使用するかをstatic $connectionで指定することができます。

use Transactd\Model;

class SalesSummary extends Model
{
    protected static $table = 'sales_summary';
    protected static $connection = 'Internal';
}

モデルの自動生成

Transactd PHP ORMには、既存のデータベースからモデルを自動生成するPHPプログラムMdlGen.phpが用意されています。 PHPDocに対応した@propertyコメントが自動生成されます。IDEでプロパティの補完機能を利用できるようになります。

MdlGen.phpでは以下の機能がサポートされています。

使い方

コマンドラインから以下のように実行します。

// Composerでインストールしたディレクトリで
>php vendor/TransactdORMPHP-master/src/utility/MdlGen.php -slocalhost -dormtest -tcutomers

結果:

URI=tdap://localhost/ormtest

output = Customer.php
Generate done!

1回の実行で1つのモデルが生成されます。複数のモデルを生成する場合は、テーブル毎に実行が必要です。

引数なしで実行するとパラメータの説明が表示されます。

// Composerでインストールしたディレクトリで
php vendor/TransactdORMPHP-master/src/utility/MdlGen.php

USAGE: php mdlgen.php -s[server] -d[database] -t[table] -u[userName] -p[password] -a[aliasList] -n[namespace] -o[output-directory]

  -s : Name or ipaddress of a Transactd server.
  -d : Database name.
  -t : Table name of generate target.
  -u : [optional] Username for server access.
  -p : [optional] Password for server access.
  -a : [optional] File name of alias list. (ini file format (key=value))
  -n : [optional] Namespace of the target Model.
  -o : [optional] Output directry name.(Include namespace)

出力ディレクトリとファイル名

Composerでライブラリをインストールしたディレクトリがデフォルトの出力ディレクトリです。名前空間を指定した場合はその直下の名前空間と同じディレクトリに出力されます。名前空間のディレクトリは事前に作成しておく必要があります。

ファイル名はモデル名.phpです。

フィールド名とテーブル名の変換辞書

-aオプションでフィールド名とテーブル名の変換辞書ファイルを指定できます。変換辞書は、以下のように変換元と変換先を=で記述したini形式のファイルです。日本語の名前も変換できます。ファイルはutf8で保存してください。

変換辞書例:

customer_name=customerName
product_name=productName
得意先=customer

出力サンプル

<?php

use Transactd\Model;

/**
 * Original table name:customers
 * 
 * @property integer $id
 * @property string  $name
 * @property string  $update_at
 */
class Customer extends Model
{
}

モデルの取得

すべてのモデルを取得

$customers = Customer::all();

先頭のモデルを取得

インデックス番号を指定しなかった場合、primary keyが使用されます。

primary keyを使用し、先頭のモデルを取得

$customer = Customer::first();

インデックス番号1を使用し、先頭のモデルを取得

$customer = Customer::index(1)->first();

ひとつのモデルを取得

primary keyを使用し、id = 3の顧客を取得

$customer = Customer::find(3);

nameキーを使用し、name = 'John'の顧客を取得

$nameKeyNum = 1;
$john = Customer::index($nameKeyNum)->find('John');

マルチセグメントキーを使用し顧客を取得

$customer = Customer::index(2)->find([3, '1567-900-00']);

複数のモデルを取得

複数のレコードを一度に取得します。

primary keyを使用し、id = 3id = 4id = 6のレコードを取得

$customers = Customer::findMany([3, 4, 6]);

マルチセグメントキーを使用し複数の顧客を取得

$customers = Customer::index(2)->findMany([[3, '1567-900-00'],[9, '1467-700-01']]);

複数のモデルを取得するクエリ

QueryExecuterと同様に、クエリ条件を用いてモデルを取得することができます。

$customers = Customer::keyValue(0)->where('name', 'John')->noBreakReject()->get();

QueryExecuterインスタンスの代わりにモデルクラスの名前を書くだけです。詳細はクエリの条件式を参照してください。

高度なモデル定義

インピーダンス・ミスマッチ

テーブルとモデルの間で取り扱うデータ領域に違いがある場合、Transactd PHP ORMはその値を別のオブジェクトに転送することができます。

例えば、customersテーブルが顧客の名前と住所を持っている場合を考えます。このとき、住所をCustomerクラスのaddressプロパティに、Addressオブジェクトとして持たせたいとします。

転送を行うには、static $transferMapに、'field_name' => 'property_name_of_the_transfer_object' 形式で転送の定義をします。

class Address
{
}

class Customer extends Model
{
    static protected $transferMap = ['zip' => 'address', 'city' => 'address', 'street' => 'address'];
    public $address = null;
    public function __construct()
    {
        parent::__construct();
        $this->address = new Address(); // address は転送の前に生成されている必要がある
    }
}

すると、zipプロパティには以下のようにアクセスできるようになります:

$customer = find(1);
if ($customer->address->zip === '390-0825') {
    // (省略)
}

Note: 値が転送されるオブジェクトは、メインオブジェクトのプロパティの値として、転送の前に生成されている必要があります。もしプロパティがnullの場合、その値は読み書きされません。

アクセス修飾子

メンバを公開したくない場合は、protectedを使用します。

noteプロパティをprotectedにする

class Customer extends Model
{
    protected $note;
}

Transactd PHP ORMがデータをモデルに読み取る際は、setterメソッドが定義されていた場合、それを使用して値を設定します。

class Customer extends Model
{
    protected $note;
    public __set($name, $value)
    {
        if ($name === 'note') {
            $this->note = trim($value); // エクステンション(php_tranasactd.[so|dll])はこのコードで値を設定
        }
    }
}

モデルを保存する際は、getterメソッドが定義されていたとしても使用されません。 Transactd PHP ORMは、$noteがprotectedメンバだったとしても、それを直接読み取ります。

class Customer extends Model
{
    protected $note;
    public __get($name)
    {
        if ($name === 'note') {
            return $this->note; // エクステンション(php_tranasactd.[so|dll])はこのコードを使用しない
        }
        return parent::__get($name); // Model::__getを呼ぶ(リレーションで重要になる)
    }
}

存在しないプロパティへのアクセス

モデルに存在しないプロパティへの読み取りを行うと、例外がスローされます。これにより、タイプミスの発見が容易になります。

selectを使用した場合、選択されていないフィールドへの読み取りを行った場合も同様に例外がスローされます。

$customer = Customer::keyValue(1)->select('id', 'name')->get();
echo $customer->phone; // Error! \Exceptionがスローされる

しかし、存在しないプロパティへの書き込みは、無条件で可能です。

$customer = new Customer();
// phone123はモデルに存在しないメンバ
$customer->phone123 = '0263-99-9999'; // 書き込みは可能

CachedQueryExecuterとキャッシュ

ここまで、モデルはQueryExecuterを継承していると説明していましたが、正確にはCachedQueryExecuterを継承しています。 Transactd PHP ORMがオブジェクトを読み取る際は、キャッシュ内に該当オブジェクトがあれば、それが返されます。キャッシュによってデータベースへのアクセス回数を減らすことができます。

以下のメソッドは、キャッシュにオブジェクトを追加、もしくはキャッシュ内のオブジェクトを更新します:

以下のメソッドは、読み取りの際にまずキャッシュ内を探します:

モデルのキャッシュをクリアする

Customerモデルのキャッシュをすべてクリアします。

Customer::clear();

オブジェクトを再読み込みし、キャッシュを更新する

$customer->refresh();

カスタムコレクション

カスタムコレクションを定義することもできます。

カスタムコレクションの定義

カスタムコレクションの例

class CCollection extends Transactd\Collection
{
    public function __construct($array, $rel = null, $parent = null)
    {
        parent::__construct($array, $rel, $parent);
    }
}

ORMでカスタムコレクションを使用する

コレクションを生成するnewCollectionメソッドを、モデルに追加します。

class Customer extends Model
{
     public function newCollection($ar, $rel, $parent)
     {
         return new CCollection($ar, $rel, $parent);
     }
}

ModelとQueryExecuterのメソッドチェーン

モデルに定義されたscopeプレフィックス付きのメソッドは、QueryExecuterのインスタンスから呼び出すことができます。

メソッドの定義

scopeプレフィックスのついたメソッドは、引数にQueryExecuterインスタンスを受け取り、戻り値としてもQueryExecuterインスタンスを返します。

class Customer extends Model
{
     public function scopeInJapan($query) {
         return $query->where('country', 'japan');
     }
}

メソッドチェーンを使用する

定義したメソッドを呼び出す際は、scopeプレフィックスを除いた名前を使用します。 QueryExecuterはモデル内でscope + メソッド名の名前を持つメソッドを探し、呼び出します。

$customer = Customer::keyValue(1)->where('id', '<', 11)->inJapan()->get();