This project is no longer maintained and has been archived.

Component Overview

This chapter is intended to give you a birds eye view of all the main components that make up Doctrine and how they work together. We've discussed most of the components in the previous chapters but after this chapter you will have a better idea of all the components and what their jobs are.

Manager

The Doctrine_Manager class is a singleton and is the root of the configuration hierarchy and is used as a facade for controlling several aspects of Doctrine. You can retrieve the singleton instance with the following code.

$ manager = Doctrine_Manager::getInstance();

Retrieving Connections

$ connections = $manager->getConnections();
foreach (connections as $connection) {
    echo $connection->getName() . "";
}

The Doctrine_Manager implements an iterator so you can simple loop over the $manager variable to loop over the connections.

foreach ($manager as $connection) {
    echo $connection->getName() . "";
}

Connection

Doctrine_Connection is a wrapper for database connection. The connection is typically an instance of PDO but because of how Doctrine is designed, it is possible to design your own adapters that mimic the functionality that PDO provides.

The Doctrine_Connection class handles several things:

  • Handles database portability things missing from PDO (eg. LIMIT / OFFSET emulation)
  • Keeps track of Doctrine_Table objects
  • Keeps track of records
  • Keeps track of records that need to be updated / inserted / deleted
  • Handles transactions and transaction nesting
  • Handles the actual querying of the database in the case of INSERT / UPDATE / DELETE operations
  • Can query the database using DQL. You will learn more about DQL in the DQL: Doctrine Query Language chapter.
  • Optionally validates transactions using Doctrine_Validator and gives full information of possible errors.

Available Drivers

Doctrine has drivers for every PDO-supported database. The supported databases are:

  • FreeTDS / Microsoft SQL Server / Sybase
  • Firebird/Interbase 6
  • Informix
  • Mysql
  • Oracle
  • Odbc
  • PostgreSQL
  • Sqlite

Creating Connections

// bootstrap.php
$ conn = Doctrine_Manager::connection('mysql://username:password@localhost/test', 'connection 1');

We have already created a new connection in the previous chapters. You can skip the above step and use the connection we've already created. You can retrieve it by using the Doctrine_Manager::connection method.

Flushing the Connection

When you create new User records you can flush the connection and save all un-saved objects for that connection. Below is an example:

$ conn = Doctrine_Manager::connection();

$ user1 = new User();
$ user1->username = 'Jack';

$ user2 = new User();
$ user2->username = 'jwage';

$ conn->flush();

Calling Doctrine_Connection::flush will save all unsaved record instances for that connection. You could of course optionally call save on each record instance and it would be the same thing.

$ user1->save();
$ user2->save();

Table

Doctrine_Table holds the schema information specified by the given component (record). For example if you have a User class that extends Doctrine_Record, each schema definition call gets delegated to a unique table object that holds the information for later use.

Each Doctrine_Table is registered by Doctrine_Connection. You can retrieve the table object for each component easily which is demonstrated right below.

For example, lets say we want to retrieve the table object for the User class. We can do this by simply giving User as the first argument for the Doctrine_Core::getTable method.

Getting a Table Object

In order to get table object for specified record just call Doctrine_Record::getTable.

// test.php
$ accountTable = Doctrine_Core::getTable('Account');

Getting Column Information

You can retrieve the column definitions set in Doctrine_Record by using the appropriate Doctrine_Table methods. If you need all information of all columns you can simply use:

// test.php
$ columns = $accountTable->getColumns();

foreach ($columns as $column) {
    print_r($column);
}

The above example would output the following when executed:

1$ php test.php Array ( [type] => integer [length] => 20 [autoincrement] => 1 [primary] => 1 ) Array ( [type] => string [length] => 255 ) Array ( [type] => decimal [length] => 18 )
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Sometimes this can be an overkill. The following example shows how to retrieve the column names as an array:

// test.php
$ names = $accountTable->getColumnNames();
print_r($names);

The above example would output the following when executed:

1$ php test.php Array ( [0] => id [1] => name [2] => amount )
2
3
4
5
6

Getting Relation Information

You can also get an array of all the Doctrine_Relation objects by simply calling Doctrine_Table::getRelations like the following:

// test.php
$ userTable = Doctrine_Core::getTable('User');
$ relations = $userTable->getRelations();
foreach ($relations as $name => $relation) {
    echo $name . ":\n";
    echo "Local - " . $relation->getLocal() . "\n";
    echo "Foreign - " .    $relation->getForeign() . "\n\n";
}

