This project is no longer maintained and has been archived. |
Inheritance
Doctrine supports three types of inheritance strategies which can be mixed together. The three types are simple, concrete and column aggregation. You will learn about these three different types of inheritance and how to use them in this chapter.
For this chapter lets delete all our existing schemas and models from our test environment we created and have been using in the earlier chapters:
$ rm schema.yml
$ touch schema.yml
$ rm -rf models/*
Simple
Simple inheritance is the easiest and simplest inheritance to use. In simple inheritance all the child classes share the same columns as the parent and all information is stored in the parent table.
// 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');
}
}
Now lets create a User
model that extends Entity
:
// models/User.php
class User extends Entity
{ }
Do the same thing for the Group
model:
// models/Group.php
class Group extends Entity
{ }
Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:
Lets check the SQL that is generated by the above models:
// test.php
// ...
$ sql = Doctrine_Core::generateSqlFromArray(array('Entity', 'User', 'Group'));
echo $sql[0];
The above code would output the following SQL query:
CREATE TABLE entity (id BIGINT AUTO_INCREMENT,
username VARCHAR(20),
password VARCHAR(16),
created_at DATETIME,
updated_at DATETIME,
name VARCHAR(30),
PRIMARY KEY(id)) ENGINE = INNODB
When using YAML schema files you are able to define columns in the child classes but when the YAML is parsed the columns are moved to the parent for you automatically. This is only a convenience to you so that you can organize your columns easier. |
Concrete
Concrete inheritance creates separate tables for child classes. However
in concrete inheritance each class generates a table which contains all
columns (including inherited columns). In order to use concrete
inheritance you'll need to add explicit parent::setTableDefinition()
calls to child classes as shown below.
// models/TextItem.php
class TextItem extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('topic', 'string', 100);
}
}
Now lets create a model named Comment
that extends TextItem
and
add an extra column named content
:
// models/Comment.php
class Comment extends TextItem
{
public function setTableDefinition()
{
parent::setTableDefinition();
$this->hasColumn('content', 'string', 300);
}
}
Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:
Lets check the SQL that is generated by the above models:
// test.php
// ...
$ sql = Doctrine_Core::generateSqlFromArray(array('TextItem', 'Comment'));
echo $sql[0] . "";
echo $sql[1];
The above code would output the following SQL query:
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
In concrete inheritance you don't necessarily have to define additional
columns, but in order to make Doctrine create separate tables for each
class you'll have to make iterative setTableDefinition()
calls.
In the following example we have three database tables called
entity
, user
and group
. Users
and groups
are both
entities
. The only thing we have to do is write 3 classes
(Entity
, Group
and User
) and make iterative
setTableDefinition()
method calls.
// 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()
{
// the following method call is needed in
// concrete inheritance
parent::setTableDefinition();
}
}
// models/Group.php
class Group extends Entity
{
public function setTableDefinition()
{
// the following method call is needed in
// concrete inheritance
parent::setTableDefinition();
}
}
Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:
Lets check the SQL that is generated by the above models:
// test.php
// ...
$ sql = Doctrine_Core::generateSqlFromArray(array('Entity', 'User', 'Group'));
echo $sql[0] . "";
echo $sql[1] . "";
echo $sql[2] . "";
The above code would output the following SQL query:
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
Column Aggregation
In the following example we have one database table called entity
.
Users
and groups
are both entities
and they share the same
database table.
The entity
table has a column called type
which tells whether an
entity
is a group
or a user
. Then we decide that users
are type 1 and groups type 2.
The only thing we have to do is to create 3 records (the same as before)
and add the call to the Doctrine_Table::setSubclasses()
method from
the parent class.
// 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
{ }
Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:
1 ---
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Lets check the SQL that is generated by the above models:
// test.php
// ...
$ sql = Doctrine_Core::generateSqlFromArray(array('Entity', 'User', 'Group'));
echo $sql[0];
The above code would output the following SQL query:
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
Notice how the type
column was automatically added.
This is how column aggregation inheritance knows which model each
record in the database belongs to.
This feature also enable us to query the Entity
table and get a
User
or Group
object back if the returned object matches the
constraints set in the parent class.
See the code example below for an example of this. First lets save a new
User
object:
// test.php
// ...
$ user = new User();
$ user->name = 'Bjarte S. Karlsen';
$ user->username = 'meus';
$ user->password = 'rat';
$ user->save();
Now lets save a new Group
object:
// test.php
// ...
$ group = new Group();
$ group->name = 'Users';
$ group->username = 'users';
$ group->password = 'password';
$ group->save();
Now if we query the Entity
model for the id of the User
we
created, the Doctrine_Query will return an instance of
User
.
// test.php
// ...
$ q = Doctrine_Query::create()
->from('Entity e')
->where('e.id = ?');
$ user = $q->fetchOne(array($user->id));
echo get_class($user); // User
If we do the same thing as above but for the Group
record, it will
return an instance of Group
.
// test.php
// ...
$ q = Doctrine_Query::create()
->from('Entity e')
->where('e.id = ?');
$ group = $q->fetchOne(array($group->id));
echo get_class($group); // Group
The above is possible because of the type
column.
Doctrine knows which class each record was created by, so when data
is being hydrated it can be hydrated in to the appropriate
sub-class.
We can also query the individual User
or Group
models:
$ q = Doctrine_Query::create()
->select('u.id')
->from('User u');
echo $q->getSqlQuery();
The above call to getSql()
would output the following SQL query:
SELECT e.id AS
e_id FROM entity e WHERE (e.type = '1')
Notice how the type
condition was automatically added
to the query so that it will only return records that are of type
User
.
Conclusion
Now that we've learned about how to take advantage of PHPs inheritance features with our models we can move on to learning about Doctrine Behaviors. This is one of the most sophisticated and useful features in Doctrine for accomplishing complex models with small and easy to maintain code.