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:

1--- # 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
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,
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:

1--- # schema.yml TextItem: columns: topic: string(100) Comment: inheritance: extends: TextItem type: concrete columns: content: string(300)
2
3
4
5
6
7
8
9
10
11
12
13

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:

1--- 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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::

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
.. notice::

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::

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.