The above example would output the following when executed:

1$ php test.php Email: Local - id Foreign - user_id Phonenumbers: Local - id Foreign - user_id Groups: Local - user_id Foreign - group_id Friends: Local - user1 Foreign - user2 Addresses: Local - id Foreign - user_id Threads: Local - id Foreign - user_id
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

You can get the Doctrine_Relation object for an individual relationship by using the Doctrine_Table::getRelation method.

// test.php
$ relation = $userTable->getRelation('Phonenumbers');

echo 'Name: ' . $relation['alias'] . "\n";
echo 'Local - ' . $relation['local'] . "\n";
echo 'Foreign - ' .  $relation['foreign'] . "\n";
echo 'Relation Class - ' . get_class($relation);

The above example would output the following when executed:

1$ php test.php Name: Phonenumbers Local - id Foreign - user_id Relation Class - Doctrine_Relation_ForeignKey
2
3
4
5

Notice how in the above examples the $relation variable holds an instance of Doctrine_Relation_ForeignKey yet we can access it like an array. This is because, like many Doctrine classes, it implements ArrayAccess.

You can debug all the information of a relationship by using the print_r to inspect it.

$ array = $relation->toArray();
print_r($array);

Finder Methods

Doctrine_Table provides basic finder methods. These finder methods are very fast to write and should be used if you only need to fetch data from one database table. If you need queries that use several components (database tables) use Doctrine_Connection::query.

You can easily find an individual user by its primary key by using the find method:

$ user = $userTable->find(2);
print_r($user->toArray());

The above example would output the following when executed:

1$ php test.php Array ( [id] => 2 [is_active] => 1 [is_super_admin] => 0 [first_name] => [last_name] => [username] => jwage [password] => [type] => [created_at] => 2009-01-21 13:29:12 [updated_at] => 2009-01-21 13:29:12 )
2
3
4
5
6
7
8
9
10
11
12
13

You can also use the findAll method to retrieve a collection of all User records in the database:

foreach ($userTable->findAll() as $user) {
    echo $user->username . "\n";
}

The above example would output the following when executed:

1$ php test.php Jack jwage
2
3

The findAll method is not recommended as it will Return all records in the database and if you need to retrieve information from relationships it will lazily load that data causing high query counts. You can learn how to retrieve records and their related records efficiently by reading the DQL: Doctrine Query Language chapter.

You can also retrieve a set of records with a DQL where condition by using the findByDql method:

$ users = $userTable->findByDql('username LIKE ?', '%jw%');

foreach($users as $user) {
    echo $user->username . "";
}

The above example would output the following when executed:

1$ php test.php jwage
2

Doctrine also offers some additional magic finder methods that can be read about in the [doc dql-doctrine-query-language:magic-finders :name] section of the DQL chapter.

All of the finders below provided by Doctrine_Table use instances of Doctrine_Query for executing the queries. The objects are built dynamically internally and executed.

Using Doctrine_Query instances are highly recommend when accessing multiple objects through relationships. If you don't you will have high query counts as the data will be lazily loaded. You can read more about this in the DQL: Doctrine Query Language chapter.

Custom Table Classes

Adding custom table classes is very easy. Only thing you need to do is name the classes as [componentName]Table and make them extend Doctrine_Table. So for the User model we would create a class like the following:

// models/UserTable.php
class UserTable extends Doctrine_Table
{
}

Custom Finders

You can add custom finder methods to your custom table object. These finder methods may use fast Doctrine_Table finder methods or [doc dql-doctrine-query-language DQL API] (Doctrine_Query::create).

// models/UserTable.php
class UserTable extends Doctrine_Table
{
    public function findByName($name)
    {
        return Doctrine_Query::create()
                ->from('User u')
                ->where('u.name LIKE ?', "%$name%")
                ->execute();
    }
}

Doctrine will check if a child Doctrine_Table class called UserTable exists when calling getTable and if it does, it will return an instance of that instead of the default Doctrine_Table.

In order for custom {{Doctrine_Table}} classes to be loaded you must enable the autoload_table_classes attribute in your bootstrap.php file like done below.

// boostrap.php
// ...
$ manager->setAttribute(Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES, true);

Now when we ask for the User table object we will get the following:

$ userTable = Doctrine_Core::getTable('User');

echo get_class($userTable); // UserTable

