Doctrineは3種類の継承戦略をサポートします。これらの戦略は混ぜることができます。三種類は単一、具象とカラム集約です。これらの異なる継承の使い方をこの章で学びます。
この章では前の章で利用してきたテスト環境の既存のすべてのスキーマとモデルを削除してください。
$ rm schema.yml
$ touch schema.yml
$ rm -rf models/*
単一継承は最も簡単でシンプルです。単一継承においてすべての子クラスは同じカラムを親として共有しすべての情報は親テーブルに保存されます。
// models/Entity.php
class Entity extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 30);
$this->hasColumn('username', 'string', 20);
$this->hasColumn('password', 'string', 16);
$this->hasColumn('created_at', 'timestamp');
$this->hasColumn('update_at', 'timestamp');
}
}
Entityを継承するUserモデルを作りましょう:
// models/User.php
class User extends Entity
{ }
Groupモデルに対しても同じことを行いましょう:
// models/Group.php
class Group extends Entity
{ }
YAMLフォーマットでの例は次の通りです。YAML Schema Filesの章でYAMLの詳細を読むことができます:
---
# schema.yml
# ...
Entity:
columns:
name: string(30)
username: string(20)
password: string(16)
created_at: timestamp
updated_at: timestamp
User:
inheritance:
extends: Entity
type: simple
Group:
inheritance:
extends: Entity
type: simple
上記のコードによって生成されたSQLを確認してみましょう:
// test.php
// ...
$sql = Doctrine_Core::generateSqlFromArray(array('Entity', 'User', 'Group'));
echo $sql[0];
上記のコードは次のSQLクエリを出力します:
CREATE TABLE entity (id BIGINT AUTO_INCREMENT,
username VARCHAR(20),
password VARCHAR(16),
created_at DATETIME,
updated_at DATETIME,
type VARCHAR(255),
name VARCHAR(30),
PRIMARY KEY(id)) ENGINE = INNODB
YAMLスキーマファイルを使うとき子クラスでカラムを定義できますがYAMLが解析されるときカラムは自動的に親に移動します。これはカラムを簡単に組織できるようにするためだけです。
具象継承は子クラス用の独立したテーブルを作成します。しかしながら具象継承ではそれぞれのクラスはすべてのカラムを含むテーブルを生成します(継承されたカラムを含む)。具象継承を使うには下記で示されるように子クラスへの明示的なparent::setTableDefinition()を追加する必要があります。
// models/TextItem.php
class TextItem extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('topic', 'string', 100);
}
}
TextItemを継承するCommentという名前のモデルを作りcontentという名前のカラムを追加してみましょう:
// models/Comment.php
class Comment extends TextItem
{
public function setTableDefinition()
{
parent::setTableDefinition();
$this->hasColumn('content', 'string', 300);
}
}
YAMLフォーマットでの例は次の通りです。YAML Schema Filesの章でYAMLの詳細を読むことができます:
---
# schema.yml
# ...
TextItem:
columns:
topic: string(100)
Comment:
inheritance:
extends: TextItem
type: concrete
columns:
content: string(300)
上記のモデルによって生成されたSQLをチェックしてみましょう:
// test.php
// ...
$sql = Doctrine::generateSqlFromArray(array('TextItem', 'Comment'));
echo $sql[0] . "\n";
echo $sql[1];
上記のコードは次のSQLクエリを出力します:
CREATE TABLE text_item (id BIGINT AUTO_INCREMENT,
topic VARCHAR(100),
PRIMARY KEY(id)) ENGINE = INNODB
CREATE TABLE comment (id BIGINT AUTO_INCREMENT,
topic VARCHAR(100),
content TEXT,
PRIMARY KEY(id)) ENGINE = INNODB
具象クラスにおいて追加のカラムを定義する必要はありませんが、それぞれのクラス用に個別のテーブルを作るにはsetTableDefinition()の呼び出しを繰り返し書かなければなりません。
次の例ではentity、userとgroupと呼ばれるデータベーステーブルがあります。Usersとgroupsは両方ともentitiesです。行わなければならないことは3つのクラス(Entity、GroupとUser)を書きsetTableDefinition()メソッドの呼び出しを繰り返し記述することです。
// models/Entity.php
class Entity extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 30);
$this->hasColumn('username', 'string', 20);
$this->hasColumn('password', 'string', 16);
$this->hasColumn('created', 'integer', 11);
}
}
// models/User.php
class User extends Entity
{
public function setTableDefinition()
{
// 次のメソッド呼び出しは
// 具象継承で必要
parent::setTableDefinition();
}
}
// models/Group.php
class Group extends Entity
{
public function setTableDefinition()
{
// 次のメソッド呼び出しは
// 具象継承で必要
parent::setTableDefinition();
}
}
YAMLフォーマットでの例は次の通りです。YAML Schema Filesの章でYAMLの詳細を読むことができます:
---
Entity:
columns:
name: string(30)
username: string(20)
password: string(16)
created: integer(11)
User:
inheritance:
extends: Entity
type: concrete
Group:
tableName: groups
inheritance:
extends: Entity
type: concrete
上記のモデルによって生成されたSQLをチェックしてみましょう:
// test.php
// ...
$sql = Doctrine::generateSqlFromArray(array('Entity', 'User', 'Group'));
echo $sql[0] . "\n";
echo $sql[1] . "\n";
echo $sql[2] . "\n";
上記のコードは次のSQLクエリを出力します:
CREATE TABLE user (id BIGINT AUTO_INCREMENT,
name VARCHAR(30),
username VARCHAR(20),
password VARCHAR(16),
created BIGINT,
PRIMARY KEY(id)) ENGINE = INNODB
CREATE TABLE groups (id BIGINT AUTO_INCREMENT,
name VARCHAR(30),
username VARCHAR(20),
password VARCHAR(16),
created BIGINT,
PRIMARY KEY(id)) ENGINE = INNODB
CREATE TABLE entity (id BIGINT AUTO_INCREMENT,
name VARCHAR(30),
username VARCHAR(20),
password VARCHAR(16),
created BIGINT,
PRIMARY KEY(id)) ENGINE = INNODB
次の例においてentityという名前の1つのデータベーステーブルがあります。Usersとgroupsは両方ともentitiesでこれらは同じデータベーステーブルを共有します。
entityテーブルはtypeと呼ばれる1つのカラムを持ちます。このカラムはgroupもしくはuserであることを伝えます。usersはタイプ1でグループはタイプ2であると決めます。
行わなければならない唯一の作業は3のレコード(以前と同じ)を作成し親クラスからのDoctrine_Table::setSubclasses()メソッド呼び出しを追加することです。
// models/Entity.php
class Entity extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('name', 'string', 30);
$this->hasColumn('username', 'string', 20);
$this->hasColumn('password', 'string', 16);
$this->hasColumn('created_at', 'timestamp');
$this->hasColumn('update_at', 'timestamp');
$this->setSubclasses(array(
'User' => array('type' => 1),
'Group' => array('type' => 2)
)
);
}
}
// models/User.php
class User extends Entity
{ }
// models/Group.php
class Group extends Entity
{ }
YAMLフォーマットでの例は次の通りです。YAML Schema Filesの章でYAMLの詳細を読むことができます:
---
Entity:
columns:
username: string(20)
password: string(16)
created_at: timestamp
updated_at: timestamp
User:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 1
Group:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 2
上記のモデルによって生成されたSQLをチェックしてみましょう:
// test.php
// ...
$sql = Doctrine::generateSqlFromArray(array('Entity', 'User', 'Group'));
echo $sql[0];
上記のコードは次のSQLクエリを出力します:
CREATE TABLE entity (id BIGINT AUTO_INCREMENT,
username VARCHAR(20),
password VARCHAR(16),
created_at DATETIME,
updated_at DATETIME,
type VARCHAR(255),
PRIMARY KEY(id)) ENGINE = INNODB
typeカラムが自動的に追加されたことに注目してください。データベースのそれぞれのレコードが所属するモデルを知っているカラム集約継承です。
この機能によってEntityテーブルにクエリを行い変えされたオブジェクトが親クラスで設定された制約にマッチする場合UserもしくはGroupオブジェクトを戻します。
具体的な内容は下記のコードで見てみましょう。最初に新しいUserオブジェクトを保存しましょう:
// test.php
// ...
$user = new User();
$user->name = 'Bjarte S. Karlsen';
$user->username = 'meus';
$user->password = 'rat';
$user->save();
新しいGroupオブジェクトを保存しましょう:
// test.php
// ...
$group = new Group();
$group->name = 'Users';
$group->username = 'users';
$group->password = 'password';
$group->save();
作成したUserのid用のEntityモデルにクエリを行うと、Doctrine_QueryはUserのインスタンスを返します。
// test.php
// ...
$q = Doctrine_Query::create()
->from('Entity e')
->where('e.id = ?');
$user = $q->fetchOne(array($user->id));
echo get_class($user); // User
Groupレコードに対して同じようなことを行うと、Groupのインスタンスが戻されます。
// test.php
// ...
$q = Doctrine_Query::create()
->from('Entity e')
->where('e.id = ?');
$group = $q->fetchOne(array($group->id));
echo get_class($group); // Group
上記の内容はtypeカラムであるから可能です。Doctrineはどのクラスによってそれぞれのレコードが作成されたのか知っているので、データは適切なサブクラスにハイドレイトされます。
個別のUserもしくはGroupモデルにクエリを行うこともできます:
$q = Doctrine_Query::create()
->select('u.id')
->from('User u');
echo $q->getSqlQuery();
上記のgetSql()の呼び出しは次のSQLクエリを出力します:
SELECT
e.id AS e__id
FROM entity e
WHERE (e.type = '1')
User型であるレコードのみが返されるようにtypeの条件が自動的に追加されたことに注目してください。