$ users = $userTable->findByName("Jack");

The above example where we add a {{findByName()}} method is made possible automatically by the magic finder methods. You can read about them in the [doc dql-doctrine-query-language:magic-finders :name] section of the DQL chapter.

Record

Doctrine represents tables in your RDBMS with child Doctrine_Record classes. These classes are where you define your schema information, options, attributes, etc. Instances of these child classes represents records in the database and you can get and set properties on these objects.

Properties

Each assigned column property of Doctrine_Record represents a database table column. You will learn more about how to define your models in the Defining Models chapter.

Now accessing the columns is easy:

// test.php
$ userTable = Doctrine_Core::getTable('User');
$ user = $userTable->find(1);
  • Access property through overloading:

    // ...
    echo $user->username;
  • Access property with get():

    // ...
    echo $user->get('username);
  • Access property with ArrayAccess:

    // ...
    echo $user['username'];

The recommended way to access column values is by using the ArrayAccess as it makes it easy to switch between record and array fetching when needed.

Iterating through the properties of a record can be done in similar way as iterating through an array - by using the foreach construct. This is possible since Doctrine_Record implements a magic IteratorAggregate interface.

foreach ($user as $field => $value) {
    echo $field . ': ' . $value . "";
}

As with arrays you can use the isset for checking if given property exists and unset for setting given property to null.

We can easily check if a property named 'name' exists in a if conditional:

if (isset($user['username'])) {
}

If we want to unset the name property we can do it using the unset function in php:

unset($user['username']);

When you have set values for record properties you can get an array of the modified fields and values using Doctrine_Record::getModified:

// test.php
$ user['username'] = 'Jack Daniels';
print_r($user->getModified());

The above example would output the following when executed:

1$ php test.php Array ( [username] => Jack Daniels )
2
3
4

You can also simply check if a record is modified by using the Doctrine_Record::isModified method:

echo $user->isModified() ? 'Modified' : 'Not Modified';

Sometimes you may want to retrieve the column count of given record. In order to do this you can simply pass the record as an argument for the Doctrine_Record implements a magic Countable interface. The other way would be calling the count method.

echo $record->count();
echo count($record);

Doctrine_Record offers a special method for accessing the identifier of given record. This method is called identifier and it returns an array with identifier field names as keys and values as the associated property values.

$ user['username'] = 'Jack Daniels';
$ user->save();

print_r($user->identifier()); // array('id' => 1)

A common case is that you have an array of values which you need to assign to a given record. It may feel awkward and clumsy to set these values separately. No need to worry though, Doctrine_Record offers a way for merging a given array or record to another

The merge method iterates through the properties of the given record or array and assigns the values to the object

$ values = array(
    'username' => 'someone',
    'age' => 11,
);

$ user->merge($values);

echo $user->username; // someone
echo $user->age; // 11

You can also merge a one records values in to another like the following:

$ user1 = new User();
$ user1->username = 'jwage';

$ user2 = new User();
$ user2->merge($user1);

echo $user2->username; // jwage

fromArray method which is identical to merge and only exists for consistency with the toArray method.

Updating Records

Updating objects is very easy, you just call the Doctrine_Record::save method. The other way is to call Doctrine_Connection::flush which saves all objects. It should be noted though that flushing is a much heavier operation than just calling save method.

$ userTable = Doctrine_Core::getTable('User');
$ user = $userTable->find(2);

if ($user !== false) {
    $user->username = 'Jack Daniels';
    $user->save();
}

Sometimes you may want to do a direct update. In direct update the objects aren't loaded from database, rather the state of the database is directly updated. In the following example we use DQL UPDATE statement to update all users.

Run a query to make all user names lowercase:

$ q = Doctrine_Query::create()
        ->update('User u')
        ->set('u.username', 'LOWER(u.name)');

$ q->execute();

You can also run an update using objects if you already know the identifier of the record. When you use the Doctrine_Record::assignIdentifier method it sets the record identifier and changes the state so that calling Doctrine_Record::save performs an update instead of insert.

$ user = new User();
$ user->assignIdentifier(1);
$ user->username = 'jwage';
$ user->save();

Replacing Records

Replacing records is simple. If you instantiate a new object and save it and then late instantiate another new object with the same primary key or unique index value which already exists in the database, then it will replace/update that row in the database instead of inserting a new one. Below is an example.

First, imagine a User model where username is a unique index.

// test.php
$ user = new User();
$ user->username = 'jwage';
$ user->password = 'changeme';
$ user->save();

Issues the following query

1INSERT INTO user (username, password) VALUES (?, ?), ('jwage', 'changeme')
2
3
4
5

Now lets create another new object and set the same username but a different password.

$ user = new User();
$ user->username = 'jwage';
$ user->password = 'newpassword';
$ user->replace();

Issues the following query

1REPLACE INTO user (id, username, password) VALUES (?, ?, ?), (null, 'jwage', 'newpassword')
2
3
4
5

The record is replaced/updated instead of a new one being inserted

Refreshing Records

Sometimes you may want to refresh your record with data from the database, use Doctrine_Record::refresh.

$ user = Doctrine_Core::getTable('User')->find(2);
$ user->username = 'New name';

Now if you use the Doctrine_Record::refresh method it will select the data from the database again and update the properties of the instance.

$ user->refresh();

Refreshing relationships

The Doctrine_Record::refresh method can also refresh the already loaded record relationships, but you need to specify them on the original query.

First lets retrieve a User with its associated Groups:

$ q = Doctrine_Query::create()
        ->from('User u')
        ->leftJoin('u.Groups')
        ->where('id = ?');

$ user = $q->fetchOne(array(1));

Now lets retrieve a Group with its associated Users:

$ q = Doctrine_Query::create()
        ->from('Group g')
        ->leftJoin('g.Users')
        ->where('id = ?');

$ group = $q->fetchOne(array(1));

Now lets link the retrieved User and Group through a UserGroup instance:

$ userGroup = new UserGroup();
$ userGroup->user_id = $user->id;
$ userGroup->group_id = $group->id;
$ userGroup->save();

You can also link a User to a Group in a much simpler way, by simply adding the Group to the User. Doctrine will take care of creating the UserGroup instance for you automatically:

$ user->Groups[] = $group;
$ user->save()

Now if we call Doctrine_Record::refresh(true) it will refresh the record and its relationships loading the newly created reference we made above:

$ user->refresh(true);
$ group->refresh(true);

You can also lazily refresh all defined relationships of a model using Doctrine_Record::refreshRelated:

$ user = Doctrine_Core::getTable('User')->findOneByName('jon');
$ user->refreshRelated();

If you want to refresh an individual specified relationship just pass the name of a relationship to the refreshRelated function and it will lazily load the relationship:

$ user->refreshRelated('Phonenumber');

Deleting Records

Deleting records in Doctrine is handled by Doctrine_Record::delete, Doctrine_Collection::delete and Doctrine_Connection::delete methods.

$ userTable = Doctrine_Core::getTable("User");

$ user = $userTable->find(2);

// deletes user and all related composite objects
if($user !== false) {
    $user->delete();
}

If you have a Doctrine_Collection of User records you can call delete and it will loop over all records calling Doctrine_Record::delete for you.

$ users = $userTable->findAll();

Now you can delete all users and their related composite objects by calling Doctrine_Collectiondelete. It will loop over all Users in the collection calling delete one each one:

$ users->delete();

Using Expression Values

There might be situations where you need to use SQL expressions as values of columns. This can be achieved by using Doctrine_Expression which converts portable DQL expressions to your native SQL expressions.

Lets say we have a class called event with columns timepoint(datetime) and name(string). Saving the record with the current timestamp can be achieved as follows:

// test.php
$ user = new User();
$ user->username = 'jwage';
$ user->updated_at = new Doctrine_Expression('NOW()');
$ user->save();

The above code would issue the following SQL query:

1INSERT INTO user (username, updated_at) VALUES ('jwage', NOW())

When you use Doctrine_Expression with your objects in order to get the updated value you will have to manually call refresh to get the updated value from the database.

$ user->refresh();

Getting Record State

Every Doctrine_Record has a state. First of all records can be transient or persistent. Every record that is retrieved from database is persistent and every newly created record is considered transient. If a Doctrine_Record is retrieved from database but the only loaded property is its primary key, then this record has a state called proxy.

Every transient and persistent Doctrine_Record is either clean or dirty. Doctrine_Record is clean when none of its properties are changed and dirty when at least one of its properties has changed.

A record can also have a state called locked. In order to avoid infinite recursion in some rare circular reference cases Doctrine uses this state internally to indicate that a record is currently under a manipulation operation.

Below is a table containing all the different states a record can be in with a short description of it:

Name Description
Doctrine_Record::STATE_PROXY Record is i proxy state meaning its persistent but not all of its properties are loaded from the database.
Doctrine_Record::STATE_TCLEAN Record is t nsient clean, meaning its transient and none of its properties are changed.
Doctrine_Record::STATE_TDIRTY Record is t nsient dirty, meaning its transient and some of its properties are changed.
Doctrine_Record::STATE_DIRTY Record is d ty, meaning its persistent and some of its properties are changed.
Doctrine_Record::STATE_CLEAN Record is c an, meaning its persistent and none of its properties are changed.
Doctrine_Record::STATE_LOCKED Record is l ked.

You can easily get the state of a record by using the Doctrine_Record::state method:

$ user = new User();

if ($user->state() == Doctrine_Record::STATE_TDIRTY) {
    echo 'Record is transient dirty';
}

values specified in the schema. If we use an object that has no default values and instantiate a new instance it will return TCLEAN.

$ account = new Account();

if ($account->state() == Doctrine_Record::STATE_TCLEAN) {
    echo 'Record is transient clean';
}

Getting Object Copy

Sometimes you may want to get a copy of your object (a new object with all properties copied). Doctrine provides a simple method for this: Doctrine_Record::copy.

$ copy = $user->copy();

Notice that copying the record with copy returns a new record (state TDIRTY) with the values of the old record, and it copies the relations of that record. If you do not want to copy the relations too, you need to use copy(false).

Get a copy of user without the relations:

$ copy = $user->copy(false);

Using the PHP clone functionality simply uses this copy functionality internally:

$ copy = clone $user;

Saving a Blank Record

By default Doctrine doesn't execute when save is being called on an unmodified record. There might be situations where you want to force-insert the record even if it has not been modified. This can be achieved by assigning the state of the record to Doctrine_Record::STATE_TDIRTY:

$ user = new User();
$ user->state('TDIRTY');
$ user->save();

When setting the state you can optionally pass a string for the state and it will be converted to the appropriate state constant. In the example above, TDIRTY is actually converted to Doctrine_Record::STATE_TDIRTY.

Mapping Custom Values

There might be situations where you want to map custom values to records. For example values that depend on some outer sources and you only want these values to be available at runtime not persisting those values into database. This can be achieved as follows:

$ user->mapValue('isRegistered', true);

$ user->isRegistered; // true

Serializing

Sometimes you may want to serialize your record objects (possibly for caching purposes):

$ string = serialize($user);

$ user = unserialize($string);

Checking Existence

Very commonly you'll need to know if given record exists in the database. You can use the exists method for checking if given record has a database row equivalent:

$ record = new User();

echo $record->exists() ? 'Exists' : 'Does Not Exist'; // Does Not Exist

$ record->username = 'someone'; $record->save();

echo $record->exists() ? 'Exists' : 'Does Not Exist'; // Exists

Function Callbacks for Columns

Doctrine_Record offers a way for attaching callback calls for column values. For example if you want to trim certain column, you can simply use:

$ record->call('trim', 'username');

Collection

Doctrine_Collection is a collection of records (see Doctrine_Record). As with records the collections can be deleted and saved using Doctrine_Collection::delete and Doctrine_Collection::save accordingly.

When fetching data from database with either DQL API (see Doctrine_Query) or rawSql API (see Doctrine_RawSql) the methods return an instance of Doctrine_Collection by default.

The following example shows how to initialize a new collection:

$ users = new Doctrine_Collection('User');

Now add some new data to the collection:

$ users[0]->username = 'Arnold';
$ users[1]->username = 'Somebody';

Now just like we can delete a collection we can save it:

$ users->save();

Accessing Elements

You can access the elements of Doctrine_Collection with get methods or with ArrayAccess interface.

$ userTable = Doctrine_Core::getTable('User');
$ users = $userTable->findAll();
  • Accessing elements with ArrayAccess interface:

    $ users[0]->username = "Jack Daniels";
    $ users[1]->username = "John Locke";
  • Accessing elements with get:

    $ echo $users->get(1)->username;
    

Adding new Elements

When accessing single elements of the collection and those elements (records) don't exist Doctrine auto-adds them.

In the following example we fetch all users from database (there are 5) and then add couple of users in the collection.

As with PHP arrays the indexes start from zero.

// test.php
$ users = $userTable->findAll();

echo count($users); // 5

$ users[5]->username = "new user 1";
$ users[6]->username = "new user 2";

You could also optionally omit the 5 and 6 from the array index and it will automatically increment just as a PHP array would:

$ users[]->username = 'new user 3'; // key is 7
$ users[]->username = 'new user 4'; // key is 8

Getting Collection Count

The Doctrine_Collection::count method returns the number of elements currently in the collection:

$ users = $userTable->findAll();

echo $users->count();

Since Doctrine_Collection implements Countable interface a valid alternative for the previous example is to simply pass the collection as an argument for the count() function:

echo count($users);

Saving the Collection

Similar to Doctrine_Record the collection can be saved by calling the save gets called Doctrine issues save operations an all records and wraps the whole procedure in a transaction.

$ users = $userTable->findAll();

$ users[0]->username = 'Jack Daniels';
$ users[1]->username = 'John Locke';

$ users->save();

Deleting the Collection

Doctrine Collections can be deleted in very same way is Doctrine Records you just call delete method. As for all collections Doctrine knows how to perform single-shot-delete meaning it only performs one database query for the each collection.

For example if we have collection of users. When deleting the collection of users doctrine only performs one query for this whole transaction. The query would look something like:

1DELETE FROM user WHERE id IN (1, 2, 3, ... , N)

Key Mapping

Sometimes you may not want to use normal indexing for collection elements. For example in some cases mapping primary keys as collection keys might be useful. The following example demonstrates how this can be achieved.

  • Map the id column:

    // test.php
    $ userTable = Doctrine_Core::getTable('User');
    $ userTable->setAttribute(Doctrine_Core::ATTR_COLL_KEY, 'id');
  • Now user collections will use the values of id column as element indexes:

    $ users = $userTable->findAll();
    
    foreach($users as $id => $user) {
        echo $id . $user->username;
    }
  • You may want to map the name column:

    $ userTable = Doctrine_Core::getTable('User');
    $ userTable->setAttribute(Doctrine_Core::ATTR_COLL_KEY, 'username');
  • Now user collections will use the values of name column as element indexes:

    $ users = $userTable->findAll();
    
    foreach($users as $username => $user) {
        echo $username . ' - ' . $user->created_at . "";
    }

Note this would only be advisable if the username column is specified as unique in your schema otherwise you will have cases where data cannot be hydrated properly due to duplicate collection keys.

Validator

Validation in Doctrine is a way to enforce your business rules in the model part of the MVC architecture. You can think of this validation as a gateway that needs to be passed right before data gets into the persistent data store. The definition of these business rules takes place at the record level, that means in your active record model classes (classes derived from Doctrine_Record). The first thing you need to do to be able to use this kind of validation is to enable it globally. This is done through the Doctrine_Manager.

// bootstrap.php
$ manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL);

Once you enabled validation, you'll get a bunch of validations automatically:

  • Data type validations: All values assigned to columns are checked for the right type. That means if you specified a column of your record as type 'integer', Doctrine will validate that any values assigned to that column are of this type. This kind of type validation tries to be as smart as possible since PHP is a loosely typed language. For example 2 as well as "7" are both valid integers whilst "3f" is not. Type validations occur on every column (since every column definition needs a type).
  • Length validation: As the name implies, all values assigned to columns are validated to make sure that the value does not exceed the maximum length.

You can combine the following constants by using bitwise operations: VALIDATE_ALL, VALIDATE_TYPES, VALIDATE_LENGTHS, VALIDATE_CONSTRAINTS, VALIDATE_NONE.

For example to enable all validations except length validations you would use:

// bootstrap.php
$ manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, VALIDATE_ALL & ~VALIDATE_LENGTHS);

You can read more about this topic in the Data Validation chapter.

More Validation

The type and length validations are handy but most of the time they're not enough. Therefore Doctrine provides some mechanisms that can be used to validate your data in more detail.

Validators are an easy way to specify further validations. Doctrine has a lot of predefined validators that are frequently needed such as email, country, ip, range and regexp validators. You find a full list of available validators in the [doc data-validation :name] chapter. You can specify which validators apply to which column through the 4th argument of the hasColumn method. If that is still not enough and you need some specialized validation that is not yet available as a predefined validator you have three options:

  • You can write the validator on your own.
  • You can propose your need for a new validator to a Doctrine developer.
  • You can use validation hooks.

The first two options are advisable if it is likely that the validation is of general use and is potentially applicable in many situations. In that case it is a good idea to implement a new validator. However if the validation is special it is better to use hooks provided by Doctrine:

  • validate (Executed every time the record gets validated)
  • validateOnInsert (Executed when the record is new and gets validated)
  • validateOnUpdate (Executed when the record is not new and gets validated)

If you need a special validation in your active record you can simply override one of these methods in your active record class (a descendant of Doctrine_Record). Within these methods you can use all the power of PHP to validate your fields. When a field does not pass your validation you can then add errors to the record's error stack. The following code snippet shows an example of how to define validators together with custom validation:

// models/User.php
class User extends BaseUser
{
    protected function validate()
    {
        if ($this->username == 'God') {
            // Blasphemy! Stop that! ;-)
            // syntax: add(, )
            $errorStack = $this->getErrorStack();
            $errorStack->add('name', 'You cannot use this username!');
        }
    }
}
// models/Email.php
class Email extends BaseEmail
{
    // ...

    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...

        // validators 'email' and 'unique' used
        $this->hasColumn('address', 'string', 150, array('email', 'unique'));
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

1# schema.yml Email: columns: address: type: string(150) email: true unique: true
2
3
4
5
6
7

Valid or Not Valid

Now that you know how to specify your business rules in your models, it is time to look at how to deal with these rules in the rest of your application.

Implicit Validation

Whenever a record is going to be saved to the persistent data store (i.e. through calling $record->save) the full validation procedure is executed. If errors occur during that process an exception of the type Doctrine_Validator_Exception will be thrown. You can catch that exception and analyze the errors by using the instance method Doctrine_Validator_Exception::getInvalidRecords. This method returns an ordinary array with references to all records that did not pass validation. You can then further explore the errors of each record by analyzing the error stack of each record. The error stack of a record can be obtained with the instance method Doctrine_Record::getErrorStack. Each error stack is an instance of the class Doctrine_Validator_ErrorStack. The error stack provides an easy to use interface to inspect the errors.

Explicit Validation

You can explicitly trigger the validation for any record at any time. For this purpose Doctrine_Record provides the instance method Doctrine_Record::isValid. This method returns a boolean value indicating the result of the validation. If the method returns false, you can inspect the error stack in the same way as seen above except that no exception is thrown, so you simply obtain the error stack of the record that didnt pass validation through Doctrine_Record::getErrorStack.

The following code snippet shows an example of handling implicit validation which caused a Doctrine_Validator_Exception.

// test.php
$ user = new User();

try {
    $user->username = str_repeat('t', 256);
    $user->Email->address = "drink@@notvalid..";
    $user->save();
} catch(Doctrine_Validator_Exception $e) {
    $userErrors = $user->getErrorStack();
    $emailErrors = $user->Email->getErrorStack();

    foreach($userErrors as $fieldName => $errorCodes) {
        echo $fieldName . " - " . implode(', ', $errorCodes) . "\n";
    }
    foreach($emailErrors as $fieldName => $errorCodes) {
        echo $fieldName . " - " . implode(', ', $errorCodes) . "\n";
    }
}

You could also use $e->getInvalidRecords. The direct way used above is just more simple when you know the records you're dealing with.

You can also retrieve the error stack as a nicely formatted string for easy use in your applications:

// test.php
echo $user->getErrorStackAsString();

It would output an error string that looks something like the following:

1Validation failed in class User 1 field had validation error: * 1 validator failed on username (length)
2
3
4
5

Profiler

Doctrine_Connection_Profiler is an event listener for Doctrine_Connection. It provides flexible query profiling. Besides the SQL strings the query profiles include elapsed time to run the queries. This allows inspection of the queries that have been performed without the need for adding extra debugging code to model classes.

Doctrine_Connection_Profiler can be enabled by adding it as an event listener for Doctrine_Connection:

$ profiler = new Doctrine_Connection_Profiler();

$ conn = Doctrine_Manager::connection();
$ conn->setListener($profiler);

Basic Usage

Perhaps some of your pages is loading slowly. The following shows how to build a complete profiler report from the connection:

// test.php
$ time = 0;
foreach ($profiler as $event) {
    $time += $event->getElapsedSecs();

    printf(
        "%s %f\n%s\n",
        $event->getName(),
        $event->getElapsedSecs(),
        $event->getQuery()
    );

    $params = $event->getParams();
    if (!empty($params)) {
        print_r($params);
    }
}
echo "Total time: " . $time . "\n";

Frameworks like symfony, Zend, etc. offer web debug toolbars that use this functionality provided by Doctrine for reporting the number of queries executed on every page as well as the time it takes for each query.

Locking Manager

The term 'Transaction' does not refer to database transactions here but to the general meaning of this term.

Locking is a mechanism to control concurrency. The two most well known locking strategies are optimistic and pessimistic locking. The following is a short description of these two strategies from which only pessimistic locking is currently supported by Doctrine.

Optimistic Locking

The state/version of the object(s) is noted when the transaction begins. When the transaction finishes the noted state/version of the participating objects is compared to the current state/version. When the states/versions differ the objects have been modified by another transaction and the current transaction should fail. This approach is called 'optimistic' because it is assumed that it is unlikely that several users will participate in transactions on the same objects at the same time.

Pessimistic Locking

The objects that need to participate in the transaction are locked at the moment the user starts the transaction. No other user can start a transaction that operates on these objects while the locks are active. This ensures that the user who starts the transaction can be sure that no one else modifies the same objects until he has finished his work.

Doctrine's pessimistic offline locking capabilities can be used to control concurrency during actions or procedures that take several HTTP request and response cycles and/or a lot of time to complete.

Examples

The following code snippet demonstrates the use of Doctrine's pessimistic offline locking capabilities.

At the page where the lock is requested get a locking manager instance:

// test.php
$ lockingManager = new Doctrine_Locking_Manager_Pessimistic();

Ensure that old locks which timed out are released before we try to acquire our lock 300 seconds = 5 minutes timeout. This can be done by using the releaseAgedLocks method:

// test.php
$ user = Doctrine_Core::getTable('User')->find(1);

try {
    $lockingManager->releaseAgedLocks(300);
    $gotLock = $lockingManager->getLock($user, 'jwage');

    if ($gotLock){
        echo "Got lock!";
    } else {
        echo "Sorry, someone else is currently working on this record";
    }
} catch(Doctrine_Locking_Exception $dle) {
    echo $dle->getMessage(); // handle the error
}

At the page where the transaction finishes get a locking manager instance:

// test.php
$ user = Doctrine_Core::getTable('User')->find(1);

$ lockingManager = new Doctrine_Locking_Manager_Pessimistic();

try {
    if ($lockingManager->releaseLock($user, 'jwage')) {
        echo "Lock released";
    } else {
        echo "Record was not locked. No locks released.";
    }
} catch(Doctrine_Locking_Exception $dle) {
    echo $dle->getMessage(); // handle the error
}

Technical Details

The pessimistic offline locking manager stores the locks in the database (therefore 'offline'). The required locking table is automatically created when you try to instantiate an instance of the manager and the ATTR_CREATE_TABLES is set to TRUE. This behavior may change in the future to provide a centralized and consistent table creation procedure for installation purposes.

Views

Database views can greatly increase the performance of complex queries. You can think of them as cached queries. Doctrine_View provides integration between database views and DQL queries.

Using Views

Using views on your database using Doctrine is easy. We provide a nice Doctrine_View class which provides functionality for creating, dropping and executing views.

The Doctrine_View class integrates with the Doctrine_Query class by saving the SQL that would be executed by Doctrine_Query.

First lets create a new Doctrine_Query instance to work with:

$ q = Doctrine_Query::create()
        ->from('User u')
        ->leftJoin('u.Phonenumber p')
        ->limit(20);

Now lets create the Doctrine_View instance and pass it the Doctrine_Query instance as well as a name for identifying that database view:

$ view = new Doctrine_View($q, 'RetrieveUsersAndPhonenumbers');

Now we can easily create the view by using the Doctrine_View::create method:

try {
    $view->create();
} catch (Exception $e) {
}

Alternatively if you want to drop the database view you use the Doctrine_View::drop method:

try {
    $view->drop();
} catch (Exception $e) {
}

Using views are extremely easy. Just use the Doctrine_View::execute for executing the view and returning the results just as a normal Doctrine_Query object would:

$ users = $view->execute();

foreach ($users as $user) {
    print_r($us->toArray());
}

Conclusion

We now have been exposed to a very large percentage of the core functionality provided by Doctrine. The next chapters of this book are documentation that cover some of the optional functionality that can help make your life easier on a day to day basis.

Lets move on to the next chapter where we can learn about how to use native SQL to hydrate our data in to arrays and objects instead of the Doctrine Query Language.