You are currently viewing the 1.1 version of Doctrine ORM for PHP.

Introduction

Code Examples

The text in this book contains lots of PHP code examples. All starting and ending PHP tags have been removed to reduce the length of the book. Be sure to include the PHP tags when you copy and paste the examples.

What is Doctrine?

Doctrine is an object relational mapper (ORM) for PHP 5.2.3+ that sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication.

What is an ORM?

Object relational mapping is a technique used in programming languages when dealing with databases for translating incompatible data types in relational databases. This essentially allows for us to have a "virtual object database," that can be used from the programming language. Lots of free and commercial packages exist that allow this but sometimes developers chose to create their own ORM.

What is the Problem?

We are faced with many problems when building web applications. Instead of trying to explain it all it is best to read what Wikipedia has to say about object relational mappers.

Pulled from Wikipedia:

Data management tasks in object-oriented (OO) programming are typically implemented by manipulating objects, which are almost always non-scalar values. For example, consider an address book entry that represents a single person along with zero or more phone numbers and zero or more addresses. This could be modeled in an object-oriented implementation by a "person object" with "slots" to hold the data that comprise the entry: the person's name, a list (or array) of phone numbers, and a list of addresses. The list of phone numbers would itself contain "phone number objects" and so on. The address book entry is treated as a single value by the programming language (it can be referenced by a single variable, for instance). Various methods can be associated with the object, such as a method to return the preferred phone number, the home address, and so on.

However, many popular database products such as SQL DBMS can only store and manipulate scalar values such as integers and strings organized within tables.

The programmer must either convert the object values into groups of simpler values for storage in the database (and convert them back upon retrieval), or only use simple scalar values within the program. Object-relational mapping is used to implement the first approach.

The height of the problem is translating those objects to forms that can be stored in the database for easy retrieval, while preserving the properties of the objects and their relationships; these objects are then said to be persistent.

Minimum Requirements

Doctrine requires PHP >= 5.2.3+, although it doesn't require any external libraries. For database function call abstraction Doctrine uses PDO which comes bundled with the PHP official release that you get from www.php.net.

If you use a 3 in 1 package under windows like Uniform Server, MAMP or any other non-official package, you may be required to perform additional configurations.

Basic Overview

Doctrine is a tool for object-relational mapping in PHP. It sits on top of PDO and is itself divided into two main layers, the DBAL and the ORM. The picture below shows how the layers of Doctrine work together.

Doctrine Layers

The DBAL(Database Abstraction Layer) completes and extends the basic database abstraction/independence that is already provided by PDO. The DBAL library can be used standalone, if all you want is a powerful database abstraction layer on top of PDO. The ORM layer depends on the DBAL and therefore, when you load the ORM package the DBAL is already included.

Doctrine Explained

The following section tries to explain where Doctrine stands in the world of ORM tools. The Doctrine ORM is mainly built around the Active Record, Data Mapper and Meta Data Mapping patterns.

Through extending a specific base class named Doctrine_Record, all the child classes get the typical ActiveRecord interface (save/delete/etc.) and it allows Doctrine to easily participate in and monitor the lifecycles of your records. The real work, however, is mostly forwarded to other components, like the Doctrine_Table class. This class has the typical Data Mapper interface, createQuery(), find(id), findAll(), findBy*(), findOneBy*() etc. So the ActiveRecord base class enables Doctrine to manage your records and provides them with the typical ActiveRecord interface whilst the mapping footwork is done elsewhere.

The ActiveRecord approach comes with its typical limitations. The most obvious is the enforcement for a class to extend a specific base class in order to be persistent (a Doctrine_Record). In general, the design of your domain model is pretty much restricted by the design of your relational model. There is an exception though. When dealing with inheritance structures, Doctrine provides some sophisticated mapping strategies which allow your domain model to diverge a bit from the relational model and therefore give you a bit more freedom.

Doctrine is in a continuous development process and we always try to add new features that provide more freedom in the modeling of the domain. However, as long as Doctrine remains mainly an ActiveRecord approach, there will always be a pretty large, (forced) similarity of these two models.

The current situation is depicted in the following picture.

Relational Bounds

As you see in the picture, the domain model can't drift far away from the bounds of the relational model.

After mentioning these drawbacks, it's time to mention some advantages of the ActiveRecord approach. Apart from the (arguably slightly) simpler programming model, it turns out that the strong similarity of the relational model and the Object Oriented (OO) domain model also has an advantage: It makes it relatively easy to provide powerful generation tools, that can create a basic domain model out of an existing relational schema. Further, as the domain model can't drift far from the relational model due to the reasons above, such generation and synchronization tools can easily be used throughout the development process. Such tools are one of Doctrine's strengths.

We think that these limitations of the ActiveRecord approach are not that much of a problem for the majority of web applications because the complexity of the business domains is often moderate, but we also admit that the ActiveRecord approach is certainly not suited for complex business logic (which is often approached using Domain-Driven Design) as it simply puts too many restrictions and has too much influence on your domain model.

Doctrine is a great tool to drive the persistence of simple or moderately complex domain models(1) and you may even find that it's a good choice for complex domain models if you consider the trade-off between making your domain model more database-centric and implementing all the mapping on your own (because at the time of this writing we are not aware of any powerful ORM tools for PHP that are not based on an ActiveRecord approach).

(1) Note that complexity != size. A domain model can be pretty large without being complex and vice versa. Obviously, larger domain models have a greater probability of being complex.

Now you already know a lot about what Doctrine is and what it is not. If you would like to dive in now and get started right away, jump straight to the next chapter "Getting Started".

Key Concepts

The Doctrine Query Language (DQL) is an object query language. It let's you express queries for single objects or full object graphs, using the terminology of your domain model: class names, field names, relations between classes, etc. This is a powerful tool for retrieving or even manipulating objects without breaking the separation of the domain model (field names, class names, etc) from the relational model (table names, column names, etc). DQL looks very much like SQL and this is intended because it makes it relatively easy to grasp for people knowing SQL. There are, however, a few very important differences you should always keep in mind:

Take this example DQL query:


FROM User u
LEFT JOIN u.Phonenumbers where u.level > 1

The things to notice about this query:

  • We select from classes and not tables. We are selecting from the User class/model.
  • We join along associations (u.Phonenumbers)
  • We can reference fields (u.level)
  • There is no join condition (ON x.y = y.x). The associations between your classes and how these are expressed in the database are known to Doctrine (You need to make this mapping known to Doctrine, of course. How to do that is explained later in the Defining Models chapter.).

DQL expresses a query in the terms of your domain model (your classes, the attributes they have, the relations they have to other classes, etc.).

It's very important that we speak about classes, fields and associations between classes here. User is not a table / table name . It may be that the name of the database table that the User class is mapped to is indeed named User but you should nevertheless adhere to this differentiation of terminology. This may sound nit picky since, due to the ActiveRecord approach, your relational model is often very similar to your domain model but it's really important. The column names are rarely the same as the field names and as soon as inheritance is involved, the relational model starts to diverge from the domain model. You can have a class User that is in fact mapped to several tables in the database. At this point it should be clear that talking about "selecting from the User table" is simply wrong then. And as Doctrine development continues there will be more features available that allow the two models to diverge even more.

Further Reading

For people new to object-relational mapping and (object-oriented) domain models we recommend the following literature:

The books by Martin Fowler cover a lot of the basic ORM terminology, the different approaches of modeling business logic and the patterns involved.

Another good read is about Domain Driven Design. Though serious Domain-Driven Design is currently not possible with Doctrine, this is an excellent resource for good domain modeling, especially in complex business domains, and the terminology around domain models that is pretty widespread nowadays is explained in depth (Entities, Value Objects, Repositories, etc).

Conclusion

Well, now that we have given a little educational reading about the methodologies and principals behind Doctrine we are pretty much ready to dive in to everything that is Doctrine. Lets dive in to setting up Doctrine in the Getting Started chapter.

Getting Started

Checking Requirements

First we need to make sure that you can run Doctrine on your server. We can do this one of two ways:

First create a small PHP script named phpinfo.php and upload it somewhere on your web server that is accessible to the web:

phpinfo();

Now execute it from your browser by going to http://localhost/phpinfo.php. You will see a list of information detailing your PHP configuration. Check that your PHP version is >= 5.2.3 and that you have PDO and the desired drivers installed.

You can also check your PHP installation has the necessary requirements by running some commands from the terminal. We will demonstrate in the next example.

Check that your PHP version is >= 5.2.3 with the following command:

$ php -v

Now check that you have PDO and the desired drivers installed with the following command:

$ php -i

You could also execute the phpinfo.php from the command line and get the same result as the above example:

$ php phpinfo.php

Checking the requirements are required in order to run the examples used throughout this documentation.

Installing

Currently it is possible to install Doctrine four different ways that are listed below:

  • SVN (subversion)
  • SVN externals
  • PEAR Installer
  • Download PEAR Package

It is recommended to download Doctrine via SVN (subversion), because in this case updating is easy. If your project is already under version control with SVN, you should choose SVN externals.

If you wish to just try out Doctrine in under 5 minutes, the sandbox package is recommended. We will discuss the sandbox package in the next section.

Sandbox

Doctrine also provides a special package which is a zero configuration Doctrine implementation for you to test Doctrine without writing one line of code. You can download it from the download page.

The sandbox implementation is not a recommend implementation for a production application. It's only purpose is for exploring Doctrine and running small tests.

SVN

It is highly recommended that you use Doctrine via SVN and the externals option. This option is the best as you will receive the latest bug fixes from SVN to ensure the best experience using Doctrine.

Installing

To install Doctrine via SVN is very easy. You can download any version of Doctrine from the SVN server: http://svn.doctrine-project.org

To check out a specific version you can use the following command from your terminal:

svn co http://svn.doctrine-project.org/branches/1.1 .

If you do not have a SVN client, chose one from the list below. Find the Checkout option and enter http://svn.doctrine-project.org/1.0 in the path or repository url parameter. There is no need for a username or password to check out Doctrine.

Updating

Updating Doctrine with SVN is just as easy as installing. Simply execute the following command from your terminal:

$ svn update

SVN Externals

If your project is already under version control with SVN, then it is recommended that you use SVN externals to install Doctrine.

You can start by navigating to your checked out project in your terminal:

$ cd /var/www/my_project

Now that you are under your checked out project, you can execute the following command from your terminal and setup Doctrine as an SVN external:

$ svn propedit svn:externals lib/vendor

The above command will open your editor and you need to place the following text inside and save:

doctrine http://svn.doctrine-project.org/branches/1.1/lib

Now you can install Doctrine by doing an svn update:

$ svn update

It will download and install Doctrine at the following path: /var/www/my_project/lib/vendor/doctrine

Don't forget to commit your change to the SVN externals.

$ svn commit

PEAR Installer

Doctrine also provides a PEAR server for installing and updating Doctrine on your servers. You can easily install Doctrine with the following command:

$ pear install pear.phpdoctrine.org/Doctrine-1.1.x

Replace the above 1.0.x with the version you wish to install. For example "1.1.0".

Download Pear Package

If you do not wish to install via PEAR or do not have PEAR installed, you can always just manually download the package from the website. Once you download the package to your server you can extract it using the following command under linux.

$ tar xzf Doctrine-1.1.0.tgz

Implementing

Now that you have Doctrine in your hands, we are ready to implement Doctrine in to our application. This is the first step towards getting started with Doctrine.

First create a directory named doctrine_test. This is where we will place all our test code:

$ mkdir doctrine_test
$ cd doctrine_test

Including Doctrine Libraries

The first thing we must do is find the Doctrine.php file containing the core class so that we can require it in to our application. The Doctrine.php file is in the lib folder from when you downloaded Doctrine in the previous section.

We need to move the Doctrine libraries in to the doctrine_test directory into a folder in doctrine_test/lib/vendor/doctrine:

$ mkdir lib
$ mkdir lib/vendor
$ mkdir lib/vendor/doctrine
$ mv /path/to/doctrine/lib doctrine

Or if you are using SVN, you can use externals:

$ svn co http://svn.doctrine-project.org/branches/1.1/lib lib/vendor/doctrine

Now add it to your svn externals:

$ svn propedit svn:externals lib/vendor

It will open up your editor and place the following inside and save:

doctrine http://svn.doctrine-project.org/branches/1.1/lib

Now when you do SVN update you will get the Doctrine libraries updated:

$ svn update lib/vendor

Require Doctrine Base Class

We need to create a php script for bootstrapping Doctrine and all the configuration for it. Create a file named bootstrap.php and place the following code in the file:

// bootstrap.php

/**
 * Bootstrap Doctrine.php, register autoloader specify
 * configuration attributes and load models.
 */

require_once(dirname(__FILE__) . '/lib/vendor/doctrine/Doctrine.php');

Register Autoloader

Now that we have the Doctrine class present, we need to register the class autoloader function in the bootstrap file:

// bootstrap.php

// ...
spl_autoload_register(array('Doctrine', 'autoload'));

Lets also create the singleton Doctrine_Manager instance and assign it to a variable named $manager:

// bootstrap.php

// ...
$manager = Doctrine_Manager::getInstance();

Autoloading Explained

You can read about the PHP autoloading on the php website here. Using the autoloader allows us to lazily load classes as they are requested instead of pre-loading all classes. This is a huge benefit to performance.

The way the Doctrine autoloader works is simple. Because our class names and paths are related, we can determine the path to a Doctrine class based on its name.

Imagine we have a class named Doctrine_Some_Class and we instantiate an instance of it:

$class = new Doctrine_Some_Class();

The above code will trigger a call to the Doctrine::autoload() function and pass it the name of the class instantiated. The class name string is manipulated and transformed in to a path and required. Below is some pseudo code that shows how the class is found and required:

class Doctrine
{
    public function autoload($className)
    {
        $classPath = str_replace('_', '/', $className) . '.php';
        $path = '/path/to/doctrine/' . $classPath;
        require_once($path);
        return true;
    }
}

In the above example the Doctrine_Some_Class can be found at /path/to/doctrine/Doctrine/Some/Class.php.

Obviously the real Doctrine::autoload() function is a bit more complex and has some error checking to ensure the file exists but the above code demonstrates how it works.

Bootstrap File

We will use this bootstrap class in later chapters and sections so be sure to create it!

The bootstrap file we have created should now look like the following:

// bootstrap.php

/**
 * Bootstrap Doctrine.php, register autoloader specify
 * configuration attributes and load models.
 */

require_once(dirname(__FILE__) . '/lib/vendor/doctrine/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$manager = Doctrine_Manager::getInstance();

This new bootstrapping file will be referenced several times in this book as it is where we will make changes to our implementation as we learn how to use Doctrine step by step.

The configuration attributes mentioned above are a feature in Doctrine used for configuring and controlling functionality. You will learn more about attributes and how to get/set them in the Configuration chapter.

Test Script

Now lets create a simple test script that we can use to run various tests as we learn about the features of Doctrine.

Create a new file in the doctrine_test directory named test.php and place the following code inside:

// test.php

require_once('bootstrap.php');

echo Doctrine::getPath();

Now you can execute the test script from your command line. This is how we will perform tests with Doctrine throughout the chapters so make sure it is working for you! It should output the path to your Doctrine installation.

$ php test.php
/path/to/doctrine/lib

Conclusion

Phew! This was our first chapter where we actually got into some code. As you saw, first we were able to check that our server can actually run Doctrine. Then we learned all the different ways we can download and install Doctrine. Lastly we learned how to implement Doctrine by setting up a small test environment that we will use to perform some exercises in the remaining chapters of the book.

Now lets move on and get our first taste of Doctrine connections in the Introduction to Connections chapter.

Introduction to Connections

DSN, the Data Source Name

In order to connect to a database through Doctrine, you have to create a valid DSN(Data Source Name).

Doctrine supports both PEAR DB/MDB2 like data source names as well as PDO style data source names. The following section deals with PEAR like data source names. If you need more info about the PDO-style data source names see the documentation on pdo.

The DSN consists in the following parts:

DSN part Description
phptype Database backend used in PHP (i.e. mysql , pgsql etc.)
dbsyntax Database used with regards to SQL syntax etc.
protocol Communication protocol to use ( i.e. tcp, unix etc.)
hostspec Host specification (hostname[:port])
database Database to use on the DBMS server
username User name for login
password Password for login
proto_opts Maybe used with protocol
option Additional connection options in URI query string format. Options are separated by ampersand (&). The Following table shows a non complete list of options:

List of options

Name Description
charset Some backends support setting the client charset.
new_link Some RDBMS do not create new connections when connecting to the same host multiple times. This option will attempt to force a new connection.

The DSN can either be provided as an associative array or as a string. The string format of the supplied DSN is in its fullest form:

phptype(dbsyntax)://username:password@protocol+hostspec/database?option=value

Most variations are allowed:

phptype://username:password@protocol+hostspec:110//usr/db_file.db
phptype://username:password@hostspec/database
phptype://username:password@hostspec
phptype://username@hostspec
phptype://hostspec/database
phptype://hostspec
phptype:///database
phptype:///database?option=value&anotheroption=anothervalue
phptype(dbsyntax)
phptype

The currently supported PDO database drivers are:

Driver name Supported databases
fbsql FrontBase
ibase InterBase / Firebird (requires PHP 5)
mssql Microsoft SQL Server (NOT for Sybase. Compile PHP --with-mssql)
mysql MySQL
mysqli MySQL (supports new authentication protocol) (requires PHP 5)
oci Oracle 7/8/9/10
pgsql PostgreSQL
querysim QuerySim
sqlite SQLite 2

A second DSN format supported is

phptype(syntax)://user:pass@protocol(proto_opts)/database

If your database, option values, username or password contain characters used to delineate DSN parts, you can escape them via URI hex encodings:

Character Hex Code
: %3a
/ %2f
@ %40
+ %2b
( %28
) %29
? %3f
= %3d
& %26

Please note, that some features may be not supported by all database drivers.

Examples

Example 1. Connect to database through a socket

mysql://user@unix(/path/to/socket)/pear

Example 2. Connect to database on a non standard port

pgsql://user:pass@tcp(localhost:5555)/pear

If you use, the ip address {127.0.0.1}, the port parameter is ignored (default: 3306).

Example 3. Connect to SQLite on a Unix machine using options

sqlite:////full/unix/path/to/file.db?mode=0666

Example 4. Connect to SQLite on a Windows machine using options

sqlite:///c:/full/windows/path/to/file.db?mode=0666

Example 5. Connect to MySQLi using SSL

mysqli://user:pass@localhost/pear?key=client-key.pem&cert=client-cert.pem

Opening New Connections

Opening a new database connection in Doctrine is very easy. If you wish to use PDO you can just initialize a new PDO object.

Remember our bootstrap.php file we created in the Getting Started chapter? Under the code where we registered the Doctrine autoloader we are going to instantiate our new connection:

// bootstrap.php

// ...
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';

$dbh = new PDO($dsn, $user, $password);
$conn = Doctrine_Manager::connection($dbh);

Directly passing a PDO instance to Doctrine_Manager::connection() will not allow Doctrine to be aware of the username and password for the connection, since their is no way to retrieve it from an existing PDO instance. The username and password is required in order for Doctrine to be able to create and drop databases. To get around this you can manually set the username and password option directly on the $conn object.

// bootstrap.php

// ...
$conn->setOption('username', $user);
$conn->setOption('password', $password);

Lazy Database Connecting

Lazy-connecting to database can save a lot of resources. There might be many times where you don't need an actual database connection, hence its always recommended to use lazy-connecting (that means Doctrine will only connect to database when needed).

This feature can be very useful when using for example page caching, hence not actually needing a database connection on every request. Remember connecting to database is an expensive operation.

In the example below we will show you when you create a new Doctrine connection, the connection to the database isn't created until it is actually needed.

// bootstrap.php

// ...

// At this point no actual connection to the database is created
$conn = Doctrine_Manager::connection('mysql://username:password@localhost/test');

// The first time the connection is needed, it is instantiated
// This query triggers the connection to be created
$conn->execute('SHOW TABLES');

Testing your Connection

After reading the previous sections of this chapter, you should now know how to create a connection. So, lets modify our bootstrap file to include the initialization of a connection. For this example we will just be using a sqlite memory database but you can use whatever type of database connection you prefer.

Add your database connection to bootstrap.php and it should look something like the following:

/**
 * Bootstrap Doctrine.php, register autoloader and specify
 * configuration attributes
 */

require_once('../doctrine/branches/1.0/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$manager = Doctrine_Manager::getInstance();

$conn = Doctrine_Manager::connection('sqlite::memory:', 'doctrine');

To test the connection lets modify our test.php script and perform a small test. Since we create a variable name $conn, that variable is available to the test script so lets setup a small test to make sure our connection is working:

First lets create a test table and insert a record:

// test.php

// ...
$conn->export->createTable('test', array('name' => array('type' => 'string')));
$conn->execute('INSERT INTO test (name) VALUES (?)', array('jwage'));

Now lets execute a simple SELECT query from the test table we just created to make sure the data was inserted and that we can retrieve it:

// test.php

// ...
$stmt = $conn->prepare('SELECT * FROM test');
$stmt->execute();
$results = $stmt->fetchAll();
print_r($results);

Execute test.php from your terminal and you should see:

$ php test.php
Array
(
    [0] => Array
        (
            [name] => jwage
            [0] => jwage
        )

)

Conclusion

Great! Now we learned some basic operations of Doctrine connections. We have modified our Doctrine test environment to have a new connection. This is required because the examples in the coming chapters will require a connection.

Lets move on to the Configuration chapter and learn how you can control functionality and configurations using the Doctrine attribute system.

Configuration

Doctrine controls configuration of features and functionality using attributes. In this section we will discuss how to set and get attributes as well as an overview of what attributes exist for you to use to control Doctrine functionality.

Levels of Configuration

Doctrine has a three-level configuration structure. You can set configuration attributes at a global, connection and table level. If the same attribute is set on both lower level and upper level, the uppermost attribute will always be used. So for example if a user first sets default fetchmode in global level to Doctrine::FETCH_BATCH and then sets a table fetchmode to Doctrine::FETCH_LAZY, the lazy fetching strategy will be used whenever the records of that table are being fetched.

  • Global level - The attributes set in global level will affect every connection and every table in each connection.
  • Connection level - The attributes set in connection level will take effect on each table in that connection.
  • Table level - The attributes set in table level will take effect only on that table.

In the following example we set an attribute at the global level:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);

In the next example above we override the global attribute on given connection:

// bootstrap.php

// ...
$conn->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_NONE);

In the last example we override once again the connection level attribute in the table level:

// bootstrap.php

// ...
$table = Doctrine::getTable('User');

$table->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);

We haven't introduced the above used Doctrine::getTable() method. You will learn more about the table objects used in Doctrine in the Table section of the next chapter.

Defaults Attributes

Doctrine has a few specific attributes available that allow you to specify the default values of things that in the past were hardcoded values. Such as default column length, default column type, etc.

Default Column Options

It is possible to specify an array of default options to be used on every column in your model.

// bootstrap.php

// ...

$manager->setAttribute(Doctrine::ATTR_DEFAULT_COLUMN_OPTIONS,
  array('type' => 'string', 'length' => 255, 'notnull' => true));

Default Added Auto Id

You can customize the properties of the automatically added primary key in Doctrine models.

$manager->setAttribute(Doctrine::ATTR_DEFAULT_IDENTIFIER_OPTIONS,
  array('name' => '%s_id', 'type' => 'string', 'length' => 16));

The %s string in the name is replaced with the table name.

Portability

Each database management system (DBMS) has it's own behaviors. For example, some databases capitalize field names in their output, some lowercase them, while others leave them alone. These quirks make it difficult to port your applications over to another database type. Doctrine strives to overcome these differences so your applications can switch between DBMS's without any changes. For example switching from sqlite to mysql.

The portability modes are bitwised, so they can be combined using | and removed using ^. See the examples section below on how to do this.

You can read more about the bitwise operators on the PHP website: http://www.php.net/language.operators.bitwise

Portability Mode Attributes

Below is a list of all the available portability attributes and the description of what each one does:

Name Description
PORTABILITY_ALL Turn on all portability features. This is the default setting.
PORTABILITY_DELETE_COUNT Force reporting the number of rows deleted. Some DBMS's don't count the number of rows deleted when performingsimple DELETE FROM tablename queries. This mode tricks such DBMS's into telling the count by adding WHERE 1=1 to the end of DELETE queries.
PORTABILITY_EMPTY_TO_NULL Convert empty strings values to null in data in and output. Needed because Oracle considers empty strings to be null, while most other DBMS's know the difference between empty and null.
PORTABILITY_ERRORS Makes certain error messages in certain drivers compatible with those from other DBMS's
PORTABILITY_FIX_ASSOC_FIELD_NAMES This removes any qualifiers from keys in associative fetches. Some RDBMS, like for example SQLite, will by default use the fully qualified name for a column in assoc fetches if it is qualified in a query.
PORTABILITY_FIX_CASE Convert names of tables and fields to lower or upper case in all methods. The case depends on the field_case option that may be set to either CASE_LOWER (default) or CASE_UPPER
PORTABILITY_NONE Turn off all portability features.
PORTABILITY_NUMROWS Enable hack that makes numRows() work in Oracle.
PORTABILITY_EXPR Makes DQL API throw exceptions when non-portable expressions are being used.
PORTABILITY_RTRIM Right trim the data output for all data fetches. This does not applied in drivers for RDBMS that automatically right trim values of fixed length character values, even if they do not right trim value of variable length character values.

Examples

Now we can use the setAttribute() method to enable portability for lowercasing and trimming with the following code:

// bootstrap.php

// ...
$conn->setAttribute('portability',
        Doctrine::PORTABILITY_FIX_CASE | Doctrine::PORTABILITY_RTRIM);

Enable all portability options except trimming

// bootstrap.php

// ...
$conn->setAttribute('portability',
        Doctrine::PORTABILITY_ALL ^ Doctrine::PORTABILITY_RTRIM);

Identifier quoting

You can quote the db identifiers (table and field names) with quoteIdentifier(). The delimiting style depends on which database driver is being used.

Just because you CAN use delimited identifiers, it doesn't mean you SHOULD use them. In general, they end up causing way more problems than they solve. Anyway, it may be necessary when you have a reserved word as a field name (in this case, we suggest you to change it, if you can).

Some of the internal Doctrine methods generate queries. Enabling the quote_identifier attribute of Doctrine you can tell Doctrine to quote the identifiers in these generated queries. For all user supplied queries this option is irrelevant.

Portability is broken by using the following characters inside delimited identifiers:

Name Character Driver
backtick ` MySQL
double quote " Oracle
brackets [ or ] Access

Delimited identifiers are known to generally work correctly under the following drivers: Mssql, Mysql, Oracle, Pgsql, Sqlite and Firebird.

When using the Doctrine::ATTR_QUOTE_IDENTIFIER option, all of the field identifiers will be automatically quoted in the resulting SQL statements:

// bootstrap.php

// ...
$conn->setAttribute(Doctrine::ATTR_QUOTE_IDENTIFIER, true);

Will result in a SQL statement that all the field names are quoted with the backtick '`' operator (in MySQL).

SELECT 
*
FROM sometable
WHERE `id` = '123'

As opposed to:

SELECT 
*
FROM sometable
WHERE id = '123'

Exporting

The export attribute is used for telling Doctrine what it should export when exporting classes to your database for creating your tables.

If you don't want to export anything when exporting you can use:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_NONE);

For exporting tables only (but not constraints) you can use on of the following:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_TABLES);

You can also use the following syntax as it is the same as the above:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_EXPORT,
  Doctrine::EXPORT_ALL ^ Doctrine::EXPORT_CONSTRAINTS);

For exporting everything (tables and constraints) you can use:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);

Naming convention attributes

Naming convention attributes affect the naming of different database related elements such as tables, indexes and sequences. Basically every naming convention attribute has affect in both ways. When importing schemas from the database to classes and when exporting classes into database tables.

So for example by default Doctrine naming convention for indexes is %s_idx. Not only do the indexes you set get a special suffix, also the imported classes get their indexes mapped to their non-suffixed equivalents. This applies to all naming convention attributes.

Index name format

Doctrine::ATTR_IDXNAME_FORMAT can be used for changing the naming convention of indexes. By default Doctrine uses the format [name]_idx. So defining an index called 'ageindex' will actually be converted into 'ageindex_idx'.

You can change the index naming convention with the following code:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_IDXNAME_FORMAT, '%s_index');

Sequence name format

Similar to Doctrine::ATTR_IDXNAME_FORMAT, Doctrine::ATTR_SEQNAME_FORMAT can be used for changing the naming convention of sequences. By default Doctrine uses the format [name]_seq, hence creating a new sequence with the name of mysequence will lead into creation of sequence called mysequence_seq.

You can change the sequence naming convention with the following code:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_SEQNAME_FORMAT, '%s_sequence');

Table name format

The table name format can be changed the same as the index and sequence name format with the following code:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_TBLNAME_FORMAT, '%s_table');

Database name format

The database name format can be changed the same as the index, sequence and table name format with the following code:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_DBNAME_FORMAT, 'myframework_%s');

Validation attributes

Doctrine provides complete control over what it validates. The validation procedure can be controlled with Doctrine::ATTR_VALIDATE.

The validation modes are bitwised, so they can be combined using | and removed using ^. See the examples section below on how to do this.

Validation mode constants

Name Description
VALIDATE_NONE Turns off the whole validation procedure.
VALIDATE_LENGTHS Makes Doctrine validate all field lengths.
VALIDATE_TYPES Makes Doctrine validate all field types. Doctrine does loose type validation. This means that for example string with value '13.3' will not pass as an integer but '13' will.
VALIDATE_CONSTRAINTS Makes Doctrine validate all field constraints such as notnull, email etc.
VALIDATE_ALL Turns on all validations.

Validation by default is turned off so if you wish for your data to be validated you will need to enable it. Some examples of how to change this configuration are provided below.

Examples

You can turn on all validations by using the Doctrine::VALIDATE_ALL attribute with the following code:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);

You can also configure Doctrine to validate lengths and types, but not constraints with the following code:

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_VALIDATE,
  Doctrine::VALIDATE_LENGTHS | Doctrine::VALIDATE_TYPES);

Optional String Syntax

You can optionally specify attribute names and values as strings. Below is an example. This is allowed as a convenience to shorten the syntax and make it easier to type.

// bootstrap.php

// ...
$conn->setAttribute('validate', 'none');

Internally when strings are used they are converted to the constants and used.

Conclusion

Now we have gone over some of the most common attributes used to configure Doctrine. Some of these attributes may not apply to you ever or you may not understand what you could use them for now. As you read the next chapters you will see which attributes you do and don't need to use and things will begin to make more sense.

If you saw some attributes you wanted to change the value above, then you should have added it to your bootstrap.php file and it should look something like the following now:

/**
 * Bootstrap Doctrine.php, register autoloader and specify
 * configuration attributes
 */

require_once('../doctrine/branches/1.0/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$manager = Doctrine_Manager::getInstance();

$conn = Doctrine_Manager::connection('sqlite::memory:', 'doctrine');

$manager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_ALL);
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
$manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE);

Now we are ready to move on to the next chapter where we will learn everything their is to know about Doctrine Connections.

Connections

Introduction

From the start Doctrine has been designed to work with multiple connections. Unless separately specified Doctrine always uses the current connection for executing the queries.

In this chapter we will demonstrate how to create and work with Doctrine connections.

Opening Connections

Doctrine_Manager provides the static method Doctrine_Manager::connection() which opens new connections.

In this example we will show you to open a new connection:

// test.php

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

Retrieve Connections

If you use the Doctrine_Manager::connection() method and don't pass any arguments it will return the current connection:

// test.php

// ...
$conn2 = Doctrine_Manager::connection();

if ($conn === $conn2) {
    echo 'Doctrine_Manager::connection() returns the current connection';
}

Current Connection

The current connection is the last opened connection. In the next example we will show how you can get the current connection from the Doctrine_Manager instance:

// test.php

// ...
$conn2 = Doctrine_Manager::connection('mysql://username2:password2@localhost/test2', 'connection 2');

if ($conn2 === $manager->getCurrentConnection()) {
    echo 'Current connection is the connection we just created!';
}

Change Current Connection

You can change the current connection by calling Doctrine_Manager::setCurrentConnection().

// test.php

// ...
$manager->setCurrentConnection('connection 1');

echo $manager->getCurrentConnection()->getName(); // connection 1

Iterating Connections

You can iterate over the opened connections by simply passing the manager object to a foreach clause. This is possible since Doctrine_Manager implements special IteratorAggregate interface.

The IteratorAggregate is a special PHP interface for implementing iterators in to your objects.

// test.php

// ...
foreach($manager as $conn) {
    echo $conn->getName() . "\n";
}

Get Connection Name

You can easily get the name of a Doctrine_Connection instance with the following code:

// test.php

// ...
$conn = Doctrine_Manager::connection();

$name = $manager->getConnectionName($conn);

echo $name; // connection 1

Close Connection

You can easily close a connection and remove it from the Doctrine connection registry with the following code:

// test.php

// ...
$conn = Doctrine_Manager::connection();

$manager->closeConnection($conn);

If you wish to close the connection but not remove it from the Doctrine connection registry you can use the following code instead:

// test.php

// ...
$conn = Doctrine_Manager::connection();
$conn->close();

Get All Connections

You can retrieve an array of all the registered connections by using the Doctrine_Manager::getConnections() method like below:

// test.php

// ...
$conns = $manager->getConnections();
foreach ($conns as $conn) {
    echo $conn->getName() . "\n";
}

The above is essentially the same as iterating over the Doctrine_Manager object like we did earlier. Here it is again:

// test.php

// ...
foreach ($manager as $conn) {
    echo $conn->getName() . "\n";
}

Count Connections

You can easily get the number of connections from a Doctrine_Manager object since it implements the Countable interface.

// test.php

// ...
$num = count($manager);

echo $num;

The above is the same as doing:

// test.php

// ...
$num = $manager->count();

Creating and Dropping Database

When you create connections using Doctrine, you gain the ability to easily create and drop the databases related to those connections.

This is as simple as using some functions provided in the Doctrine_Manager or Doctrine_Connection classes.

The following code will iterate over all instantiated connections and call the dropDatabases()/createDatabases() function on each one:

// test.php

// ...
$manager->createDatabases();

$manager->dropDatabases();

Drop/create database for specific connection

You can easily drop or create the database for a specific Doctrine_Connection instance by calling the dropDatabase()/createDatabase() function on the connection instance with the following code:

// test.php

// ...
$conn->createDatabase();

$conn->dropDatabase();

Conclusion

Now that we have learned all about Doctrine connections we should be ready to dive right in to models in the Introduction to Models chapter. We will learn a little bit about Doctrine models first. Then we will start to have some fun and create our first test models and see what kind of magic Doctrine can provide for you.

Introduction to Models

Introduction

At the lowest level, Doctrine represents your database schema with a set of PHP classes. These classes define the schema and behavior of your model.

A basic model that represents a user in a web application might look something like this.

class User extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('username', 'string', 255);
        $this->hasColumn('password', 'string', 255);
    }

    public function setUp()
    {
        $this->actAs('Timestampable');
    }
}

We aren't actually going to use the above class definition, it is only meant to be an example. We will generate our first class definition from an existing database table later in this chapter.

Each Doctrine_Record child class can have a setTableDefinition() and setUp() method. The setTableDefinition() method is for defining columns, indexes and other information about the schema of tables. The setUp() method is for attaching behaviors and defining relationships between Doctrine_Record child classes. In the above example we are enabling the Timestampable behavior which adds some automagic functionality. You will learn more about what all can be used in these functions in the Defining Models chapter.

Generating Models

Doctrine offers ways to generate these classes to make it easier to get started using Doctrine.

Generating from existing databases is only meant to be a convenience for getting started. After you generate from the database you will have to tweak it and clean things up as needed.

Existing Databases

A common case when looking for ORM tools like Doctrine is that the database and the code that access it is growing large/complex. A more substantial tool is needed than manual SQL code.

Doctrine has support for generating Doctrine_Record classes from your existing database. There is no need for you to manually write all the Doctrine_Record classes for your domain model.

Making the first import

Let's consider we have a mysql database called doctrine_test with a single table named user. The user table has been created with the following sql statement:

CREATE TABLE user (
   id bigint(20) NOT NULL auto_increment,
   first_name varchar(255) default NULL,
   last_name varchar(255) default NULL,
   username varchar(255) default NULL,
   password varchar(255) default NULL,
   type varchar(255) default NULL,
   is_active tinyint(1) default '1',
   is_super_admin tinyint(1) default '0',
   created_at TIMESTAMP,
   updated_at TIMESTAMP,
   PRIMARY KEY  (id)
) ENGINE=InnoDB

Now we would like to convert it into Doctrine_Record class. With Doctrine this is easy! Remember our test script we created in the Getting Started chapter? We're going to use that generate our models.

First we need to modify our bootstrap.php to use the MySQL database instead of sqlite memory:

// bootstrap.php

// ...
$conn = Doctrine_Manager::connection('mysql://root:mys3cr3et@localhost/doctrine_test', 'doctrine');
// ...

You can use the $conn->createDatabase() method to create the database if it does not already exist and the connected user has permission to create databases. Then use the above provided CREATE TABLE statement to create the table.

Now we need a place to store our generated classes so lets create a directory named models in the doctrine_test directory:

$ mkdir doctrine_test/models

Now we just need to add the code to our test.php script to generate the model classes:

// test.php

// ...
Doctrine::generateModelsFromDb('models', array('doctrine'), array('generateTableClasses' => true));

The generateModelsFromDb method only requires one parameter and it is the import directory (the directory where the generated record files will be written to). The second argument is an array of database connection names to generate models for, and the third is the array of options to use for the model building.

That's it! Now there should be a file called BaseUser.php in your doctrine_test/models/generated directory. The file should look like the following:

// models/generated/BaseUser.php

/**
 * This class has been auto-generated by the Doctrine ORM Framework
 */
abstract class BaseUser extends Doctrine_Record
{
  public function setTableDefinition()
  {
    $this->setTableName('user');
    $this->hasColumn('id', 'integer', 8, array('type' => 'integer', 'length' => 8, 'primary' => true, 'autoincrement' => true));
    $this->hasColumn('first_name', 'string', 255, array('type' => 'string', 'length' => 255));
    $this->hasColumn('last_name', 'string', 255, array('type' => 'string', 'length' => 255));
    $this->hasColumn('username', 'string', 255, array('type' => 'string', 'length' => 255));
    $this->hasColumn('password', 'string', 255, array('type' => 'string', 'length' => 255));
    $this->hasColumn('type', 'string', 255, array('type' => 'string', 'length' => 255));
    $this->hasColumn('is_active', 'integer', 1, array('type' => 'integer', 'length' => 1, 'default' => '1'));
    $this->hasColumn('is_super_admin', 'integer', 1, array('type' => 'integer', 'length' => 1, 'default' => '0'));
    $this->hasColumn('created_at', 'timestamp', null, array('type' => 'timestamp', 'notnull' => true));
    $this->hasColumn('updated_at', 'timestamp', null, array('type' => 'timestamp', 'notnull' => true));
  }
}

You should also have a file called User.php in your doctrine_test/models directory. The file should look like the following:

// models/User.php

/**
 * This class has been auto-generated by the Doctrine ORM Framework
 */
class User extends BaseUser
{

}

Doctrine will automatically generate a skeleton Doctrine_Table class for the model at doctrine_test/models/UserTable.php because we passed the option generateTableClasses with a value of true. The file should look like the following:

// models/UserTable.php

/**
 * This class has been auto-generated by the Doctrine ORM Framework
 */
class UserTable extends Doctrine_Table
{

}

You can place custom functions inside the User and UserTable classes to customize the functionality of your models. Below are some examples:

// models/User.php

// ...
class User extends BaseUser
{
    public function setPassword($password)
    {
        return $this->_set('password', md5($password));
    }
}

In order for the above password accessor overriding to work properly you must enabled the auto_accessor_override attribute in your bootstrap.php file like done below.

// bootstrap.php

// ...
$manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);

Now when you try and set a users password it will be md5 encrypted. First we need to modify our bootstrap.php file to include some code for autoloading our models from the models directory:

// bootstrap.php

// ...
Doctrine::loadModels('models');

The model loading is fully explained later in the Autoloading Models section of this chapter.

Now we can modify test.php to include some code which will test the changes we made to the User model:

// test.php

// ...

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

echo $user->password; // outputs md5 hash and not changeme

Now when you execute test.php from your terminal you should see the following:

$ php test.php
4cb9c8a8048fd02294477fcb1a41191a

Here is an example of some custom functions you might add to the UserTable class:

// models/UserTable.php

// ...
class UserTable extends Doctrine_Table
{
    public function getCreatedToday()
    {
        $today = date('Y-m-d h:i:s', strtotime(date('Y-m-d')));
        return $this->createQuery('u')
            ->where('u.created_at > ?', $today)
            ->execute();
    }
}

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::ATTR_AUTOLOAD_TABLE_CLASSES, true);

Now you have access to this function when you are working with the UserTable instance:

// test.php

// ...
$usersCreatedToday = Doctrine::getTable('User')->getCreatedToday();

Schema Files

You can alternatively manage your models with YAML schema files and generate PHP classes from them. First lets generate a YAML schema file from the existing models we already have to make things easier. Change test.php to have the following code inside:

// test.php

// ...

Doctrine::generateYamlFromModels('schema.yml', 'models');

Execute the test.php script:

$ php test.php

Now you should see a file named schema.yml created in the root of the doctrine_test directory. It should look like the following:

---
User:
  tableName: user
  columns:
    id:
      type: integer(8)
      primary: true
      autoincrement: true
    is_active:
      type: integer(1)
      default: '1'
    is_super_admin:
      type: integer(1)
      default: '0'
    created_at:
      type: timestamp(25)
      notnull: true
    updated_at:
      type: timestamp(25)
      notnull: true
    first_name: string(255)
    last_name: string(255)
    username: string(255)
    password: string(255)
    type: string(255)

So now that we have a valid YAML schema file, we can now maintain our schema from here and generate the PHP classes from here. Lets create a new php script called generate.php. This script will re-generate everything and make sure the database is reinstantiated each time the script is called:

// generate.php

require_once('bootstrap.php');

Doctrine::dropDatabases();
Doctrine::createDatabases();
Doctrine::generateModelsFromYaml('schema.yml', 'models');
Doctrine::createTablesFromModels('models');

Now you can alter your schema.yml and re-generate your models by running the following command from your terminal:

$ php generate.php

Now that we have our YAML schema file setup and we can re-generate our models from the schema files lets cleanup the file a little and take advantage of some of the power of Doctrine:

---
User:
  actAs: [Timestampable]
  columns:
    is_active:
      type: integer(1)
      default: '1'
    is_super_admin:
      type: integer(1)
      default: '0'
    first_name: string(255)
    last_name: string(255)
    username: string(255)
    password: string(255)
    type: string(255)

Notice some of the changes we made:
1.) Removed the explicit tableName definition as it will default to user.
2.) Attached the Timestampable behavior.
3.) Removed id column as it is automatically added if no primary key is defined.
4.) Removed updated_at and created_at columns as they can be handled automatically by the Timestampable behavior.
Now look how much cleaner the YAML is and is because we take advantage of defaults and utilize core behaviors it is much less work we have to do ourselves.

Now re-generate your models from the YAML schema file:

$ php generate.php

You can learn more about YAML Schema Files in its dedicated chapter.

Manually Writing Models

You can optionally skip all the convenience methods and write your models manually using nothing but your own PHP code. You can learn all about the models syntax in the Defining Models chapter.

Autoloading Models

Doctrine offers two ways of loading models. We have conservative(lazy) loading, and aggressive loading. Conservative loading will not require the PHP file initially, instead it will cache the path to the class name and this path is then used in the Doctrine::autoload() we registered earlier with spl_autoload_register(). Below are some examples using the both types of model loading.

Conservative

Conservative model loading is going to be the ideal model loading method for a production environment. This method will lazy load all of the models instead of loading them all when model loading is executed.

Conservative model loading requires that each file contain only one class, and the file must be named after the class. For example, if you have a class named User, it must be contained in a file named User.php.

To use conservative model loading we need to set the model loading attribute to be conservative:

$manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_CONSERVATIVE);

We already made this change in an earlier step in the bootstrap.php file so you don't need to make this change again.

When we use the Doctrine::loadModels() functionality all found classes will be cached internally so the autoloader can require them later.

Doctrine::loadModels('models');

Now when we instantiate a new class, for example a User class, the autoloader will be triggered and the class is required.

// triggers call to Doctrine::autoload() and the class is included
$user = new User();

Instantiating the class above triggers a call to Doctrine::autoload() and the class that was found in the call to Doctrine::loadModels() will be required and made available.

Conservative model loading is recommended in most cases, specifically for production environments as you do not want to require every single model class even when it is not needed as this is unnecessary overhead. You only want to require it when it is needed.

Aggressive

Aggressive model loading is the default model loading method and is very simple, it will look for all files with a .php extension and will include it. Doctrine can not satisfy any inheritance and if your models extend another model, it cannot include them in the correct order so it is up to you to make sure all dependencies are satisfied in each class.

With aggressive model loading you can have multiple classes per file and the file name is not required to be related to the name of the class inside of the file.

The downside of aggressive model loading is that every php file is included in every request, so if you have lots of models it is recommended you use conservative model loading.

To use aggressive model loading we need to set the model loading attribute to be aggressive:

$manager->setAttribute(Doctrine::ATTR_MODEL_LOADING, Doctrine::MODEL_LOADING_AGGRESSIVE);

Aggressive is the default of the model loading attribute so explicitly setting it is not necessary if you wish to use it.

When we use the Doctrine::loadModels() functionality all the classes found will be included right away:

Doctrine::loadModels('/path/to/models');

Custom Accessors/Mutators

With Doctrine it is possible to define custom accessors and mutators to use in your Doctrine models. This is possible with the hasAccessor() and hasMutator() mapping methods. Or, you can define both the mutator and accessor in one call using the hasAccessorMutator() method.

Imagine a user model where you want to define a custom password mutator that encrypts the password using the PHP md5() method:

class User extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('username', 'string' 255);
        $this->hasColumn('password', 'string', 255);
    }

    public function setUp()
    {
        $this->hasMutator('password', 'md5Password');
    }

    public function md5Password($value)
    {
        $this->_set('password', md5($value));
    }
}

Now when you try and set the password the custom mutator will be invoked instead of the normal mutating procedure.

$user = new User();
$user->username = 'jwage';
$user->password = 'changeme'; // invokes User::md5Password()

Conclusion

This chapter is probably the most intense chapter so far but it is a good one. We learned a little about how to use models, how to generate models from existing databases, how to write our own models, and how to maintain our models as YAML schema files. We also modified our Doctrine test environment to implement some functionality for loading models from our models directory.

This topic of Doctrine models is so large that it warranted the chapters being split in to three pieces to make it easier on the developer to absorb all the information. In the next chapter we will really get in to the API we use to define our models.

Defining Models

As we mentioned before, at the lowest level in Doctrine your schema is represented by a set of php classes that map the schema meta data for your database tables.

In this chapter we will explain in detail how you can map your schema information using php code.

Columns

One problem with database compatibility is that many databases differ in their behavior of how the result set of a query is returned. MySQL leaves the field names unchanged, which means if you issue a query of the form "SELECT myField FROM ..." then the result set will contain the field 'myField'.

Unfortunately, this is just the way MySQL and some other databases do it. Postgres for example returns all field names in lowercase whilst Oracle returns all field names in uppercase. "So what? In what way does this influence me when using Doctrine?", you may ask. Fortunately, you don't have to bother about that issue at all.

Doctrine takes care of this problem transparently. That means if you define a derived Record class and define a field called myField you will always access it through $record->myField (or $record['myField'], whatever you prefer) no matter whether you're using MySQL or Postgres or Oracle etc.

In short: You can name your fields however you want, using under_scores, camelCase or whatever you prefer.

In Doctrine columns and column aliases are case sensitive. So when you are using columns in your DQL queries, the column/field names must match the case in your model definition.

Column Lengths

In Doctrine column length is an integer that specifies the column length. Some column types depend not only the given portable type but also on the given length. For example type string with length 1000 will be translated into native type TEXT on mysql.

The length is different depending on the type of column you are using:

  • integer - Length is the the number of bytes the integer occupies.
  • string - Number of the characters allowed in the string.
  • float/decimal - Total number of characters allowed excluding the decimal.
  • enum - If using native enum length does not apply but if using emulated enums then it is just the string length of the column value.

Column Aliases

Doctrine offers a way of setting column aliases. This can be very useful when you want to keep the application logic separate from the database logic. For example if you want to change the name of the database field all you need to change at your application is the column definition.

// models/Book.php

class Book extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('bookTitle as title', 'string');
    }
}

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

---
# schema.yml

# ...
Book:
  columns:
    bookTitle:
      name: bookTitle as title
      type: string

Now the column in the database is named bookTitle but you can access the property on your objects using title.

// test.php

// ...
$book = new Book();
$book->title = 'Some book';
$book->save();

Default values

Doctrine supports default values for all data types. When default value is attached to a record column this means two things. First this value is attached to every newly created Record and when Doctrine creates your database tables it includes the default value in the create table statement.

// models/generated/BaseUser.php

class User extends BaseUser
{
    public function setTableDefinition()
    {
        $this->hasColumn('username', 'string', 255, array('default' => 'default username'));

        // ...
    }

    // ...
}

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

---
# schema.yml

# ...
User:
# ...
  columns:
    username:
      type: string(255)
      default: default username
# ...

Now when you print the name on a brand new User record it will print the default value:

// test.php

// ...
$user = new User();
echo $user->username; // default username

Data types

Introduction

All DBMS provide multiple choice of data types for the information that can be stored in their database table fields. However, the set of data types made available varies from DBMS to DBMS.

To simplify the interface with the DBMS supported by Doctrine, a base set of data types was defined. Applications may access them independently of the underlying DBMS.

The Doctrine applications programming interface takes care of mapping data types when managing database options. It is also able to convert that is sent to and received from the underlying DBMS using the respective driver.

The following data type examples should be used with Doctrine's createTable() method. The example array at the end of the data types section may be used with createTable() to create a portable table on the DBMS of choice (please refer to the main Doctrine documentation to find out what DBMS back ends are properly supported). It should also be noted that the following examples do not cover the creation and maintenance of indices, this chapter is only concerned with data types and the proper usage thereof.

It should be noted that the length of the column affects in database level type as well as application level validated length (the length that is validated with Doctrine validators).

Example 1. Column named 'content' with type 'string' and length 3000 results in database type 'TEXT' of which has database level length of 4000. However when the record is validated it is only allowed to have 'content' -column with maximum length of 3000.

Example 2. Column with type 'integer' and length 1 results in 'TINYINT' on many databases.

In general Doctrine is smart enough to know which integer/string type to use depending on the specified length.

Type modifiers

Within the Doctrine API there are a few modifiers that have been designed to aid in optimal table design. These are:

  • The notnull modifiers
  • The length modifiers
  • The default modifiers
  • unsigned modifiers for some field definitions, although not all DBMS's support this modifier for integer field types.
  • collation modifiers (not supported by all drivers)
  • fixed length modifiers for some field definitions.

Building upon the above, we can say that the modifiers alter the field definition to create more specific field types for specific usage scenarios. The notnull modifier will be used in the following way to set the default DBMS NOT NULL Flag on the field to true or false, depending on the DBMS's definition of the field value: In PostgreSQL the "NOT NULL" definition will be set to "NOT NULL", whilst in MySQL (for example) the "NULL" option will be set to "NO". In order to define a "NOT NULL" field type, we simply add an extra parameter to our definition array (See the examples in the following section)

'sometime' = array(
    'type'    => 'time',
    'default' => '12:34:05',
    'notnull' => true,
),

Using the above example, we can also explore the default field operator. Default is set in the same way as the notnull operator to set a default value for the field. This value may be set in any character set that the DBMS supports for text fields, and any other valid data for the field's data type. In the above example, we have specified a valid time for the "Time" data type, '12:34:05'. Remember that when setting default dates and times, as well as datetimes, you should research and stay within the epoch of your chosen DBMS, otherwise you will encounter difficult to diagnose errors!

'sometext' = array(
    'type'   => 'string',
    'length' => 12,
),

The above example will create a character varying field of length 12 characters in the database table. If the length definition is left out, Doctrine will create a length of the maximum allowable length for the data type specified, which may create a problem with some field types and indexing. Best practice is to define lengths for all or most of your fields.

Boolean

The boolean data type represents only two values that can be either 1 or 0. Do not assume that these data types are stored as integers because some DBMS drivers may implement this type with single character text fields for a matter of efficiency. Ternary logic is possible by using null as the third possible value that may be assigned to fields of this type.

The next several examples are not meant for you to use and give them a try. They are simply for demonstrating purposes to show you how to use the different Doctrine data types using PHP code or YAML schema files.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('booltest', 'boolean');
    }
}

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

---
Test:
  columns:
    booltest: boolean

Integer

The integer type is the same as integer type in PHP. It may store integer values as large as each DBMS may handle.

Fields of this type may be created optionally as unsigned integers but not all DBMS support it. Therefore, such option may be ignored. Truly portable applications should not rely on the availability of this option.

The integer type maps to different database type depending on the column length.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('integertest', 'integer', 4, array(
                'unsigned' => true
            )
        );
    }
}

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

---
Test:
  columns:
    integertest:
      type: integer(4)
      unsigned: true

Float

The float data type may store floating point decimal numbers. This data type is suitable for representing numbers withina large scale range that do not require high accuracy. The scale and the precision limits of the values that may be stored in a database depends on the DBMS that it is used.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('floattest', 'float');
    }
}

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

---
Test:
  columns:
    floattest: float

Decimal

The decimal data type may store fixed precision decimal numbers. This data type is suitable for representing numbers that require high precision and accuracy.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('decimaltest', 'decimal');
    }
}

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

---
Test:
  columns:
    decimaltest: decimal

You can specify the length of the decimal just like you would set the length of any other column and you can specify the scale as an option in the third argument:

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('decimaltest', 'decimal', 18, array(
                'scale' => 2
            )
        );
    }
}

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

---
Test:
  columns:
    decimaltest:
      type: decimal(18)
      scale: 2

String

The text data type is available with two options for the length: one that is explicitly length limited and another of undefined length that should be as large as the database allows.

The length limited option is the most recommended for efficiency reasons. The undefined length option allows very large fields but may prevent the use of indexes, nullability and may not allow sorting on fields of its type.

The fields of this type should be able to handle 8 bit characters. Drivers take care of DBMS specific escaping of characters of special meaning with the values of the strings to be converted to this type.

By default Doctrine will use variable length character types. If fixed length types should be used can be controlled via the fixed modifier.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('stringtest', 'string', 200, array(
                'fixed' => true
            )
        );
    }
}

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

---
Test:
  columns:
    stringtest:
      type: string(200)
      fixed: true

Array

This is the same as the 'array' type in PHP.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('arraytest', 'array', 10000);
    }
}

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

---
Test:
  columns:
    arraytest: array(10000)

Object

Doctrine supports objects as column types. Basically you can set an object to a field and Doctrine handles automatically the serialization / unserialization of that object.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('objecttest', 'object');
    }
}

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

---
Test:
  columns:
    objecttest: object

The array and object types simply serialize the data when persisting to the database and unserialize the data when pulling from the database.

Blob

Blob (Binary Large OBject) data type is meant to store data of undefined length that may be too large to store in text fields, like data that is usually stored in files.

Blob fields are usually not meant to be used as parameters of query search clause (WHERE) unless the underlying DBMS supports a feature usually known as "full text search"

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('blobtest', 'blob');
    }
}

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

---
Test:
  columns:
    blobtest: blob

Clob

Clob (Character Large OBject) data type is meant to store data of undefined length that may be too large to store in text fields, like data that is usually stored in files.

Clob fields are meant to store only data made of printable ASCII characters whereas blob fields are meant to store all types of data.

Clob fields are usually not meant to be used as parameters of query search clause (WHERE) unless the underlying DBMS supports a feature usually known as "full text search"

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('clobtest', 'clob');
    }
}

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

---
Test:
  columns:
    clobtest: clob

Timestamp

The timestamp data type is a mere combination of the date and the time of the day data types. The representation of values of the time stamp type is accomplished by joining the date and time string values in a single string joined by a space. Therefore, the format template is YYYY-MM-DD HH:MI:SS. The represented values obey the same rules and ranges described for the date and time data types

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('timestamptest', 'timestamp');
    }
}

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

---
Test:
  columns:
    timestamptest: timestamp

Time

The time data type may represent the time of a given moment of the day. DBMS independent representation of the time of the day is also accomplished by using text strings formatted according to the ISO-8601 standard.

The format defined by the ISO-8601 standard for the time of the day is HH:MI:SS where HH is the number of hour the day from 00 to 23 and MI and SS are respectively the number of the minute and of the second from 00 to 59. Hours, minutes and seconds numbered below 10 should be padded on the left with 0.

Some DBMS have native support for time of the day formats, but for others the DBMS driver may have to represent them as integers or text values. In any case, it is always possible to make comparisons between time values as well sort query results by fields of this type.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('timetest', 'time');
    }
}

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

---
Test:
  columns:
    timetest: time

Date

The date data type may represent dates with year, month and day. DBMS independent representation of dates is accomplished by using text strings formatted according to the IS0-8601 standard.

The format defined by the ISO-8601 standard for dates is YYYY-MM-DD where YYYY is the number of the year (Gregorian calendar), MM is the number of the month from 01 to 12 and DD is the number of the day from 01 to 31. Months or days numbered below 10 should be padded on the left with 0.

Some DBMS have native support for date formats, but for others the DBMS driver may have to represent them as integers or text values. In any case, it is always possible to make comparisons between date values as well sort query results by fields of this type.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('datetest', 'date');
    }
}

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

---
Test:
  columns:
    datetest: date

Enum

Doctrine has a unified enum type. The possible values for the column can be specified on the column definition with Doctrine_Record::hasColumn()

If you wish to use native enum types for your DBMS if it supports it then you must set the following attribute:

$conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM, true);

Here is an example of how to specify the enum values:

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('enumtest', 'enum', null,
            array('values' => array('php', 'java', 'python'))
        );
    }
}

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

---
Test:
  columns:
    enumtest:
      type: enum
      values: [php, java, python]

Gzip

Gzip datatype is the same as string except that its automatically compressed when persisted and uncompressed when fetched. This datatype can be useful when storing data with a large compressibility ratio, such as bitmap images.

class Test extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('gziptest', 'gzip');
    }
}

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

---
Test:
  columns:
    gziptest: gzip

The family of php functions for compressing are used internally for compressing and uncompressing the contents of the gzip column type.

Examples

Consider the following definition:

class Example extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('id', 'string', 32, array(
                'type' => 'string',
                'fixed' => 1,
                'primary' => true,
                'length' => '32'
            )
        );

        $this->hasColumn('someint', 'integer', 10, array(
                'type' => 'integer',
                'unsigned' => true,
                'length' => '10'
            )
        );

        $this->hasColumn('sometime', 'time', 25, array(
                'type' => 'time',
                'default' => '12:34:05',
                'notnull' => true,
                'length' => '25'
            )
        );

        $this->hasColumn('sometext', 'string', 12, array(
                'type' => 'string',
                'length' => '12'
            )
        );

        $this->hasColumn('somedate', 'date', 25, array(
                'type' => 'date',
                'length' => '25'
            )
        );

        $this->hasColumn('sometimestamp', 'timestamp', 25, array(
                'type' => 'timestamp',
                'length' => '25'
            )
        );

        $this->hasColumn('someboolean', 'boolean', 25, array(
                'type' => 'boolean',
                'length' => '25'
            )
        );

        $this->hasColumn('somedecimal', 'decimal', 18, array(
                'type' => 'decimal',
                'length' => '18'
            )
        );

        $this->hasColumn('somefloat', 'float', 2147483647, array(
                'type' => 'float',
                'length' => '2147483647'
            )
        );

        $this->hasColumn('someclob', 'clob', 2147483647, array(
                'type' => 'clob',
                'length' => '2147483647'
            )
        );

        $this->hasColumn('someblob', 'blob', 2147483647, array(
                'type' => 'blob',
                'length' => '2147483647'
            )
        );
    }
}

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

---
Example:
  tableName: example
  columns:
    id:
      type: string(32)
      fixed: true
      primary: true
    someint:
      type: integer(10)
      unsigned: true
    sometime:
      type: time(25)
      default: '12
      notnull: true
    sometext: string(12)
    somedate: date(25)
    sometimestamp: timestamp(25)
    someboolean: boolean(25)
    somedecimal: decimal(18)
    somefloat: float(2147483647)
    someclob: clob(2147483647)
    someblob: blob(2147483647)

The above example will create the following database table in Pgsql:

Column Type
id character(32)
someint integer
sometime time without time zone
sometext character or varying(12)
somedate date
sometimestamp timestamp without time zone
someboolean boolean
somedecimal numeric(18,2)
somefloat double precision
someclob text
someblob bytea

The schema will create the following database table in Mysql:

Field Type
id char(32)
someint integer
sometime time
sometext varchar(12)
somedate date
sometimestamp timestamp
someboolean tinyint(1)
somedecimal decimal(18,2)
somefloat double
someclob longtext
someblob longblob

Relationships

Introduction

In Doctrine all record relations are being set with Doctrine_Record::hasMany, Doctrine_Record::hasOne methods. Doctrine supports almost all kinds of database relations from simple one-to-one foreign key relations to join table self-referencing relations.

Unlike the column definitions the Doctrine_Record::hasMany and Doctrine_Record::hasOne methods are placed within a method called setUp(). Both methods take two arguments: the first argument is a string containing the name of the class and optional alias, the second argument is an array consisting of relation options. The option array contains the following keys:

Name Optional Description
local No The local field of the relation. Local field is the linked field in the defining class.
foreign No The foreign field of the relation. Foreign field is the linked field in the linked class.
refClass Yes The name of the association class. This is only needed for many-to-many associations.
owningSide Yes Set to boolean true to indicate the owning side of the relation. The owning side is the side that owns the foreign key. There can only be one owning side in an association between two classes. Note that this option is required if Doctrine can't guess the owning side or it's guess is wrong. An example where this is the case is when both 'local' and 'foreign' are part of the identifier (primary key). It never hurts to specify the owning side in this way.
onDelete Yes The onDelete integrity action that is applied on the foreign key constraint when the tables are created by Doctrine.
onUpdate Yes The onUpdate integrity action that is applied on the foreign key constraint when the tables are created by Doctrine.
cascade Yes Specify application level cascading operations. Currently only delete is supported

So lets take our first example, say we have two classes Forum_Board and Forum_Thread. Here Forum_Board has many Forum_Threads, hence their relation is one-to-many. We don't want to write Forum_ when accessing relations, so we use relation aliases and use the alias Threads.

First lets take a look at the Forum_Board class. It has three columns: name, description and since we didn't specify any primary key, Doctrine auto-creates an id column for it.

We define the relation to the Forum_Thread class by using the hasMany() method. Here the local field is the primary key of the board class whereas the foreign field is the board_id field of the Forum_Thread class.

// models/Forum_Board.php

class Forum_Board extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string', 100);
        $this->hasColumn('description', 'string', 5000);
    }

    public function setUp()
    {
        $this->hasMany('Forum_Thread as Threads', array(
                'local' => 'id',
                'foreign' => 'board_id'
            )
        );
    }
}

Notice the as keyword being used above. This means that the Forum_Board has a many relationship defined to Forum_Thread but is aliased as Threads.

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

---
# schema.yml

# ...
Forum_Board:
  columns:
    name: string(100)
    description: string(5000)

Then lets have a peek at the Forum_Thread class. The columns here are irrelevant, but pay attention to how we define the relation. Since each Thread can have only one Board we are using the hasOne() method. Also notice how we once again use aliases and how the local column here is board_id while the foreign column is the id column.

// models/Forum_Thread.php

class Forum_Thread extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer');
        $this->hasColumn('board_id', 'integer');
        $this->hasColumn('title', 'string', 200);
        $this->hasColumn('updated', 'integer', 10);
        $this->hasColumn('closed', 'integer', 1);
    }

    public function setUp()
    {
        $this->hasOne('Forum_Board as Board', array(
                'local' => 'board_id',
                'foreign' => 'id'
            )
        );

        $this->hasOne('User', array(
                'local' => 'user_id',
                'foreign' => 'id'
            )
        );
    }
}

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

---
# schema.yml

# ...
Forum_Thread:
  columns:
    user_id: integer
    board_id: integer
    title: string(200)
    updated: integer(10)
    closed: integer(1)
  relations:
    User:
      local: user_id
      foreign: id
      foreignAlias: Threads
    Board:
      class: Forum_Board
      local: board_id
      foreign: id
      foreignAlias: Threads

Now we can start using these classes. The same accessors that you've already used for properties are all available for relations.

First lets create a new board:

// test.php

// ...
$board = new Forum_Board();
$board->name = 'Some board';

Now lets create a new thread under the board:

// test.php

// ...
$board->Threads[0]->title = 'new thread 1';
$board->Threads[1]->title = 'new thread 2';

Each Thread needs to be associated to a user so lets create a new User and associate it to each Thread:

$user = new User();
$user->username = 'jwage';
$board->Threads[0]->User = $user;
$board->Threads[1]->User = $user;

Now we can save all the changes with one call. It will save the new board as well as its threads:

// test.php

// ...
$board->save();

Lets do a little inspecting and see the data structure that is created when you use the code from above. Add some code to test.php to output an array of the object graph we've just populated:

print_r($board->toArray(true));

The Doctrine_Record::toArray() takes all the data of a Doctrine_Record instance and converts it to an array so you can easily inspect the data of a record. It accepts an argument named $deep telling it whether or not to include relationships. In this example we have specified {[true]} because we want to include the Threads data.

Now when you execute test.php with PHP from your terminal you should see the following:

$ php test.php
Array
(
    [id] => 2
    [name] => Some board
    [description] =>
    [Threads] => Array
        (
            [0] => Array
                (
                    [id] => 3
                    [user_id] => 1
                    [board_id] => 2
                    [title] => new thread 1
                    [updated] =>
                    [closed] =>
                    [User] => Array
                        (
                            [id] => 1
                            [is_active] => 1
                            [is_super_admin] => 0
                            [first_name] =>
                            [last_name] =>
                            [username] => jwage
                            [password] =>
                            [type] =>
                            [created_at] => 2009-01-20 16:41:57
                            [updated_at] => 2009-01-20 16:41:57
                        )

                )

            [1] => Array
                (
                    [id] => 4
                    [user_id] => 1
                    [board_id] => 2
                    [title] => new thread 2
                    [updated] =>
                    [closed] =>
                    [User] => Array
                        (
                            [id] => 1
                            [is_active] => 1
                            [is_super_admin] => 0
                            [first_name] =>
                            [last_name] =>
                            [username] => jwage
                            [password] =>
                            [type] =>
                            [created_at] => 2009-01-20 16:41:57
                            [updated_at] => 2009-01-20 16:41:57
                        )

                )

        )

)

Notice how the auto increment primary key and foreign keys are automatically set by Doctrine internally. You don't have to worry about the setting of primary keys and foreign keys at all!

Foreign Key Associations

One to One

One-to-one relations are probably the most basic relations. In the following example we have two classes, User and Email with their relation being one-to-one.

First lets take a look at the Email class. Since we are binding a one-to-one relationship we are using the hasOne() method. Notice how we define the foreign key column (user_id) in the Email class. This is due to a fact that Email is owned by the User class and not the other way around. In fact you should always follow this convention - always place the foreign key in the owned class.

The recommended naming convention for foreign key columns is: [tableName]_[primaryKey]. As here the foreign table is 'user' and its primary key is 'id' we have named the foreign key column as 'user_id'.

// models/Email.php

class Email extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer');
        $this->hasColumn('address', 'string', 150);
    }

    public function setUp()
    {
        $this->hasOne('User', array(
                'local' => 'user_id',
                'foreign' => 'id'
            )
        );
    }
}

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

---
# schema.yml

# ...
Email:
  columns:
    user_id: integer
    address: string(150)
  relations:
    User:
      local: user_id
      foreign: id

When using YAML schema files it is not required to specify the relationship on the opposite end(User) because the relationship is automatically flipped and added for you. The relationship will be named the name of the class. So in this case the relationship on the User side will be called Email and will be many. If you wish to customize this you can use the foreignAlias and foreignType options.

The Email class is very similar to the User class. Notice how the local and foreign columns are switched in the hasOne() definition compared to the definition of the Email class.

// models/User.php

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        $this->hasOne('Email', array(
                'local' => 'id',
                'foreign' => 'user_id'
            )
        );
    }
}

Notice how we override the setUp() method and call parent::setUp(). This is because the BaseUser class which is generated from YAML or from an existing database contains the main setUp() method and we override it in the User class to add an additional relationship.

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

---
# schema.yml

# ...
User:
# ...
  relations:
    # ...
    Email:
      local: id
      foreign: user_id

One to Many and Many to One

One-to-Many and Many-to-One relations are very similar to One-to-One relations. The recommended conventions you came in terms with in the previous chapter also apply to one-to-many and many-to-one relations.

In the following example we have two classes: User and Phonenumber. We define their relation as one-to-many (a user can have many phonenumbers). Here once again the Phonenumber is clearly owned by the User so we place the foreign key in the Phonenumber class.

// models/User.php

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        // ...

        $this->hasMany('Phonenumber as Phonenumbers', array(
                'local' => 'id',
                'foreign' => 'user_id'
            )
        );
    }
}

// models/Phonenumber.php

class Phonenumber extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer');
        $this->hasColumn('phonenumber', 'string', 50);
    }

    public function setUp()
    {
        $this->hasOne('User', array(
                'local' => 'user_id',
                'foreign' => 'id'
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
# ...
  relations:
    # ...
    Phonenumbers:
      type: many
      class: Phonenumber
      local: id
      foreign: user_id

Phonenumber:
  columns:
    user_id: integer
    phonenumber: string(50)
  relations:
    User:
      local: user_id
      foreign: id

Tree Structure

A tree structure is a self-referencing foreign key relation. The following definition is also called Adjacency List implementation in terms of hierarchical data concepts.

// models/Task.php

class Task extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string', 100);
        $this->hasColumn('parent_id', 'integer');
    }

    public function setUp()
    {
        $this->hasOne('Task as Parent', array(
                'local' => 'parent_id',
                'foreign' => 'id'
            )
        );

        $this->hasMany('Task as Subtasks', array(
                'local' => 'id',
                'foreign' => 'parent_id'
            )
        );
    }
}

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

---
# schema.yml

# ...
Task:
  columns:
    name: string(100)
    parent_id: integer
  relations:
    Parent:
      class: Task
      local: parent_id
      foreign: id
      foreignAlias: Subtasks

The above implementation is purely an example and is not the most efficient way to store and retrieve hierarchical data. Check the NestedSet behavior included in Doctrine for the recommended way to deal with hierarchical data.

Join Table Associations

Many to Many

If you are coming from relational database background it may be familiar to you how many-to-many associations are handled: an additional association table is needed.

In many-to-many relations the relation between the two components is always an aggregate relation and the association table is owned by both ends. For example in the case of users and groups: when a user is being deleted, the groups he/she belongs to are not being deleted. However, the associations between this user and the groups he/she belongs to are instead being deleted. This removes the relation between the user and the groups he/she belonged to, but does not remove the user nor the groups.

Sometimes you may not want that association table rows are being deleted when user / group is being deleted. You can override this behavior by setting the relations to association component (in this case Groupuser) explicitly.

In the following example we have Groups and Users of which relation is defined as many-to-many. In this case we also need to define an additional class called Groupuser.

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        // ...

        $this->hasMany('Group as Groups', array(
                'local' => 'user_id',
                'foreign' => 'group_id',
                'refClass' => 'UserGroup'
            )
        );
    }
}

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

---
User:
# ...
  relations:
    # ...
    Groups:
      class: Group
      local: user_id
      foreign: group_id
      refClass: UserGroup

The above refClass option is required when setting up many-to-many relationships.

// models/Group.php

class Group extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->setTableName('groups');
        $this->hasColumn('name', 'string', 30);
    }

    public function setUp()
    {
        $this->hasMany('User as Users', array(
                'local' => 'group_id',
                'foreign' => 'user_id',
                'refClass' => 'UserGroup'
            )
        );
    }
}

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

---
# schema.yml

# ...
Group:
  tableName: groups
  columns:
    name: string(30)
  relations:
    Users:
      class: User
      local: group_id
      foreign: user_id
      refClass: UserGroup

Please note that group is a reserved keyword so that is why we renamed the table to groups using the setTableName method. The other option is to turn on identifier quoting using the Doctrine::ATTR_QUOTE_IDENTIFIERS attribute so that the reserved word is escaped with quotes.

$manager->setAttribute(Doctrine::Doctrine::ATTR_QUOTE_IDENTIFIERS, true);
// models/UserGroup.php

class UserGroup extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer', null, array(
                'primary' => true
            )
        );

        $this->hasColumn('group_id', 'integer', null, array(
                'primary' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
UserGroup:
  columns:
    user_id:
      type: integer
      primary: true
    group_id:
      type: integer
      primary: true

Notice how the relationship is bi-directional. Both User has many Group and Group has many User. This is required by Doctrine in order for many-to-many relationships to fully work.

Now lets play around with the new models and create a user and assign it some groups. First create a new User instance:

// test.php

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

Now add two new groups to the User:

// test.php

// ...
$user->Groups[0]->name = 'First Group';

$user->Groups[1]->name = 'Second Group';

Now you can save the groups to the database:

// test.php

// ...
$user->save();

Now you can delete the associations between user and groups it belongs to:

// test.php

// ...
$user->UserGroup->delete();

$groups = new Doctrine_Collection(Doctrine::getTable('Group'));

$groups[0]->name = 'Third Group';

$groups[1]->name = 'Fourth Group';

$user->Groups[2] = $groups[0];
// $user will now have 3 groups

$user->Groups = $groups;
// $user will now have two groups 'Third Group' and 'Fourth Group'

$user->save();

Now if we inspect the $user object data with the Doctrine_Record::toArray():

// test.php

// ...
print_r($user->toArray(true));

The above example would produce the following output:

$ php test.php
Array
(
    [id] => 1
    [is_active] => 1
    [is_super_admin] => 0
    [first_name] =>
    [last_name] =>
    [username] => default username
    [password] =>
    [type] =>
    [created_at] => 2009-01-20 16:48:57
    [updated_at] => 2009-01-20 16:48:57
    [Groups] => Array
        (
            [0] => Array
                (
                    [id] => 3
                    [name] => Third Group
                )

            [1] => Array
                (
                    [id] => 4
                    [name] => Fourth Group
                )

        )

    [UserGroup] => Array
        (
        )

)

Self Referencing (Nest Relations)

Non-Equal Nest Relations
// models/User.php

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        // ...

        $this->hasMany('User as Parents', array(
                'local'    => 'child_id',
                'foreign'  => 'parent_id',
                'refClass' => 'UserReference'
            )
        );

        $this->hasMany('User as Children', array(
                'local'    => 'parent_id',
                'foreign'  => 'child_id',
                'refClass' => 'UserReference'
            )
        );
    }
}

// models/UserReference.php

class UserReference extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('parent_id', 'integer', null, array(
                'primary' => true
            )
        );

        $this->hasColumn('child_id', 'integer', null, array(
                'primary' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
# ...
  relations:
    # ...
    Parents:
      class: User
      local: child_id
      foreign: parent_id
      refClass: UserReference
      foreignAlias: Children

UserReference:
  columns:
    parent_id:
      type: integer
      primary: true
    child_id:
      type: integer
      primary: true
Equal Nest Relations

Equal nest relations are perfectly suitable for expressing relations where a class references to itself and the columns within the reference class are equal.

This means that when fetching related records it doesn't matter which column in the reference class has the primary key value of the main class.

The previous clause maybe hard to understand so lets take an example. We define a class called User which can have many friends. Notice here how we use the 'equal' option.

// models/User.php

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        // ...

        $this->hasMany('User as Friends', array(
                'local'    => 'user1',
                'foreign'  => 'user2',
                'refClass' => 'FriendReference',
                'equal'    => true,
            )
        );
    }
}

// models/FriendReference.php

class FriendReference extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user1', 'integer', null, array(
                'primary' => true
            )
        );

        $this->hasColumn('user2', 'integer', null, array(
                'primary' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
# ...
  relations:
    # ...
    Friends:
      class: User
      local: user1
      foreign: user2
      refClass: FriendReference
      equal: true

FriendReference:
  columns:
    user1:
      type: integer
      primary: true
    user2:
      type: integer
      primary: true

Now lets define 4 users: Jack Daniels, John Brandy, Mikko Koskenkorva and Stefan Beer with Jack Daniels and John Brandy being buddies and Mikko Koskenkorva being the friend of all of them.

// test.php

// ...
$daniels = new User();
$daniels->username = 'Jack Daniels';

$brandy = new User();
$brandy->username = 'John Brandy';

$koskenkorva = new User();
$koskenkorva->username = 'Mikko Koskenkorva';

$beer = new User();
$beer->username = 'Stefan Beer';

$daniels->Friends[0] = $brandy;

$koskenkorva->Friends[0] = $daniels;
$koskenkorva->Friends[1] = $brandy;
$koskenkorva->Friends[2] = $beer;

$conn->flush();

Calling Doctrine_Connection::flush() will trigger an operation that saves all unsaved objects and wraps it in a single transaction.

Now if we access for example the friends of Stefan Beer it would return one user 'Mikko Koskenkorva':

// test.php

// ...
$beer->free();
unset($beer);
$user = Doctrine::getTable('User')->findOneByUsername('Stefan Beer');

print_r($user->Friends->toArray());

Now when you execute test.php you will see the following:

$ php test.php
Array
(
    [0] => Array
        (
            [id] => 4
            [is_active] => 1
            [is_super_admin] => 0
            [first_name] =>
            [last_name] =>
            [username] => Mikko Koskenkorva
            [password] =>
            [type] =>
            [created_at] => 2009-01-20 16:53:13
            [updated_at] => 2009-01-20 16:53:13
        )

)

Foreign Key Constraints

Introduction

A foreign key constraint specifies that the values in a column (or a group of columns) must match the values appearing in some row of another table. In other words foreign key constraints maintain the referential integrity between two related tables.

Say you have the product table with the following definition:

// models/Product.php

class Product extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');
        $this->hasColumn('price', 'decimal', 18);
        $this->hasColumn('discounted_price', 'decimal', 18);
    }

    public function setUp()
    {
        $this->hasMany('Order as Orders', array(
                'local' => 'id',
                'foreign' => 'product_id'
            )
        );
    }
}

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

---
# schema.yml

# ...
Product:
  columns:
    name:
      type: string
    price:
      type: decimal(18)
    discounted_price:
      type: decimal(18)
  relations:
    Orders:
      class: Order
      local: id
      foreign: product_id

Let's also assume you have a table storing orders of those products. We want to ensure that the order table only contains orders of products that actually exist. So we define a foreign key constraint in the orders table that references the products table:

// models/Order.php

class Order extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->setTableName('orders');
        $this->hasColumn('product_id', 'integer');
        $this->hasColumn('quantity', 'integer');
    }

    public function setUp()
    {
        $this->hasOne('Product', array(
                'local' => 'product_id',
                'foreign' => 'id'
            )
        );
    }
}

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

---
# schema.yml

# ...
Order:
  tableName: orders
  columns:
    product_id: integer
    quantity: integer
  relations:
    Product:
      local: product_id
      foreign: id

Foreign key columns are automatically indexed by Doctrine to ensure optimal performance when issuing queries involving the foreign key.

When exported the class Order would execute the following SQL:

CREATE TABLE orders (
    id integer PRIMARY KEY auto_increment,
    product_id integer REFERENCES products (id),
    quantity integer,
    INDEX product_id_idx (product_id)
)

Now it is impossible to create orders with a product_id that does not appear in the product table.

We say that in this situation the orders table is the referencing table and the products table is the referenced table. Similarly, there are referencing and referenced columns.

Integrity Actions

CASCADE

Delete or update the row from the parent table and automatically delete or update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported. Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column in the parent table or in the child table.

SET NULL

Delete or update the row from the parent table and set the foreign key column or columns in the child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.

NO ACTION

In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary key value is not allowed to proceed if there is a related foreign key value in the referenced table.

RESTRICT

Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as omitting the ON DELETE or ON UPDATE clause.

SET DEFAULT

In the following example we define two classes, User and Phonenumber with their relation being one-to-many. We also add a foreign key constraint with onDelete cascade action. This means that every time a user is being deleted its associated phonenumbers will also be deleted.

The integrity constraints listed above are case sensitive and must be in upper case when being defined in your schema. Below is an example where the database delete cascading is used.

class Phonenumber extends Doctrine_Record
{
    // ...

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

        // ...

        $this->hasOne('User', array(
                'local' => 'user_id',
                'foreign' => 'id',
                'onDelete' => 'CASCADE'
            )
        );
    }
}

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

---
# schema.yml

# ...
Phonenumber:
# ...
  relations:
    # ...
    User:
      local: user_id
      foreign: id
      onDelete: CASCADE

Notice how the integrity constraints are placed on the side where the foreign key exists. This is required in order for the integrity constraints to be exported to your database properly.

Indexes

Introduction

Indexes are used to find rows with specific column values quickly. Without an index, the database must begin with the first row and then read through the entire table to find the relevant rows.

The larger the table, the more this consumes time. If the table has an index for the columns in question, the database can quickly determine the position to seek to in the middle of the data file without having to look at all the data. If a table has 1,000 rows, this is at least 100 times faster than reading rows one-by-one.

Indexes come with a cost as they slow down the inserts and updates. However, in general you should always use indexes for the fields that are used in SQL where conditions.

Adding indexes

You can add indexes by using Doctrine_Record::index. An example of adding a simple index to field called name:

The following index examples are not meant for you to actually add to your test Doctrine environment. They are only meant to demonstrate the API for adding indexes.

class IndexTest extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');

        $this->index('myindex', array(
                'fields' => array('name')
            )
        );
    }
}

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

---
IndexTest:
  columns:
    name: string
  indexes:
    myindex:
      fields: [name]

An example of adding a multi-column index to field called name:

class MultiColumnIndexTest extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');
        $this->hasColumn('code', 'string');

        $this->index('myindex', array(
                'fields' => array('name', 'code')
            )
        );
    }
}

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

---
MultiColumnIndexTest:
  columns:
    name: string
    code: string
  indexes:
    myindex:
      fields: [name, code]

An example of adding multiple indexes on same table:

class MultipleIndexTest extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');
        $this->hasColumn('code', 'string');
        $this->hasColumn('age', 'integer');

        $this->index('myindex', array(
                'fields' => array('name', 'code')
            )
        );

        $this->index('ageindex', array(
                'fields' => array('age')
            )
        );
    }
}

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

---
MultipleIndexTest:
  columns:
    name: string
    code: string
    age: integer
  indexes:
    myindex:
      fields: [name, code]
    ageindex:
      fields: [age]

Index options

Doctrine offers many index options, some of them being database specific. Here is a full list of available options:

Name Description
sorting A string value that can be either 'ASC' or 'DESC'.
length Index length (only some drivers support this).
primary Whether or not the index is a primary index.
type A string value that can be unique, fulltext, gist or gin.

Here is an example of how to create a unique index on the name column.

class MultipleIndexTest extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');
        $this->hasColumn('code', 'string');
        $this->hasColumn('age', 'integer');

        $this->index('myindex', array(
                'fields' => array(
                    'name' => array(
                        'sorting' => 'ASC',
                        'length'  => 10),
                        'code'
                    ),
                'type' => 'unique',
            )
        );
    }
}

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

---
MultipleIndexTest:
  columns:
    name: string
    code: string
    age: integer
  indexes:
    myindex:
      fields:
        name:
          sorting: ASC
          length: 10
        code: -
      type: unique

Special indexes

Doctrine supports many special indexes. These include Mysql FULLTEXT and Pgsql GiST indexes. In the following example we define a Mysql FULLTEXT index for the field 'content'.

// models/Article.php

class Article extends Doctrine_Record
{
    public function setTableDefinition()
    {
          $this->hasColumn('name', 'string', 255);
        $this->hasColumn('content', 'string');

        $this->option('type', 'MyISAM');

        $this->index('content', array(
                'fields' => array('content'),
                'type'   => 'fulltext'
            )
        );
    }
}

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

---
# schema.yml

# ...
Article:
  options:
    type: MyISAM
  columns:
    name: string(255)
    content: string
  indexes:
    content:
      fields: [content]
      type: fulltext

Notice how we set the table type to MyISAM. This is because the fulltext index type is only supported in MyISAM so you will receive an error if you use something like InnoDB.

Checks

You can create any kind of CHECK constraints by using the check() method of the Doctrine_Record. In the last example we add constraint to ensure that price is always higher than the discounted price.

// models/Product.php

class Product extends Doctrine_Record
{
    public function setTableDefinition()
    {
        // ...

        $this->check('price > discounted_price');
    }

    // ...
}

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

---
# schema.yml

# ...
Product:
# ...
  checks:
    price_check: price > discounted_price

Generates (in pgsql):

CREATE TABLE product (
    id INTEGER,
    price NUMERIC,
    discounted_price NUMERIC,
    PRIMARY KEY(id),
    CHECK (price >= 0),
    CHECK (price <= 1000000),
    CHECK (price > discounted_price))

Some databases don't support CHECK constraints. When this is the case Doctrine simply skips the creation of check constraints.

If the Doctrine validators are turned on the given definition would also ensure that when a record is being saved its price is always greater than zero.

If some of the prices of the saved products within a transaction is below zero, Doctrine throws Doctrine_Validator_Exception and automatically rolls back the transaction.

Table Options

Doctrine offers various table options. All table options can be set via the Doctrine_Record::option function.

For example if you are using MySQL and want to use INNODB tables it can be done as follows:

class MyInnoDbRecord extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');

        $this->option('type', 'INNODB');
    }
}

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

---
MyInnoDbRecord:
  columns:
    name: string
  options:
    type: INNODB

In the following example we set the collate and character set options:

class MyCustomOptionRecord extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');

        $this->option('collate', 'utf8_unicode_ci');
        $this->option('charset', 'utf8');
    }
}

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

---
MyCustomOptionRecord:
  columns:
    name: string
  options:
    collate: utf8_unicode_ci
    charset: utf8

It is worth noting that for certain databases (Firebird, MySql and PostgreSQL) setting the charset option might not be enough for Doctrine to return data properly. For those databases, users are advised to also use the setCharset function of the database connection:

$conn = Doctrine_Manager::connection();
$conn->setCharset('utf8');

You can set the default charset and collate at the manager, connection or table level just like all the other configurations in Doctrine.

Set globally on a manager instance.

// bootstrap.php

// ...

$manager->setCollate('utf8_unicode_ci');
$manager->setCharset('utf8');

The same can be set on the connection level.

// bootstrap.php

// ...

$connection->setCollate('utf8_unicode_ci');
$connection->setCharset('utf8');

You can also set this at the table level like always:

class User extends Doctrine_Record
{
    public function setTableDefinition()
    {
        // ...

        $this->setCollate('utf8_unicode_ci');
        $this->setCharset('utf8');
    }
}

Transitive Persistence

Doctrine offers both database and application level cascading operations. This section will explain in detail how to setup both application and database level cascades.

Application-Level Cascades

Since it can be quite cumbersome to save and delete individual objects, especially if you deal with an object graph, Doctrine provides application-level cascading of operations.

Save Cascades

You may already have noticed that save() operations are already cascaded to associated objects by default.

Delete Cascades

Doctrine provides a second application-level cascade style: delete. Unlike the save() cascade, the delete cascade needs to be turned on explicitly as can be seen in the following code snippet:

// models/User.php

class User extends BaseUser
{
    // ...

    public function setUp()
    {
        parent::setup();

        // ...

        $this->hasMany('Address as Addresses', array(
                'local' => 'id',
                'foreign' => 'user_id',
                'cascade' => array('delete')
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
# ...
  relations:
    # ...
    Addresses:
      class: Address
      local: id
      foreign: user_id
      cascade: [delete]

The cascade option is used to specify the operations that are cascaded to the related objects on the application-level.

Please note that the only currently supported value is delete, more options will be added in future releases of Doctrine.

In the example above, Doctrine would cascade the deletion of a User to it's associated Addresses. The following describes the generic procedure when you delete a record through $record->delete():

1. Doctrine looks at the relations to see if there are any deletion cascades it needs to apply. If there are no deletion cascades, go to 3).

2. For each relation that has a delete cascade specified, Doctrine verifies that the objects that are the target of the cascade are loaded. That usually means that Doctrine fetches the related objects from the database if they're not yet loaded.(Exception: many-valued associations are always re-fetched from the database, to make sure all objects are loaded). For each associated object, proceed with step 1).

3. Doctrine orders all deletions and executes them in the most efficient way, maintaining referential integrity.

From this description one thing should be instantly clear: Application-level cascades happen on the object-level, meaning operations are cascaded from one object to another and in order to do that the participating objects need to be available.

This has some important implications:

  • Application-level delete cascades don't perform well on many-valued associations when there are a lot of objects in the related collection (that is because they need to be fetched from the database, the actual deletion is pretty efficient).
  • Application-level delete cascades do not skip the object lifecycle as database-level cascades do (see next chapter). Therefore all registered event listeners and other callback methods are properly executed in an application-level cascade.

Database-Level Cascades

Some cascading operations can be done much more efficiently at the database level. The best example is the delete cascade.

Database-level delete cascades are generally preferrable over application-level delete cascades except:

  • Your database does not support database-level cascades (i.e. when using MySql with MYISAM tables).
  • You have listeners that listen on the object lifecycle and you want them to get invoked.

Database-level delete cascades are applied on the foreign key constraint. Therefore they're specified on that side of the relation that owns the foreign key. Picking up the example from above, the definition of a database-level cascade would look as follows:

// models/Address.php

class Address extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer');
        $this->hasColumn('address', 'string', 255);
        $this->hasColumn('country', 'string', 255);
        $this->hasColumn('city', 'string', 255);
        $this->hasColumn('state', 'string', 2);
        $this->hasColumn('postal_code', 'string', 25);
    }

    public function setUp()
    {
        $this->hasOne('User', array(
                'local' => 'user_id',
                'foreign' => 'id',
                'onDelete' => 'CASCADE'
            )
        );
    }
}

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

---
# schema.yml

# ...
Address:
  columns:
    user_id: integer
    address: string(255)
    country: string(255)
    city: string(255)
    state: string(2)
    postal_code: string(25)
  relations:
    User:
      local: user_id
      foreign: id
      onDelete: CASCADE

The onDelete option is translated to proper DDL/DML statements when Doctrine creates your tables.

Note that 'onDelete' => 'CASCADE' is specified on the Address class, since the Address owns the foreign key (user_id) and database-level cascades are applied on the foreign key.

Currently, the only two supported database-level cascade styles are for onDelete and onUpdate. Both are specified on the side that owns the foreign key and applied to your database schema when Doctrine creates your tables.

Conclusion

Now that we know everything about how to define our Doctrine models, I think we are ready to move on to learning about how to work with models in your application.

This is a very large topic as well so take a break, grab a mountain dew and hurry back for the next chapter.

Working with Models

Define Test Schema

Remember to delete any existing schema information and models from previous chapters.

$ rm schema.yml
$ touch schema.yml
$ rm -rf models/*

For the next several examples we will use the following schema:

// models/User.php

class User extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('username', 'string', 255, array(
                'type' => 'string',
                'length' => '255'
            )
        );

        $this->hasColumn('password', 'string', 255, array(
                'type' => 'string',
                'length' => '255'
            )
        );
    }

    public function setUp()
    {
        $this->hasMany('Group as Groups', array(
                'refClass' => 'UserGroup',
                'local' => 'user_id',
                'foreign' => 'group_id'
            )
        );

        $this->hasOne('Email', array(
                'local' => 'id',
                'foreign' => 'user_id'
            )
        );

        $this->hasMany('Phonenumber as Phonenumbers', array(
                'local' => 'id',
                'foreign' => 'user_id'
            )
        );
    }
}

// models/Email.php

class Email extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer', null, array(
                'type' => 'integer'
            )
        );

        $this->hasColumn('address', 'string', 255, array(
                'type' => 'string',
                'length' => '255'
            )
        );
    }

    public function setUp()
    {
        $this->hasOne('User', array(
                'local' => 'user_id',
                'foreign' => 'id'
            )
        );
    }
}

// models/Phonenumber.php

class Phonenumber extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer', null, array(
                'type' => 'integer'
            )
        );

        $this->hasColumn('phonenumber', 'string', 255, array(
                'type' => 'string',
                'length' => '255'
            )
        );
        $this->hasColumn('primary_num', 'boolean');
    }

    public function setUp()
    {
        $this->hasOne('User', array(
                'local' => 'user_id',
                'foreign' => 'id'
            )
        );
    }
}

// models/Group.php

class Group extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->setTableName('groups');
        $this->hasColumn('name', 'string', 255, array(
                'type' => 'string',
                'length' => '255'
            )
        );
    }

    public function setUp()
    {
        $this->hasMany('User as Users', array(
                'refClass' => 'UserGroup',
                'local' => 'group_id',
                'foreign' => 'user_id'
            )
        );
    }
}

// models/UserGroup.php

class UserGroup extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer', null, array(
                'type' => 'integer',
                'primary' => true
            )
        );

        $this->hasColumn('group_id', 'integer', null, array(
                'type' => 'integer',
                'primary' => true
            )
        );
    }
}

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

---
# schema.yml

User:
  columns:
    username: string(255)
    password: string(255)
  relations:
    Groups:
      class: Group
      local: user_id
      foreign: group_id
      refClass: UserGroup
      foreignAlias: Users

Email:
  columns:
    user_id: integer
    address: string(255)
  relations:
    User:
      foreignType: one

Phonenumber:
  columns:
    user_id: integer
    phonenumber: string(255)
    primary_num: boolean
  relations:
    User:
      foreignAlias: Phonenumbers

Group:
  tableName: groups
  columns:
    name: string(255)

UserGroup:
  columns:
    user_id:
      type: integer
      primary: true
    group_id:
      type: integer
      primary: true

Now that you have your schema defined you can instantiate the database by simply running the generate.php script we so conveniently created in the previous chapter.

$ php generate.php

Dealing with Relations

Creating Related Records

Accessing related records in Doctrine is easy: you can use exactly the same getters and setters as for the record properties.

You can use any of the three ways above, however the last one is the recommended one for array portability purposes.

// test.php

// ...
$user = new User();
$user['username'] = 'jwage';
$user['password'] = 'changeme';

$email = $user->Email;

$email = $user->get('Email');

$email = $user['Email'];

When accessing a one-to-one related record that doesn't exist, Doctrine automatically creates the object. That is why the above code is possible.

// test.php

// ...
$user->Email->address = 'jonwage@gmail.com';
$user->save();

When accessing one-to-many related records, Doctrine creates a Doctrine_Collection for the related component. Lets say we
have users and phonenumbers and their relation is one-to-many. You can add phonenumbers easily as shown above:

// test.php

// ...
$user->Phonenumbers[]->phonenumber = '123 123';
$user->Phonenumbers[]->phonenumber = '456 123';
$user->Phonenumbers[]->phonenumber = '123 777';

Now we can easily save the user and the associated phonenumbers:

// test.php

// ...
$user->save();

Another way to easily create a link between two related components is by using Doctrine_Record::link(). It often happens that you have two existing records that you would like to relate (or link) to one another. In this case, if there is a relation defined between the involved record classes, you only need the identifiers of the related record(s):

Lets create a few new Phonenumber objects and keep track of the new phone number identifiers::

// test.php

// ...
$phoneIds = array();

$phone1 = new Phonenumber();
$phone1['phonenumber'] = '555 202 7890';
$phone1->save();

$phoneIds[] = $phone1['id'];

$phone2 = new Phonenumber();
$phone2['phonenumber'] = '555 100 7890';
$phone2->save();

$phoneIds[] = $phone2['id'];

Let's link the phone numbers to the user, since the relation to Phonenumbers exists for the User record

// test.php

$user = new User();
$user['username'] = 'jwage';
$user['password'] = 'changeme';
$user->link('Phonenumbers', $phoneIds);
$user->save();

Remember to save the record after calling link(). By default the links are not persisted until you save the record. If you wish to save the links to the database instantly then you can pass the third argument $now with a value of true.

$user = new User();
$user['username'] = 'jwage';
$user['password'] = 'changeme';
$user->save();

Now the following will issue query to database creating links because of the third argument being true. Without it you would have to call save() to persist the links.

$user->link('Phonenumbers', $phoneIds, true);

If a relation to the User record class is defined for the Phonenumber record class, you may even do this:

First create a user to work with:

// test.php

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

Now create a new Phonenumber instance:

// test.php

// ...
$phone1 = new Phonenumber();
$phone1['phonenumber'] = '555 202 7890';
$phone1->save();

Now we can link the User to our Phonenumber:

// test.php

// ...
$phone1->link('User', array($user['id']));
$phone1->save();

We can create another phone number:

// test.php

// ...
$phone2 = new Phonenumber();
$phone2['phonenumber'] = '555 100 7890';
$phone2->save();

Let's link this Phonenumber to our User too:

// test.php

// ...
$phone2->link('User', array($user['id']));
$phone2->save();

Retrieving Related Records

You can retrieve related records by the very same Doctrine_Record methods as in the previous subchapter. Please note that whenever you access a related component that isn't already loaded Doctrine uses one SQL SELECT statement for the fetching, hence the following example executes 3 SQL SELECTs.

// test.php

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

echo $user->Email['address'];

echo $user->Phonenumbers[0]->phonenumber;

Much more efficient way of doing this is using DQL. The following example uses only one SQL query for the retrieval of related components.

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Email e')
    ->leftJoin('u.Phonenumbers p')
    ->where('u.id = ?', 1);

$user = $q->fetchOne();

echo $user->Email['address'];

echo $user->Phonenumbers[0]['phonenumber'];

Updating Related Records

You can update the related records by calling save for each related object / collection individually or by calling save on the object that owns the other objects. You can also call Doctrine_Connection::flush which saves all pending objects.

// test.php

// ...
$user->Email['address'] = 'koskenkorva@drinkmore.info';

$user->Phonenumbers[0]['phonenumber'] = '123123';

$user->save();

In the above example calling $user->save() saves the email and phonenumber.

Deleting Related Records

You can delete related records individually be calling delete() on a record or on a collection.

Here you can delete an individual related record:

// test.php

// ...
$user->Email->delete();

You can delete an individual record from within a collection of records:

// test.php

// ...
$user->Phonenumbers[3]->delete();

You could delete the entire collection if you wanted:

// test.php

// ...
$user->Phonenumbers->delete();

Or can just delete the entire user and all related objects:

// test.php

// ...
$user->delete();

Usually in a typical web application the primary keys of the related objects that are to be deleted come from a form. In this case the most efficient way of deleting the related records is using DQL DELETE statement. Lets say we have once again Users and Phonenumbers with their relation being one-to-many. Deleting the given Phonenumbers for given user id can be achieved as follows:

// test.php

// ...
$q = Doctrine_Query::create()
    ->delete('Phonenumber')
    ->addWhere('user_id = ?', 5)
    ->whereIn('id', array(1, 2, 3));

$numDeleted = $q->execute();

Sometimes you may not want to delete the Phonenumber records but to simply unlink the relations by setting the foreign key fields to null. This can of course be achieved with DQL but perhaps to most elegant way of doing this is by using Doctrine_Record::unlink().

Please note that the unlink() method is very smart. It not only sets the foreign fields for related Phonenumbers to null but it also removes all given Phonenumber references from the User object.

Lets say we have a User who has three Phonenumbers (with identifiers 1, 2 and 3). Now unlinking the Phonenumbers 1 and 3 can be achieved as easily as:

// test.php

// ...
$user->unlink('Phonenumbers', array(1, 3));
$user->save();

echo $user->Phonenumber->count(); // 1

Working with Related Records

Testing the Existence of a Relation

The below example would return false because the relationship has not been instantiated yet:

// test.php

// ...
$user = new User();
if (isset($user->Email)) {
    // ...
}

Now the next example will return true because we instantiated the Email relationship:

// test.php

// ...
$obj->Email = new Email();

if(isset($obj->Email)) {
    // ...
}

Many-to-Many Relations

Doctrine requires that Many-to-Many relationships be bi-directional. For example: both User must have many Groups and Group must have many User.

Creating a New Link

Lets say we have two classes User and Group which are linked through a GroupUser association class. When working with transient (new) records the fastest way for adding a User and couple of Groups for it is:

// test.php

// ...
$user = new User();
$user->username = 'Some User';
$user->Groups[0]->username = 'Some Group';
$user->Groups[1]->username = 'Some Other Group';
$user->save();

However in real world scenarios you often already have existing groups, where you want to add a given user. The most efficient way of doing this is:

// test.php

// ...
$groupUser = new GroupUser();
$groupUser->user_id = $userId;
$groupUser->group_id = $groupId;
$groupUser->save();

Deleting a Link

The right way to delete links between many-to-many associated records is by using the DQL DELETE statement. Convenient and recommended way of using DQL DELETE is through the Query API.

// test.php

// ...
$q = Doctrine_Query::create()
    ->delete('GroupUser')
    ->addWhere('user_id = ?', 5)
    ->whereIn('group_id', array(1, 2));

$deleted = $q->execute();

Another way to unlink the relationships between related objects is through the Doctrine_Record::unlink method. However, you should avoid using this method unless you already have the parent model, since it involves querying the database first.

// test.php

// ...
$user = Doctrine::getTable('User')->find(5);
$user->unlink('Group', array(1, 2));
$user->save();

You can also unlink ALL relationships to Group by omitting the second argument:

// test.php

// ...
$user->unlink('Group');
$user->save();

While the obvious and convenient way of deleting a link between User and Group would be the following, you still should *NOT* do this:

// test.php

// ...
$user = Doctrine::getTable('User')->find(5);
$user->GroupUser->remove(0)->remove(1);
$user->save();

This is due to a fact that the call to $user->GroupUser loads all Group links for given User. This can be time-consuming task if the User belongs to many Groups. Even if the user belongs to few groups this will still execute an unnecessary SELECT statement.

Fetching Data

Doctrine provides several different "hydration modes". These are different ways in which data can be retrieved. Understanding all these modes is important to always make the right choice.

There is one major distinction that divides all the hydration modes into 2 groups: The ones that are based on identity and the ones that are not.

The hydration modes that are based on (object) identity are: HYDRATE_RECORD, HYDRATE_ARRAY. The former generates an object graph while the latter generates a nested array structure that is in many cases very similar to the object graph (Not though that HYDRATE_RECORD/HYDRATE_ARRAY can potentially produce differing results in more "complex" query scenarios due to the natural difference of objects and arrays, their comparison, equality and identity semantics!).

The fact that these two hydration modes are based on identity becomes evident through the behavior of Doctrine to auto-add PK/ID fields of classes used in a DQL query if they are not already present. This means: These two hydration modes *require* the primary keys/identifiers of all those classes that are participating in a DQL query in such a way that they are "fetched" (eg at least one field appears in the SELECT clause).

HYDRATE_RECORD/HYDRATE_ARRAY

Custom accessors and mutators will not work when hydrating data as anything except records. When you hydrate as an array it is only a static array of data and is not object oriented. If you need to add custom values to your hydrated arrays you can use the some of the events such as preHydrate and postHydrate

Lets consider we have users and phonenumbers with their relation being one-to-many. Now consider the following plain sql query:

$dbh->fetchAll('SELECT u.id, u.name, p.phonenumber FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id');

If you are familiar with these kind of one-to-many joins it may be familiar to you how the basic result set is constructed. Whenever the user has more than one phonenumbers there will be duplicated data in the result set. The result set might look something like:

index  | u.id | u.name         | p.phonenumber |
 0      |   1  | Jack Daniels   | 123 123       |
 1      |   1  | Jack Daniels   | 456 456       |
 2      |   2  | John Beer      | 111 111       |
 3      |   3  | John Smith     | 222 222       |
 4      |   3  | John Smith     | 333 333       |
 5      |   3  | John Smith     | 444 444       |

Here Jack Daniels has 2 phonenumbers, John Beer has one whereas John Smith has 3 phonenumbers. You may notice how clumsy this result set is. Its hard to iterate over it as you would need some duplicate data checkings here and there.

Doctrine identity hydration removes all duplicated data. It also performs many other things such as:

  1. Custom indexing of result set elements (only with HYDRATE_ARRAY/HYDRATE_RECORD)
  2. Value casting and preparation (with all hydration modes except HYDRATE_NONE)
  3. Value assignment listening (only with HYDRATE_ARRAY/HYDRATE_RECORD)
  4. Makes multi-dimensional array out of the two-dimensional result set array, the number of dimensions is equal to the number of nested joins (only with HYDRATE_ARRAY)

Now consider the DQL equivalent of the SQL query we used:

$q = Doctrine_Query::create()
          ->select('u.id, u.name, p.phonenumber')
          ->from('User u')
          ->leftJoin('u.Phonenumber p');

$array = $q->execute(array(), Doctrine::HYDRATE_ARRAY);

The structure of this hydrated array would look like:

array(0 => array('id' => 1,
                 'name' => 'Jack Daniels',
                 'Phonenumbers' =>
                    array(0 => array('phonenumber' => '123 123'),
                          1 => array('phonenumber' => '456 456'))),
      1 => array('id' => 2,
                 'name' => 'John Beer',
                 'Phonenumbers' =>
                    array(0 => array('phonenumber' => '111 111'))),
      2 => array('id' => 3,
                 'name' => 'John Smith',
                 'Phonenumbers' =>
                    array(0 => array('phonenumber' => '111 111')),
                          2 => array('phonenumber' => '222 222'),
                          3 => array('phonenumber' => '333 333'))));

This structure also applies to the hydration of objects(records) which is the default hydration mode of Doctrine. The only differences are that the individual elements are represented as Doctrine_Record objects and the arrays converted into Doctrine_Collection objects. Whether dealing with arrays or objects you can:

  1. Iterate over the results using foreach
  2. Access individual elements using array access brackets
  3. Get the number of elements using count() function
  4. Check if given element exists using isset()
  5. Unset given element using unset()

The other group of hydration modes are not based on identity. These are: HYDRATE_NONE, HYDRATE_SCALAR and HYDRATE_SINGLE_SCALAR.

HYDRATE_NONE

This is the fastest but least useful hydration mode. It is equal to a $pdoStmt->fetchAll(PDO::FETCH_NUM); The reason Doctrine uses FETCH_NUM is that the column aliases in the SQL query are generated by Doctrine, therefore FETCH_ASSOC would be of no use because you don't know what the names of the columns in the result set would be. This hydration mode is mainly useful for debugging purposes or for some other simple scenarios.

HYDRATE_SCALAR
This hydration mode creates a flat/rectangular result set that can contain duplicate data. It's best to think of this as a normal SQL result set with a few subtle but important differences:

  1. column names are converted to field names (column aliases)
  2. data type conversions are applied

Let's look at an example DQL query: SELECT u.*, p.* FROM User u LEFT JOIN u.phonenumbers p The result with HYDRATE_SCALAR could look like this:

array(
    0 => array(
        'u_id' => '1',
        'u_name' => 'roman',
        'p_number' => '1234',
        'p_id' => '42'
        ),
    1 => array(
        'u_id' => '1',
        'u_name' => 'roman',
        'p_number' => '1111',
        'p_id' => '43'
        ),
        ...
    )

As you can see, it looks like a regular SQL result set of a JOINed SQL query. However, in order to avoid ambiguities between field names all field names in the result set are prefixed with the DQL alias that you specified in the query. This makes this hydration mode robust even for complex queries, yet the result is very predictable because the DQL aliases as well as the field names stem from your DQL query and your object model, respectively. In addition, as noted earlier, data type conversions take place where necessary.

HYDRATE_SINGLE_SCALAR

This is basically a sub-type of HYDRATE_SCALAR. This hydration mode turns out to be very useful. Without further explanation, let's look at some examples:

$q = Doctrine_Query::create();
$q->select("u.name")->from("User u");
$res = $q->execute(array(), Doctrine::HYDRATE_SINGLE_SCALAR);
echo $res; // prints 'romanb'
$q = Doctrine_Query::create();
$q->select("COUNT(u.id) num_ids")->from("User u");
$res = $q->execute(array(), Doctrine::HYDRATE_SINGLE_SCALAR);
echo $res; // echos '1' or whatever the count is..

As you can see this hydration mode is self-explanatory. Gone are the times of having to grab such a result from $result[0][0] or similar.

You should use array or scalar hydration when you only need data for read-only purposes, whereas you should use the record(object) hydration when you need/want to operate on the data and/or use the business logic that is coded in your entities (records).

Sample Queries

Count number of records for a relationship:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.*, COUNT(DISTINCT p.id) AS num_phonenumbers')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->groupBy('u.id');

$users = $q->fetchArray();

echo $users[0]['Phonenumbers'][0]['num_phonenumbers'];

Retrieve Users and the Groups they belong to:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Groups g');

$users = $q->fetchArray();

foreach ($users[0]['Groups'] as $group) {
    echo $group['name'];
}

Simple WHERE with one parameter value:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.username = ?', 'jwage');

$users = $q->fetchArray();

Multiple WHERE with multiple parameters values:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->where('u.username = ? AND p.id = ?', array(1, 1));

$users = $q->fetchArray();

You can also optionally use the andWhere() method to add to the existing where parts.

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->where('u.username = ?', 1)
    ->andWhere('p.id = ?', 1);

$users = $q->fetchArray();

Using whereIn() convenience method:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->whereIn('u.id', array(1, 2, 3));

$users = $q->fetchArray();

The following is the same as above example:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.id IN (1, 2, 3)');

$users = $q->fetchArray();

Using DBMS function in your WHERE:

// test.php

// ...
$userEncryptedKey = 'a157a558ac00449c92294c7fab684ae0';
$q = Doctrine_Query::create()
    ->from('User u')
    ->where("MD5(CONCAT(u.username, 'secret_key')) = ?", $userEncryptedKey);

$user = $q->fetchOne();

$q = Doctrine_Query::create()
    ->from('User u')
    ->where('LOWER(u.username) = LOWER(?)', 'jwage');

$user = $q->fetchOne();

Limiting result sets using aggregate functions. Limit to users with more than one phonenumber:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.*, COUNT(DISTINCT p.id) AS num_phonenumbers')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->having('num_phonenumbers > 1')
    ->groupBy('u.id');

$users = $q->fetchArray();

Join only primary phonenumbers using WITH:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Phonenumbers p WITH p.primary_num = ?', true);

$users = $q->fetchArray();

Selecting certain columns for optimization:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username, p.phone')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p');

$users = $q->fetchArray();

Using a wildcard to select all User columns but only one Phonenumber column:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.*, p.phonenumber')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p');

$users = $q->fetchArray();

Perform DQL delete with simple WHERE:

// test.php

// ...
$q = Doctrine_Query::create()
    ->delete('Phonenumber')
    ->addWhere('user_id = 5');

$deleted = $q->execute();

Perform simple DQL update for a column:

// test.php

// ...
$q = Doctrine_Query::create()
    ->update('User u')
    ->set('u.is_active', '?', true)
    ->where('u.id = ?', 1);

$updated = $q->execute();

Perform DQL update with DBMS function. Make all usernames lowercase:

// test.php

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

$updated = $q->execute();

Using mysql LIKE to search for records:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.username LIKE ?', '%jwage%');

$users = $q->fetchArray();

Use the INDEXBY keyword to hydrate the data where the key of record entry is the name of the column you assign:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u INDEXBY u.username');

$users = $q->fetchArray();

Now we can print the user with the username of jwage:

// test.php

// ...
print_r($users['jwage']);

Using positional parameters

$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.username = ?', array('Arnold'));

$users = $q->fetchArray();

Using named parameters

$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.username = :username', array(':username' => 'Arnold'));

$users = $q->fetchArray();

Using subqueries in your WHERE. Find users not in group named Group 2:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.id NOT IN (SELECT u.id FROM User u2 INNER JOIN u2.Groups g WHERE g.name = ?)', 'Group 2');

$users = $q->fetchArray();

You can accomplish this without using subqueries. The two examples below would have the same result as the example above.

Use INNER JOIN to retrieve users who have groups, excluding the group named Group 2

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->innerJoin('u.Groups g WITH g.name != ?', 'Group 2')

$users = $q->fetchArray();

Use WHERE condition to retrieve users who have groups, excluding the group named Group 2

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Groups g')
    ->where('g.name != ?', 'Group 2');

$users = $q->fetchArray();

Doctrine has many different ways you can execute queries and retrieve the data. Below are examples of all the different ways you can execute a query:

First lets create a sample query to test with:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u');

You can use array hydration with the fetchArray() method:

$users = $q->fetchArray();

You can also use array hydration by specifying the hydration method to the second argument of the execute() method:

// test.php

// ...
$users = $q->execute(array(), Doctrine::HYDRATE_ARRAY)

You can also specify the hydration method by using the setHydrationMethod() method:

$users = $q->setHydrationMode(Doctrine::HYDRATE_ARRAY)->execute(); // So is this

Sometimes you may want to totally bypass hydration and return the raw data that PDO returns:

// test.php

// ...
$users = $q->execute(array(), Doctrine::HYDRATE_NONE);

More can be read about skipping hydration in the improving performance chapter.

If you want to just fetch one record from the query:

// test.php

// ...
$user = $q->fetchOne();

// Fetch all and get the first from collection
$user = $q->execute()->getFirst();

Field Lazy Loading

Whenever you fetch an object that has not all of its fields loaded from database then the state of this object is called proxy. Proxy objects can load the unloaded fields lazily.

In the following example we fetch all the Users with the username field loaded directly. Then we lazy load the password field:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->from('User u')
    ->where('u.id = ?', 1)

$user = $q->fetchOne();

The following lazy-loads the password field and executes one additional database query to retrieve the value:

// test.php

// ...
$user->description;

Doctrine does the proxy evaluation based on loaded field count. It does not evaluate which fields are loaded on field-by-field basis. The reason for this is simple: performance. Field lazy-loading is very rarely needed in PHP world, hence introducing some kind of variable to check which fields are loaded would introduce unnecessary overhead to basic fetching.

Arrays and Objects

Doctrine_Record and Doctrine_Collection provide methods to facilitate working with arrays: toArray(), fromArray() and synchronizeWithArray().

To Array

The toArray() method returns an array representation of your records or collections. It also accesses the relationships the objects may have. If you need to print a record for debugging purposes you can get an array representation of the object and print that.

// test.php

// ...
print_r($user->toArray());

If you do not want to include the relationships in the array then you need to pass the $deep argument with a value of false:

// test.php

// ...
print_r($user->toArray(false));

From Array

If you have an array of values you want to use to fill a record or even a collection, the fromArray() method simplifies this common task.

// test.php

// ...
$data = array(
  'name' => 'John',
  'age' => '25',
  'Emails' => array(
    array('address' => 'john@mail.com'),
    array('address' => 'john@work.com')),
  'Groups' => array(1, 2, 3)
);

$user = new User();
$user->fromArray($data);
$user->save();

It is possible to use fromArray() with custom model mutators like the following:

// models/User.php

class User extends Doctrine_Record
{
    // ...

    public function setEncryptedPassword($password)
    {
        return $this->_set('password', md5($password));
    }
}

Now if when we use fromArray() we can use the setEncryptedPassword() method by passing a value named encrypted_password.

// test.php

// ...
$user->fromArray(array('encrypted_password' => 'changeme'));

Synchronize With Array

synchronizeWithArray() allows you to... well, synchronize a record with an array. So if have an array representation
of your model and modify a field, modify a relationship field or even delete or create a relationship, this changes will
be applied to the record.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.*, g.*')
    ->from('User u')
    ->leftJoin('u.Groups g')
    ->where('id = ?', 1);

$user = $q->fetchOne();

Now convert it to an array and modify some of the properties:

// test.php

// ...
$arrayUser = $user->toArray(true);

$arrayUser['username'] = 'New name';
$arrayUser['Group'][0]['name'] = 'Renamed Group';
$arrayUser['Group'][] = array('name' => 'New Group');

Now use the same query to retrieve the record and synchronize the record with the $arrayUser variable:

// test.php

// ...
$user = Doctrine_Query::create()
    ->select('u.*, g.*')
    ->from('User u')
    ->leftJoin('u.Groups g')
    ->where('id = ?', 1)
    ->fetchOne();

$user->synchronizeWithArray($arrayUser);
$user->save();

You can also synchronize relationships by specifying an array of ids to link the record to.

$user->synchronizeWithArray(array('Group' => array(1, 2, 3)));
$user->save();

The above code will remove any existing groups and link the user to the group id 1, 2, and 3.

Internally Doctrine_Record::link() and Doctrine_Record::unlink() are used to link relationships together.

Overriding the Constructor

Sometimes you want to do some operations at the creation time of your objects. Doctrine doesn't allow you to override the Doctrine_Record::__construct() method but provides an alternative:

class User extends Doctrine_Record
{
    public function construct()
    {
        $this->username = 'Test Name';
        $this->doSomething();
    }

    public function doSomething()
    {
        // ...
    }

    // ...
}

The only drawback is that it doesn't provide a way to pass parameters to the constructor.

Conclusion

By now we should know absolutely everything there is to know about models. We know how to create them, load them and most importantly we know how to use them and work with columns and relationships. Now we are ready to move on to learn about how to use the DQL (Doctrine Query Language).

DQL (Doctrine Query Language)

Introduction

Doctrine Query Language (DQL) is an Object Query Language created for helping users in complex object retrieval. You should always consider using DQL (or raw SQL) when retrieving relational data efficiently (eg. when fetching users and their phonenumbers).

In this chapter we will execute dozens of examples of how to use the Doctrine Query Language. All of these examples assume you are using the schemas defined in the previous chapters, primarily the Defining Models chapter. We will define one additional model for our testing purposes which can be found right below this note.

// models/Account.php

class Account extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string', 255);
        $this->hasColumn('amount', 'decimal');
    }
}

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

---
# schema.yml

# ...
Account:
  columns:
    name: string(255)
    amount: decimal

When compared to using raw SQL, DQL has several benefits:

  • From the start it has been designed to retrieve records(objects) not result set rows
  • DQL understands relations so you don't have to type manually sql joins and join conditions
  • DQL is portable on different databases
  • DQL has some very complex built-in algorithms like (the record limit algorithm) which can help developer to efficiently retrieve objects
  • It supports some functions that can save time when dealing with one-to-many, many-to-many relational data with conditional fetching.

If the power of DQL isn't enough, you should consider using the RawSql API for object population.

You may already be familiar with the following syntax:

DO NOT USE THE FOLLOWING CODE. It uses many sql queries for object population.

// test.php

// ...
$users = Doctrine::getTable('User')->findAll();

foreach($users as $user) {
    echo $user->username . " has phonenumbers: \n";

    foreach($user->Phonenumbers as $phonenumber) {
        echo $phonenumber->phonenumber . "\n";
    }
}

Here is the same code but implemented more efficiently using only one SQL query for object population.

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Phonenumbers p');

echo $q->getSql();

Lets take a look at the SQL that would be generated by the above query:

SELECT 
u.id AS u__id,
u.is_active AS u__is_active,
u.is_super_admin AS u__is_super_admin,
u.first_name AS u__first_name,
u.last_name AS u__last_name,
u.username AS u__username,
u.password AS u__password,
u.type AS u__type,
u.created_at AS u__created_at,
u.updated_at AS u__updated_at,
p.id AS p__id,
p.user_id AS p__user_id,
p.phonenumber AS p__phonenumber
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id

Now lets execute the query and play with the data:

// test.php

// ...
$users = $q->execute();

foreach($users as $user) {
    echo $user->username . " has phonenumbers: \n";

    foreach($user->Phonenumbers as $phonenumber) {
        echo $phonenumber->phonenumber . "\n";
    }
}

Using double quotes (") in DQL strings is discouraged. This is sensible in MySQL standard but in DQL it can be confused as an identifier. Instead it is recommended to use prepared statements for your values and it will be escaped properly.

SELECT queries

SELECT statement syntax:

SELECT
    [ALL | DISTINCT]
    <select_expr>, 
... [
FROM <components> [
WHERE <where_condition>] [
GROUP BY <groupby_expr> [ASC | DESC],
... ] [
HAVING <where_condition>] [
ORDER BY <orderby_expr> [ASC | DESC],
...] [
LIMIT <row_count>
OFF
SET
<offset>}]

The SELECT statement is used for the retrieval of data from one or more components.

Each select_expr indicates a column or an aggregate function value that you want to retrieve. There must be at least one select_expr in every SELECT statement.

First insert a few sample Account records:

// test.php

// ...
$account = new Account();
$account->name = 'test 1';
$account->amount = '100.00';
$account->save();

$account = new Account();
$account->name = 'test 2';
$account->amount = '200.00';
$account->save();

Be sure to execute test.php:

$ php test.php

Now you can test the selecting of the data with these next few sample queries:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.name')
    ->from('Account a');

echo $q->getSql();

Lets take a look at the SQL that would be generated by the above query:

SELECT 
a.id AS a__id,
a.name AS a__name
FROM account a
// test.php

// ...
$accounts = $q->execute();
print_r($accounts->toArray());

The above example would produce the following output:

$ php test.php
Array
(
    [0] => Array
        (
            [id] => 1
            [name] => test 1
            [amount] =>
        )

    [1] => Array
        (
            [id] => 2
            [name] => test 2
            [amount] =>
        )

)

An asterisk can be used for selecting all columns from given component. Even when using an asterisk the executed sql queries never actually use it (Doctrine converts asterisk to appropriate column names, hence leading to better performance on some databases).

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.*')
    ->from('Account a');

echo $q->getSql();

Compare the generated SQL from the last query example to the SQL generated by the query right above:

SELECT 
a.id AS a__id,
a.name AS a__name,
a.amount AS a__amount
FROM account a

Notice how the asterisk is replace by all the real column names that exist in the Account model.

Now lets execute the query and inspect the results:

// test.php

// ...
$accounts = $q->execute();
print_r($accounts->toArray());

The above example would produce the following output:

$ php test.php
Array
(
    [0] => Array
        (
            [id] => 1
            [name] => test 1
            [amount] => 100.00
        )

    [1] => Array
        (
            [id] => 2
            [name] => test 2
            [amount] => 200.00
        )

)

FROM clause components indicates the component or components from which to retrieve records.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username, p.*')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.user_id AS p__user_id,
p.phonenumber AS p__phonenumber
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id

The WHERE clause, if given, indicates the condition or conditions that the records must satisfy to be selected. where_condition is an expression that evaluates to true for each row to be selected. The statement selects all rows if there is no WHERE clause.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.name')
    ->from('Account a')
    ->where('a.amount > 2000');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
a.id AS a__id,
a.name AS a__name
FROM account a
WHERE a.amount > 2000

In the WHERE clause, you can use any of the functions and operators that DQL supports, except for aggregate (summary) functions. The HAVING clause can be used for narrowing the results with aggregate functions:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->having('COUNT(p.id) > 3');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
HAVING COUNT(p.id) > 3

The ORDER BY clause can be used for sorting the results

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->from('User u')
    ->orderBy('u.username');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
ORDER BY u.username

The LIMIT and OFFSET clauses can be used for efficiently limiting the number of records to a given row_count

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->from('User u')
    ->limit(20);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
LIMIT 20

Aggregate values

Aggregate value SELECT syntax:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id, COUNT(t.id) AS num_threads')
    ->from('User u, u.Threads t')
    ->where('u.id = ?', 1)
    ->groupBy('u.id');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
COUNT(f.id) AS f__0
FROM user u
LEFT JOIN forum__thread f ON u.id = f.user_id
WHERE u.id = ?
GROUP BY u.id

Now execute the query and inspect the results:

// test.php

// ...
$users = $q->execute();

You can easily access the num_threads data with the following code:

// test.php

// ...
echo $users->num_threads . ' threads found';

UPDATE queries

UPDATE statement syntax:

UPDATE <component_name> 
SET <col_name1> = <expr1> ,
<col_name2> = <expr2>
WHERE <where_condition>
ORDER BY <order_by>
LIMIT <record_count>
  • The UPDATE statement updates columns of existing records in component_name with new values and returns the number of affected records.
  • The SET clause indicates which columns to modify and the values they should be given.
  • The optional WHERE clause specifies the conditions that identify which records to update. Without WHERE clause, all records are updated.
  • The optional ORDER BY clause specifies the order in which the records are being updated.
  • The LIMIT clause places a limit on the number of records that can be updated. You can use LIMIT row_count to restrict the scope of the UPDATE. A LIMIT clause is a rows-matched restriction not a rows-changed restriction. The statement stops as soon as it has found record_count rows that satisfy the WHERE clause, whether or not they actually were changed.
// test.php

// ...
$q = Doctrine_Query::create()
    ->update('Account')
    ->set('amount', 'amount + 200')
    ->where('id > 200');

// If you just want to set the amount to a value
$q->set('amount', '?', 500);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

UPDATE account 
SET amount = amount + 200
WHERE id > 200

Now to perform the update is simple. Just execute the query:

// test.php

// ...
$rows = $q->execute();

echo $rows;

DELETE Queries

DELETE 
FROM <component_name>
WHERE <where_condition>
ORDER BY <order_by>
LIMIT <record_count>
  • The DELETE statement deletes records from component_name and returns the number of records deleted.
  • The optional WHERE clause specifies the conditions that identify which records to delete. Without WHERE clause, all records are deleted.
  • If the ORDER BY clause is specified, the records are deleted in the order that is specified.
  • The LIMIT clause places a limit on the number of rows that can be deleted. The statement will stop as soon as it has deleted record_count records.
// test.php

// ...
$q = Doctrine_Query::create()
    ->delete('Account a')
    ->where('a.id > 3');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

DELETE 
FROM account
WHERE id > 3

Now executing the DELETE query is just as you would think:

// test.php

// ...
$rows = $q->execute();

echo $rows;

When executing DQL UPDATE and DELETE queries the executing of a query returns the number of affected rows.

FROM clause

Syntax:


FROM <component_reference> [[LEFT | INNER] JOIN <component_reference>] ...

The FROM clause indicates the component or components from which to retrieve records. If you name more than one component, you are performing a join. For each table specified, you can optionally specify an alias.

Consider the following DQL query:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u

Here User is the name of the class (component) and u is the alias. You should always use short aliases, since most of the time those make the query much shorther and also because when using for example caching the cached form of the query takes less space when short aliases are being used.

JOIN syntax

DQL JOIN Syntax:

JOIN <component_reference1>] [ON | WITH] <join_condition1> [INDEXBY] <map_condition1>,
[[LEFT | INNER] JOIN <component_reference2>] [ON | WITH] <join_condition2> [INDEXBY] <map_condition2>,
...
[[LEFT | INNER] JOIN <component_referenceN>] [ON | WITH] <join_conditionN> [INDEXBY] <map_conditionN>

DQL supports two kinds of joins INNER JOINs and LEFT JOINs. For each joined component, you can optionally specify an alias.

The default join type is LEFT JOIN. This join can be indicated by the use of either LEFT JOIN clause or simply ',', hence the following queries are equal:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id, p.id')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p');

$q = Doctrine_Query::create()
    ->select('u.id, p.id')
    ->from('User u, u.Phonenumbers p');

echo $q->getSql();

The recommended form is the first because it is more verbose and easier to read and understand what is being done.

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
p.id AS p__id
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id

Notice how the JOIN condition is automatically added for you. This is because Doctrine knows how User and Phonenumber are related so it is able to add it for you.

INNER JOIN produces an intersection between two specified components (that is, each and every record in the first component is joined to each and every record in the second component). So basically INNER JOIN can be used when you want to efficiently fetch for example all users which have one or more phonenumbers.

By default DQL auto-adds the primary key join condition:

// test.php

// ...
$q = Doctrine_Query::create()
  ->select('u.id, p.id')
  ->from('User u')
  ->leftJoin('u.Phonenumbers p');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
p.id AS p__id
FROM User u
LEFT JOIN Phonenumbers p ON u.id = p.user_id

ON keyword

If you want to override this behavior and add your own custom join condition you can do it with the ON keyword. Consider the following DQL query:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id, p.id')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p ON u.id = 2');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
p.id AS p__id
FROM User u
LEFT JOIN Phonenumbers p ON u.id = 2

Notice how the ON condition that would be normally automatically added is not present and the user specified condition is used instead.

WITH keyword

Most of the time you don't need to override the primary join condition, rather you may want to add some custom conditions. This can be achieved with the WITH keyword.

// test.php

// ...
$q = Doctrine_Query::create()
  ->select('u.id, p.id')
  ->from('User u')
  ->leftJoin('u.Phonenumbers p WITH u.id = 2');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
p.id AS p__id
FROM User u
LEFT JOIN Phonenumbers p ON u.id = p.user_id
AND u.id = 2

Notice how the ON condition isn't completely replaced. Instead the conditions you specify are appended on to the automatic condition that is added for you.

The Doctrine_Query API offers two convenience methods for adding JOINS. These are called innerJoin() and leftJoin(), which usage should be quite intuitive as shown below:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->leftJoin('u.Groups g')
    ->innerJoin('u.Phonenumbers p WITH u.id > 3')
    ->leftJoin('u.Email e');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
LEFT JOIN user_group u2 ON u.id = u2.user_id
LEFT JOIN groups g ON g.id = u2.group_id
INNER JOIN phonenumber p ON u.id = p.user_id
AND u.id > 3
LEFT JOIN email e ON u.id = e.user_id

INDEXBY keyword

The INDEXBY keyword offers a way of mapping certain columns as collection / array keys. By default Doctrine indexes multiple elements to numerically indexed arrays / collections. The mapping starts from zero. In order to override this behavior you need to use INDEXBY keyword as shown above:

// test.php

// ...
$q = Doctrine_Query::create()
  ->from('User u INDEXBY u.username');

$users = $q->execute();

The INDEXBY keyword does not alter the generated SQL. It is simply used internally by Doctrine_Query to hydrate the data with the specified column as the key of each record in the collection.

Now the users in $users collection are accessible through their names:

// test.php

// ...
echo $user['jack daniels']->id;

The INDEXBY keyword can be applied to any given JOIN. This means that any given component can have each own indexing behavior. In the following we use distinct indexing for both Users and Groups.

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u INDEXBY u.username')
    ->innerJoin('u.Groups g INDEXBY g.name');

$users = $q->execute();

Now lets print out the drinkers club's creation date.

// test.php

// ...
echo $users['jack daniels']->Groups['drinkers club']->createdAt;

WHERE clause

Syntax:


WHERE <where_condition>
  • The WHERE clause, if given, indicates the condition or conditions that the records must satisfy to be selected.
  • where_condition is an expression that evaluates to true for each row to be selected.
  • The statement selects all rows if there is no WHERE clause.
  • When narrowing results with aggregate function values HAVING clause should be used instead of WHERE clause

You can use the addWhere(), andWhere(), orWhere(), whereIn(), andWhereIn(), orWhereIn(), whereNotIn(), andWhereNotIn(), orWhereNotIn() functions for building complex where conditions using Doctrine_Query objects.

Here is an example where we retrieve all active registered users or super administrators:

// test.php

// ...
$q = Doctrine_Query::create()
  ->select('u.id')
  ->from('User u')
  ->where('u.type = ?', 'registered')
  ->andWhere('u.is_active = ?', 1)
  ->orWhere('u.is_super_admin = ?', 1);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.type = ?
AND u.is_active = ?
OR u.is_super_admin = ?

Conditional expressions

Literals

Strings

A string literal that includes a single quote is represented by two single quotes; for example: ´´literal´s´´.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id, u.username')
    ->from('User u')
    ->where('u.username = ?', 'Vincent');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE u.username = ?

Because we passed the value of the username as a parameter to the where() method it is not included in the generated SQL. PDO handles the replacement when you execute the query. To check the parameters that exist on a Doctrine_Query instance you can use the getParams() method.

Integers

Integer literals support the use of PHP integer literal syntax.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.id')
    ->from('User u')
    ->where('u.id = 4');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.id = 4

Floats

Float literals support the use of PHP float literal syntax.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.id')
    ->from('Account a')
    ->where('a.amount = 432.123');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
a.id AS a__id
FROM account a
WHERE a.amount = 432.123

Booleans

The boolean literals are true and false.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.id')
    ->from('User u')
    ->where('u.is_super_admin = true');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.is_super_admin = 1

Enums

The enumerated values work in the same way as string literals.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.id')
    ->from('User u')
    ->where("u.type = 'admin'");

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.type = 'admin'

Predefined reserved literals are case insensitive, although its a good standard to write them in uppercase.

Input parameters

Here are some examples of using positional parameters:

Single positional parameter:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->where('u.username = ?', array('Arnold'));

echo $q->getSql();

When the passed parameter for a positional parameter contains only one value you can simply pass a single scalar value instead of an array containing one value.

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.username = ?

Multiple positional parameters:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.id > ? AND u.username LIKE ?', array(50, 'A%'));

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE (u.id > ?
AND u.username LIKE ?)

Here are some examples of using named parameters:

Single named parameter:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->where('u.username = :name', array(':name' => 'Arnold'));

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.username = :name

Named parameter with a LIKE statement:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->where('u.id > :id', array(':id' => 50))
    ->andWhere('u.username LIKE :name', array(':name' => 'A%'));

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.id > :id
AND u.username LIKE :name

Operators and operator precedence

The operators are listed below in order of decreasing precedence.

Operator Description
. Navigation operator
Arithmetic operators:
+, - unary
*, / multiplication and division
+, - addition and subtraction
=, >, >=, <, <=, <> (not equal), Comparison operators
[NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY
Logical operators:
NOT
AND
OR

In expressions

Syntax:

<operand> IN (<subquery>|<value list>)

An IN conditional expression returns true if the operand is found from result of the subquery or if its in the specificied comma separated value list, hence the IN expression is always false if the result of the subquery is empty.

When value list is being used there must be at least one element in that list.

Here is an example where we use a subquery for the IN:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->where('u.id IN (SELECT u.id FROM User u INNER JOIN u.Groups g WHERE g.id = ?)', 1);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.id IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id
WHERE g.id = ?)

Here is an example where we just use a list of integers:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->whereIn('u.id', array(1, 3, 4, 5));

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.id IN (?,
?,
?,
?)

You can also optionally use the following DQL syntax when working with IN conditions.

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->andWhere('u.id IN ?', array(array(1, 2, 3, 4, 5)));

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
u.password AS u__password
FROM user u
WHERE u.id IN (?,
?,
?,
?,
?)j

Notice how the placeholders were added automatically for you. You will see that their is one ? for every parameter you passed.

Like Expressions

Syntax:

string_expression [NOT] LIKE pattern_value [ESCAPE escape_character]

The string_expression must have a string value. The pattern_value is a string literal or a string-valued input parameter in which an underscore (_) stands for any single character, a percent (%) character stands for any sequence of characters (including the empty sequence), and all other characters stand for themselves. The optional escape_character is a single-character string literal or a character-valued input parameter (i.e., char or Character) and is used to escape the special meaning of the underscore and percent characters in pattern_value.

Examples:

  • address.phone LIKE '12%3' is true for '123' '12993' and false for '1234'
  • asentence.word LIKE 'l_se' is true for 'lose' and false for 'loose'
  • aword.underscored LIKE '\_%' ESCAPE '\' is true for '_foo' and false for 'bar'
  • address.phone NOT LIKE '12%3' is false for '123' and '12993' and true for '1234'

If the value of the string_expression or pattern_value is NULL or unknown, the value of the LIKE expression is unknown. If the escape_characteris specified and is NULL, the value of the LIKE expression is unknown.

Find all users whose email ends with '@gmail.com':

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->leftJoin('u.Email e')
    ->where('e.address LIKE ?', '%@gmail.com');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
LEFT JOIN email e ON u.id = e.user_id
WHERE e.address LIKE ?

Find all users whose name starts with letter 'A':

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->where('u.username LIKE ?', 'A%');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.username LIKE ?

Exists Expressions

Syntax:

<operand> [NOT ]EXISTS (<subquery>)

The EXISTS operator returns TRUE if the subquery returns one or more rows and FALSE otherwise.

The NOT EXISTS operator returns TRUE if the subquery returns 0 rows and FALSE otherwise.

For the next few examples we need to add the ReaderLog model.

// models/ReaderLog.php

class ReaderLog extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('article_id', 'integer', null, array(
                'primary' => true
            )
        );

        $this->hasColumn('user_id', 'integer', null, array(
                'primary' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
ReaderLog:
  columns:
    article_id:
      type: integer
      primary: true
    user_id:
      type: integer
      primary: true

After adding the ReaderLog model don't forget to run the generate.php script!

$ php generate.php

Now we can run some tests! First, finding all articles which have readers:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.id')
    ->from('Article a')
    ->where('EXISTS (SELECT r.id FROM ReaderLog r WHERE r.article_id = a.id)');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
a.id AS a__id
FROM article a
WHERE EXISTS (SELECT
r.id AS r__id
FROM reader_log r
WHERE r.article_id = a.id)

Finding all articles which don't have readers:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('a.id')
    ->from('Article a')
    ->where('NOT EXISTS (SELECT r.id FROM ReaderLog r WHERE r.article_id = a.id));

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
a.id AS a__id
FROM article a
WHERE NOT EXISTS (SELECT
r.id AS r__id
FROM reader_log r
WHERE r.article_id = a.id)

All and Any Expressions

Syntax:

operand comparison_operator ANY (subquery)
operand comparison_operator SOME (subquery)
operand comparison_operator ALL (subquery)

An ALL conditional expression returns true if the comparison operation is true for all values in the result of the subquery or the result of the subquery is empty. An ALL conditional expression is false if the result of the comparison is false for at least one row, and is unknown if neither true nor false.

$q = Doctrine_Query::create()
    ->from('C')
    ->where('C.col1 < ALL (FROM C2(col1))');

An ANY conditional expression returns true if the comparison operation is true for some value in the result of the subquery. An ANY conditional expression is false if the result of the subquery is empty or if the comparison operation is false for every value in the result of the subquery, and is unknown if neither true nor false.

$q = Doctrine_Query::create()
    ->from('C')
    ->where('C.col1 > ANY (FROM C2(col1))');

The keyword SOME is an alias for ANY.

$q = Doctrine_Query::create()
    ->from('C')
    ->where('C.col1 > SOME (FROM C2(col1))');

The comparison operators that can be used with ALL or ANY conditional expressions are =, <, <=, >, >=, <>. The result of the subquery must be same type with the conditional expression.

NOT IN is an alias for <> ALL. Thus, these two statements are equal:


FROM C
WHERE C.col1 <> ALL (
FROM C2(col1));
FROM C
WHERE C.col1 NOT IN (
FROM C2(col1));
$q = Doctrine_Query::create()
    ->from('C')
    ->where('C.col1 <> ALL (FROM C2(col1))');

$q = Doctrine_Query::create()
    ->from('C')
    ->where('C.col1 NOT IN (FROM C2(col1))');

Subqueries

A subquery can contain any of the keywords or clauses that an ordinary SELECT query can contain.

Some advantages of the subqueries:

  • They allow queries that are structured so that it is possible to isolate each part of a statement.
  • They provide alternative ways to perform operations that would otherwise require complex joins and unions.
  • They are, in many people's opinion, readable. Indeed, it was the innovation of subqueries that gave people the original idea of calling the early SQL "Structured Query Language."

Here is an example where we find all users which don't belong to the group id 1:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->where('u.id NOT IN (SELECT u2.id FROM User u2 INNER JOIN u2.Groups g WHERE g.id = ?)', 1);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.id NOT IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id
WHERE g.id = ?)

Here is an example where we find all users which don't belong to any groups

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->where('u.id NOT IN (SELECT u2.id FROM User u2 INNER JOIN u2.Groups g)');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.id NOT IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id)

Functional Expressions

String functions

The CONCAT function returns a string that is a concatenation of its arguments. In the example above we map the concatenation of users first_name and last_name to a value called name

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('CONCAT(u.first_name, u.last_name) AS name')
    ->from('User u');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
CONCAT(u.first_name,
u.last_name) AS u__0
FROM user u

Now we can execute the query and get the mapped function value:

$users = $q->execute();

foreach($users as $user) {
    // here 'name' is not a property of $user,
    // its a mapped function value
    echo $user->name;
}

The second and third arguments of the SUBSTRING function denote the starting position and length of the substring to be returned. These arguments are integers. The first position of a string is denoted by 1. The SUBSTRING function returns a string.

// test.php

// ...
$q = Doctrine_Query::create();
    ->select('u.username')
    ->from('User u')
    ->where("SUBSTRING(u.username, 0, 1) = 'z'");

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE SUBSTRING(u.username
FROM 0 FOR 1) = 'z'

Notice how the SQL is generated with the proper SUBSTRING syntax for the DBMS you are using!

The TRIM function trims the specified character from a string. If the character to be trimmed is not specified, it is assumed to be space (or blank). The optional trim_character is a single-character string literal or a character-valued input parameter (i.e., char or Character)[30]. If a trim specification is not provided, BOTH is assumed. The TRIM function returns the trimmed string.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->from('User u')
    ->where('TRIM(u.username) = ?', 'Someone');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE TRIM(u.username) = ?

The LOWER and UPPER functions convert a string to lower and upper case, respectively. They return a string.

// test.php

// ...
$q = Doctrine_Query::create();
    ->select('u.username')
    ->from('User u')
    ->where("LOWER(u.username) = 'jon wage'");

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE LOWER(u.username) = 'someone'

The LOCATE function returns the position of a given string within a string, starting the search at a specified position. It returns the first position at which the string was found as an integer. The first argument is the string to be located; the second argument is the string to be searched; the optional third argument is an integer that represents the string position at which the search is started (by default, the beginning of the string to be searched). The first position in a string is denoted by 1. If the string is not found, 0 is returned.

The LENGTH function returns the length of the string in characters as an integer.

Arithmetic functions

Availible DQL arithmetic functions:

ABS(simple_arithmetic_expression)
SQRT(simple_arithmetic_expression)
MOD(simple_arithmetic_expression, simple_arithmetic_expression)
  • The ABS function returns the absolute value for given number.
  • The SQRT function returns the square root for given number.
  • The MOD function returns the modulus of first argument using the second argument.

Subqueries

Introduction

Doctrine allows you to use sub-dql queries in the FROM, SELECT and WHERE statements. Below you will find examples for all the different types of subqueries Doctrine supports.

Comparisons using subqueries

Find all the users which are not in a specific group.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->where('u.id NOT IN (SELECT u.id FROM User u INNER JOIN u.Groups g WHERE g.id = ?)', 1);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
WHERE u.id NOT IN (SELECT
u2.id AS u2__id
FROM user u2
INNER JOIN user_group u3 ON u2.id = u3.user_id
INNER JOIN groups g ON g.id = u3.group_id
WHERE g.id = ?)

Retrieve the users phonenumber in a subquery and include it in the resultset of user information.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->addSelect('(SELECT p.phonenumber FROM Phonenumber p WHERE p.user_id = u.id LIMIT 1) as phonenumber')
    ->from('User u');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
(SELECT
p.phonenumber AS p__phonenumber
FROM phonenumber p
WHERE p.user_id = u.id
LIMIT 1) AS u__0
FROM user u

GROUP BY, HAVING clauses

DQL GROUP BY syntax:


GROUP BY groupby_item {,
groupby_item}*

DQL HAVING syntax:


HAVING conditional_expression

GROUP BY and HAVING clauses can be used for dealing with aggregate functions. The Following aggregate functions are available on DQL: COUNT, MAX, MIN, AVG, SUM

Selecting alphabetically first user by name.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('MIN(a.amount)')
    ->from('Account a');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
MIN(a.amount) AS a__0
FROM account a

Selecting the sum of all Account amounts.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('SUM(a.amount)')
    ->from('Account a');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
SUM(a.amount) AS a__0
FROM account a

Using an aggregate function in a statement containing no GROUP BY clause, results in grouping on all rows. In the example below we fetch all users and the number of phonenumbers they have.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->addSelect('COUNT(p.id) as num_phonenumbers')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->groupBy('u.id');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
COUNT(p.id) AS p__0
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
GROUP BY u.id

The HAVING clause can be used for narrowing the results using aggregate values. In the following example we fetch all users which have atleast 2 phonenumbers

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->addSelect('COUNT(p.id) as num_phonenumbers')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->groupBy('u.id')
    ->having('num_phonenumbers >= 2');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
COUNT(p.id) AS p__0
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
GROUP BY u.id
HAVING p__0 >= 2

You can access the number of phonenumbers with the following code:

// test.php

// ...
$users = $q->execute();

foreach($users as $user) {
    echo $user->name . ' has ' . $user->num_phonenumbers . ' phonenumbers';
}

ORDER BY clause

Introduction

Record collections can be sorted efficiently at the database level using the ORDER BY clause.

Syntax:

, ...]

Examples:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->orderBy('u.username, p.phonenumber');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
ORDER BY u.username,
p.phonenumber

In order to sort in reverse order you can add the DESC (descending) keyword to the name of the column in the ORDER BY clause that you are sorting by. The default is ascending order; this can be specified explicitly using the ASC keyword.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username')
    ->from('User u')
    ->leftJoin('u.Email e')
    ->orderBy('e.address DESC, u.id ASC');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
LEFT JOIN email e ON u.id = e.user_id
ORDER BY e.address DESC,
u.id ASC

Sorting by an aggregate value

In the following example we fetch all users and sort those users by the number of phonenumbers they have.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username, COUNT(p.id) count')
    ->from('User u')
    ->innerJoin('u.Phonenumbers p')
    ->orderby('count');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
COUNT(p.id) AS p__0
FROM user u
INNER JOIN phonenumber p ON u.id = p.user_id
ORDER BY p__0

Using random order

In the following example we use random in the ORDER BY clause in order to fetch random post.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('t.id, RANDOM() AS rand')
    ->from('Forum_Thread t')
    ->orderby('rand')
    ->limit(1);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
f.id AS f__id,
RAND() AS f__0
FROM forum__thread f
ORDER BY f__0
LIMIT 1

LIMIT and OFFSET clauses

Propably the most complex feature DQL parser has to offer is its LIMIT clause parser. Not only does the DQL LIMIT clause parser take care of LIMIT database portability it is capable of limiting the number of records instead of rows by using complex query analysis and subqueries.

Retrieve the first 20 users and all their associated phonenumbers:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.username, p.phonenumber')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->limit(20);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.phonenumber AS p__phonenumber
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
LIMIT 20

Driver Portability

DQL LIMIT clause is portable on all supported databases. Special attention have been paid to following facts:

  • Only Mysql, Pgsql and Sqlite implement LIMIT / OFFSET clauses natively
  • In Oracle / Mssql / Firebird LIMIT / OFFSET clauses need to be emulated in driver specific way
  • The limit-subquery-algorithm needs to execute to subquery separately in mysql, since mysql doesn't yet support LIMIT clause in subqueries
  • Pgsql needs the order by fields to be preserved in SELECT clause, hence limit-subquery-algorithm needs to take this into consideration when pgsql driver is used
  • Oracle only allows < 30 object identifiers (= table/column names/aliases), hence the limit subquery must use as short aliases as possible and it must avoid alias collisions with the main query.

The limit-subquery-algorithm

The limit-subquery-algorithm is an algorithm that DQL parser uses internally when one-to-many / many-to-many relational data is being fetched simultaneously. This kind of special algorithm is needed for the LIMIT clause to limit the number of records instead of sql result set rows.

This behavior can be overwritten using the configuration system (at global, connection or table level) using:

$table->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_ROWS);
$table->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_RECORDS); // revert

In the following example we have users and phonenumbers with their relation being one-to-many. Now lets say we want fetch the first 20 users and all their related phonenumbers.

Now one might consider that adding a simple driver specific LIMIT 20 at the end of query would return the correct results. Thats wrong, since we you might get anything between 1-20 users as the first user might have 20 phonenumbers and then record set would consist of 20 rows.

DQL overcomes this problem with subqueries and with complex but efficient subquery analysis. In the next example we are going to fetch first 20 users and all their phonenumbers with single efficient query. Notice how the DQL parser is smart enough to use column aggregation inheritance even in the subquery and how it's smart enough to use different aliases for the tables in the subquery to avoid alias collisions.

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id, u.username, p.*')
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->limit(20);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.phonenumber AS p__phonenumber,
p.user_id AS p__user_id
FROM user u
LEFT JOIN phonenumber p ON u.id = p.user_id
WHERE u.id IN (SELECT
DISTINCT u2.id
FROM user u2
LIMIT 20)

In the next example we are going to fetch first 20 users and all their phonenumbers and only those users that actually have phonenumbers with single efficient query, hence we use an INNER JOIN. Notice how the DQL parser is smart enough to use the INNER JOIN in the subquery:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id, u.username, p.*')
    ->from('User u')
    ->innerJoin('u.Phonenumbers p')
    ->limit(20);

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username,
p.id AS p__id,
p.phonenumber AS p__phonenumber,
p.user_id AS p__user_id
FROM user u
INNER JOIN phonenumber p ON u.id = p.user_id
WHERE u.id IN (SELECT
DISTINCT u2.id
FROM user u2
INNER JOIN phonenumber p2 ON u2.id = p2.user_id
LIMIT 20)

Named Queries

When you are dealing with a model that may change, but you need to keep your queries easily updated, you need to find an easy way to define queries. Imagine for example that you change one field and you need to follow all queries in your application to make sure it'll not break anything.

Named Queries is a nice and effective way to solve this situation, allowing you to create Doctrine_Queries and reuse them without the need to keep rewritting them.

The Named Query support is built at the top of Doctrine_Query_Registry support. Doctrine_Query_Registry is a class for registering and naming queries. It helps with the organization of your applications queries and along with that it offers some very nice convenience stuff.

The queries are added using the add() method of the registry object. It takes two parameters, the query name and the actual DQL query.

// test.php

// ...
$r = Doctrine_Manager::getInstance()->getQueryRegistry();

$r->add('User/all', 'FROM User u');

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

// find all users
$users = $userTable->find('all');

To simplify this support, Doctrine_Table support some accessors to Doctrine_Query_Registry.

Creating a Named Query

When you build your models with option generateTableClasses defined as true, each record class will also generate a *Table class, extending from Doctrine_Table.

Then, you can implement the method construct() to include your Named Queries:

class UserTable extends Doctrine_Table
{
    public function construct()
    {
        // Named Query defined using DQL string
        $this->addNamedQuery('get.by.id', 'SELECT u.username FROM User u WHERE u.id = ?');

        // Named Query defined using Doctrine_Query object
        $this->addNamedQuery(
            'get.by.similar.usernames', Doctrine_Query::create()
                ->select('u.id, u.username')
                ->from('User u')
                ->where('LOWER(u.username) LIKE LOWER(?)')
        );
    }
}

Accessing Named Query

To reach the MyFooTable class, which is a subclass of Doctrine_Table, you can do the following:

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

To access the Named Query (will return you a Doctrine_Query instance, always):

$q = $userTable->createNamedQuery('get.by.id');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id,
u.username AS u__username
FROM user u
WHERE u.id = ?

Executing a Named Query

There are two ways to execute a Named Query. The first one is by retrieving the Doctrine_Query and then executing it normally, as a normal instance:

// test.php

// ...
$users = Doctrine::getTable('User')
    ->createNamedQuery('get.by.similar.usernames')
    ->execute(array('%jon%wage%'));

You can also simplify the execution, by doing:

// test.php

// ...
$users = Doctrine::getTable('User')
    ->find('get.by.similar.usernames', array('%jon%wage%'));

The method find() also accepts a third parameter, which is the hydration mode.

Cross-Accessing Named Query

If that's not enough, Doctrine take advantage the Doctrine_Query_Registry and uses namespace queries to enable cross-access of Named Queries between objects. Suppose you have the *Table class instance of record Article. You want to call the "get.by.id" Named Query of record User. To access the Named Query, you have to do:

// test.php

// ...
$articleTable = Doctrine::getTable('Article');

$users = $articleTable->find('User/get.by.id', array(1, 2, 3));

BNF

QL_statement ::= select_statement | update_statement | delete_statement
select_statement ::= select_clause from_clause [where_clause] [groupby_clause]
[having_clause] [orderby_clause]
update_statement ::= update_clause [where_clause]
delete_statement ::= delete_clause [where_clause]
from_clause ::=
FROM identification_variable_declaration
{, {identification_variable_declaration | collection_member_declaration}}*
identification_variable_declaration ::= range_variable_declaration { join | fetch_join }*
range_variable_declaration ::= abstract_schema_name [AS ] identification_variable
join ::= join_spec join_association_path_expression [AS ] identification_variable
fetch_join ::= join_specFETCH join_association_path_expression
association_path_expression ::=
collection_valued_path_expression | single_valued_association_path_expression
join_spec::= [LEFT [OUTER ] |INNER ]JOIN
join_association_path_expression ::= join_collection_valued_path_expression |
join_single_valued_association_path_expression
join_collection_valued_path_expression::=
identification_variable.collection_valued_association_field
join_single_valued_association_path_expression::=
identification_variable.single_valued_association_field
collection_member_declaration ::=
IN ( collection_valued_path_expression) [AS ] identification_variable
single_valued_path_expression ::=
state_field_path_expression | single_valued_association_path_expression
state_field_path_expression ::=
{identification_variable | single_valued_association_path_expression}.state_field
single_valued_association_path_expression ::=
identification_variable.{single_valued_association_field.}* single_valued_association_field
collection_valued_path_expression ::=
identification_variable.{single_valued_association_field.}*collection_valued_association_field
state_field ::= {embedded_class_state_field.}*simple_state_field
update_clause ::=UPDATE abstract_schema_name [[AS ] identification_variable]
SET update_item {, update_item}*
update_item ::= [identification_variable.]{state_field | single_valued_association_field} =
new_value
new_value ::=
simple_arithmetic_expression |
string_primary |
datetime_primary |

boolean_primary |
enum_primary
simple_entity_expression |
NULL
delete_clause ::=DELETE FROM abstract_schema_name [[AS ] identification_variable]
select_clause ::=SELECT [DISTINCT ] select_expression {, select_expression}*
select_expression ::=
single_valued_path_expression |
aggregate_expression |
identification_variable |
OBJECT( identification_variable) |
constructor_expression
constructor_expression ::=
NEW constructor_name( constructor_item {, constructor_item}*)
constructor_item ::= single_valued_path_expression | aggregate_expression
aggregate_expression ::=
{AVG |MAX |MIN |SUM }( [DISTINCT ] state_field_path_expression) |
COUNT ( [DISTINCT ] identification_variable | state_field_path_expression |
single_valued_association_path_expression)
where_clause ::=WHERE conditional_expression
groupby_clause ::=GROUP BY groupby_item {, groupby_item}*
groupby_item ::= single_valued_path_expression | identification_variable
having_clause ::=HAVING conditional_expression
orderby_clause ::=ORDER BY orderby_item {, orderby_item}*
orderby_item ::= state_field_path_expression [ASC |DESC ]
subquery ::= simple_select_clause subquery_from_clause [where_clause]
[groupby_clause] [having_clause]
subquery_from_clause ::=
FROM subselect_identification_variable_declaration
{, subselect_identification_variable_declaration}*
subselect_identification_variable_declaration ::=
identification_variable_declaration |
association_path_expression [AS ] identification_variable |
collection_member_declaration
simple_select_clause ::=SELECT [DISTINCT ] simple_select_expression
simple_select_expression::=
single_valued_path_expression |
aggregate_expression |
identification_variable
conditional_expression ::= conditional_term | conditional_expressionOR conditional_term
conditional_term ::= conditional_factor | conditional_termAND conditional_factor
conditional_factor ::= [NOT ] conditional_primary
conditional_primary ::= simple_cond_expression |( conditional_expression)
simple_cond_expression ::=
comparison_expression |
between_expression |
like_expression |
in_expression |
null_comparison_expression |
empty_collection_comparison_expression |

collection_member_expression |
exists_expression
between_expression ::=
arithmetic_expression [NOT ]BETWEEN
arithmetic_expressionAND arithmetic_expression |
string_expression [NOT ]BETWEEN string_expressionAND string_expression |
datetime_expression [NOT ]BETWEEN
datetime_expressionAND datetime_expression
in_expression ::=
state_field_path_expression [NOT ]IN ( in_item {, in_item}* | subquery)
in_item ::= literal | input_parameter
like_expression ::=
string_expression [NOT ]LIKE pattern_value [ESCAPE escape_character]
null_comparison_expression ::=
{single_valued_path_expression | input_parameter}IS [NOT ] NULL
empty_collection_comparison_expression ::=
collection_valued_path_expressionIS [NOT] EMPTY
collection_member_expression ::= entity_expression
[NOT ]MEMBER [OF ] collection_valued_path_expression
exists_expression::= [NOT ]EXISTS (subquery)
all_or_any_expression ::= {ALL |ANY |SOME } (subquery)
comparison_expression ::=
string_expression comparison_operator {string_expression | all_or_any_expression} |
boolean_expression {= |<> } {boolean_expression | all_or_any_expression} |
enum_expression {= |<> } {enum_expression | all_or_any_expression} |
datetime_expression comparison_operator
{datetime_expression | all_or_any_expression} |
entity_expression {= |<> } {entity_expression | all_or_any_expression} |
arithmetic_expression comparison_operator
{arithmetic_expression | all_or_any_expression}
comparison_operator ::== |> |>= |< |<= |<>
arithmetic_expression ::= simple_arithmetic_expression | (subquery)
simple_arithmetic_expression ::=
arithmetic_term | simple_arithmetic_expression {+ |- } arithmetic_term
arithmetic_term ::= arithmetic_factor | arithmetic_term {* |/ } arithmetic_factor
arithmetic_factor ::= [{+ |- }] arithmetic_primary
arithmetic_primary ::=
state_field_path_expression |
numeric_literal |
(simple_arithmetic_expression) |
input_parameter |
functions_returning_numerics |
aggregate_expression
string_expression ::= string_primary | (subquery)
string_primary ::=
state_field_path_expression |
string_literal |
input_parameter |
functions_returning_strings |
aggregate_expression

datetime_expression ::= datetime_primary | (subquery)
datetime_primary ::=
state_field_path_expression |
input_parameter |
functions_returning_datetime |
aggregate_expression
boolean_expression ::= boolean_primary | (subquery)
boolean_primary ::=
state_field_path_expression |
boolean_literal |
input_parameter |
enum_expression ::= enum_primary | (subquery)
enum_primary ::=
state_field_path_expression |
enum_literal |
input_parameter |
entity_expression ::=
single_valued_association_path_expression | simple_entity_expression
simple_entity_expression ::=
identification_variable |
input_parameter
functions_returning_numerics::=
LENGTH( string_primary) |
LOCATE( string_primary, string_primary[, simple_arithmetic_expression]) |
ABS( simple_arithmetic_expression) |
SQRT( simple_arithmetic_expression) |
MOD( simple_arithmetic_expression, simple_arithmetic_expression) |
SIZE( collection_valued_path_expression)
functions_returning_datetime ::=
  CURRENT_DATE |
  CURRENT_TIME |
  CURRENT_TIMESTAMP
functions_returning_strings ::=
CONCAT( string_primary, string_primary) |
SUBSTRING( string_primary,
simple_arithmetic_expression, simple_arithmetic_expression)|
TRIM( [[trim_specification] [trim_character]FROM ] string_primary) |
LOWER( string_primary) |
UPPER( string_primary)
trim_specification ::=LEADING | TRAILING | BOTH

Magic Finders

Doctrine offers some magic finders for your Doctrine models that allow you to find a record by any column that is present in the model. This is helpful for simply finding a user by their username, or finding a group by the name of it. Normally this would require writing a Doctrine_Query instance and storing this somewhere so it can be reused. That is no longer needed for simple situations like that.

The basic pattern for the finder methods are as follows: findBy%s($value) or findOneBy%s($value). The %s can be a column name or a relation alias. If you give a column name you must give the value you are looking for. If you specify a relationship alias, you can either pass an instance of the relation class to find, or give the actual primary key value.

First lets retrieve the UserTable instance to work with:

// test.php

// ...
$userTable = Doctrine::getTable('User');

Now we can easily find a User record by its primary key by using the find() method:

// test.php

// ...
$user = $userTable->find(1);

Now if you want to find a single user by their username you can use the following magic finder:

// test.php

// ...
$user = $userTable->findOneByUsername('jonwage');

You can also easily find records by using the relationships between records. User has many Phonenumbers we can find those Phonenumbers by passing the findBy**() method a User instance:

// test.php

// ...
$phonenumberTable = Doctrine::getTable('Phonenumber');

$phonenumbers = $phonenumberTable->findByUser($user);

The documented magic finders above are made possibly by using PHP's __call() overloading functionality. The undefined functions are forwarded to Doctrine_Table::__call() where the Doctrine_Query objects are built, executed and returned to the user.

Debugging Queries

The Doctrine_Query object has a few functions that can be used to help debug problems with the query:

Sometimes you may want to see the complete DQL string of your Doctrine_Query object:

// test.php

// ...
$q = Doctrine_Query::create()
    ->select('u.id')
    ->from('User u')
    ->orderBy('u.username');

echo $q->getSql();

The above call to getSql() would output the following SQL query:

SELECT 
u.id AS u__id
FROM user u
ORDER BY u.username

The SQL returned above by the Doctrine_Query::getSql() function does not replace the tokens with the parameters. This is the job of PDO and when we execute the query we pass the parameters to PDO where the replacement is executed. You can retrieve the array of parameters with the Doctrine_Query::getParams() method.

Get the array of parameters for the Doctrine_Query instance:

// test.php

// ...
print_r($q->getParams());

The above call to getParams() returns the unmodified array of parameters that are used to build the final params to be passed to the query. If you wish to get the flattened array of params passed to PDO then use the getFlattenedParams() method.

// test.php

// ...
print_r($q->getFlattenedParams());

Conclusion

The Doctrine Query Language is by far one of the most advanced and helpful feature of Doctrine. It allows you to easily select very complex data from RDBMS relationships efficiently!

Now that we have gone over most of the major components of Doctrine and how to use them we are going to take a step back in the next chapter and look at everything from a birds eye view in the Component Overview chapter.

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.

// test.php

// ...
$manager = Doctrine_Manager::getInstance();

Retrieving Connections

// test.php

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

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

// test.php

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

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:

// test.php

// ...
$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.

// test.php

// ...
$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::getTable() method.

Getting a Table Object

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

// test.php

// ...
$accountTable = Doctrine::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();

$columns = $accountTable->getColumns();
foreach ($columns as $column)
{
    print_r($column);
}

The above example would output the following when executed:

$ php test.php
Array
(
    [type] => integer
    [length] => 20
    [autoincrement] => 1
    [primary] => 1
)
Array
(
    [type] => string
    [length] => 255
)
Array
(
    [type] => decimal
    [length] => 18
)

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:

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

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

$ 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

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:

$ php test.php
Name: Phonenumbers
Local - id
Foreign - user_id
Relation Class - Doctrine_Relation_ForeignKey

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 toArray() method and using print_r() to inspect it.

// test.php

// ...
$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:

// test.php

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

The above example would output the following when executed:

$ 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
)

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

// test.php

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

The above example would output the following when executed:

$ php test.php
Jack
jwage

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:

// test.php

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

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

The above example would output the following when executed:

$ php test.php
jwage

Doctrine also offers some additional magic finder methods that can be read about in the Magic Finders 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.

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 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::ATTR_AUTOLOAD_TABLE_CLASSES, true);

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

$userTable = Doctrine::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 Magic Finders 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::getTable('User');

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

Access property through overloading

// test.php

// ...
echo $user->username;

Access property with get()

// test.php

// ...
echo $user->get('username);

Access property with ArrayAccess

// test.php

// ...
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.

// test.php

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

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:

// test.php

// ...
if (isset($user['username'])) {

}

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

// test.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:

$ php test.php
Array
(
    [username] => Jack Daniels
)

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

// test.php

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

If you want to also check if any of the referenced relationships are modified you can pass a $deep argument to isModified() with a value of true. Below you will find an example:

$user = new User();
$mail = new Email();
$mail->address = 'test';
$user->Emails[] = $mail;

Now if we deep check if the user is modified it will return true because the Email address is modified.

$modified = $user->isModified(true); // true

Sometimes when a record has been updated you may want to inspect the old values and not just the new values. This is possible in Doctrine with the getModified() method by passing an argument of true. This will return the old values instead of the new ones.

// test.php

// ...

$user = Doctrine::getTable('User')->findOneByName('zYne-);
$user->name = 'zYne-';

$oldValues = $user->getModified(true);

/*
array(
  'name' => 'zYne',
)
*/

$newValues = $user->getModified(false);

/*
array(
  'name' => 'zYne-',
)

After saving a record in Doctrine, the modified properties are cleared out and the state of the object is returned to un-modified. Sometimes you may want to retrieve the values that were last modified and this is possible by using the getLastModified() method.

// test.php

// ...

$user = new User();
$user->username = 'jwage';
print_r($user->getModified()); // array('username' => 'jwage')
$user->save();

// getModified() returns the current modified properties and in this case
// now no propeties are modified.
print_r($user->getModified()); // array()

// you can retrieve the last modified properties like the following
print_r($user->getLastModified()); // array('username' => 'jwage')

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 count() function. This is possible since Doctrine_Record implements a magic Countable interface. The other way would be calling the count() method.

// test.php

// ...
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.

// test.php

// ...
$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

// test.php

// ...
$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:

// test.php

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

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

echo $user2->username; // jwage

Doctrine_Record also has a 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.

// test.php

// ...
$userTable = Doctrine::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:

// test.php

// ...
$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.

// test.php

// ...
$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

INSERT INTO user (username, password) VALUES (?,?) ('jwage', 'changeme')

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

// test.php

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

Issues the following query

REPLACE INTO user (id,username,password) VALUES (?,?,?)    (null, 'jwage', 'newpassword')

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().

// test.php

// ...
$user = Doctrine::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.

// test.php

// ...
$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:

// test.php

// ...
$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:

// test.php

// ...
$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:

// test.php

// ...
$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:

// test.php

// ...
$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:

// test.php

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

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

// test.php

// ...
$user = Doctrine::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:

// test.php

// ...
$user->refreshRelated('Phonenumber');

Deleting Records

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

// test.php

// ...
$userTable = Doctrine::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.

// test.php

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

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

// test.php

// ...
$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:

INSERT 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.

// test.php

// ...
$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 in proxy state meaning its persistent but not all of its properties are loaded from the database.
Doctrine_Record::STATE_TCLEAN Record is transient clean, meaning its transient and none of its properties are changed.
Doctrine_Record::STATE_TDIRTY Record is transient dirty, meaning its transient and some of its properties are changed.
Doctrine_Record::STATE_DIRTY Record is dirty, meaning its persistent and some of its properties are changed.
Doctrine_Record::STATE_CLEAN Record is clean, meaning its persistent and none of its properties are changed.
Doctrine_Record::STATE_LOCKED Record is locked.

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

// test.php

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

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

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

// test.php

// ...
$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().

// test.php

// ...
$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

// test.php

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

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

// test.php

// ...
$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.

// test.php

// ...
$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:

// test.php

// ...
class User extends Doctrine_Record
{
    public function construct()
    {
        $this->mapValue('name');
    }

    // ...
}

$user = new User();

$user->name = 'jwage';
echo $user->name; // jwage

Serializing

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

// test.php

// ...
$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:

// test.php

// ...
$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:

// test.php

// ...
$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:

// test.php

// ...
$users = new Doctrine_Collection('User');

Now add some new data to the collection:

// test.php

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

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

$users->save();

Splitting a collection in to a simple key => value pair array is simple. It can be accomplished with the toKeyValueArray() method.

// test.php

// ...

$q = Doctrine_Query::create()
    ->from('User u');

$users = $q->execute();

$array = $users->toKeyValueArray('id', 'name');
print_r($array);

The above example would output the following array structure:

array(
  4 => 'zYne',
  5 => 'Arnold Schwarzenegger',
  6 => 'Michael Caine',
  7 => 'Takeshi Kitano',
  8 => 'Sylvester Stallone',
  9 => 'Kurt Russell',
  10 => 'Jean Reno',
  11 => 'Edward Furlong',
)

Accessing Elements

You can access the elements of Doctrine_Collection with set() and get() methods or with ArrayAccess interface.

// test.php

// ...
$userTable = Doctrine::getTable('User');
$users = $userTable->findAll();

Accessing elements with ArrayAccess interface

// test.php

// ...
$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:

// test.php

// ...
$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.

// test.php

// ...
$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.

// test.php

// ...
echo count($users);

Saving the Collection

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

// test.php

// ...
$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:

DELETE 
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::getTable('User');

$userTable->setAttribute(Doctrine::ATTR_COLL_KEY, 'id');

Now user collections will use the values of id column as element indexes:

// test.php

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

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

You may want to map the name column:

// test.php

// ...
$userTable = Doctrine::getTable('User');

$userTable->setAttribute(Doctrine::ATTR_COLL_KEY, 'username');

Now user collections will use the values of name column as element indexes:

// test.php

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

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

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.

Loading Related Records

Doctrine provides means for efficiently retrieving all related records for all record elements. That means when you have for example a collection of users you can load all phonenumbers for all users by simple calling the loadRelated() method.

However, in most cases you don't need to load related elements explicitly, rather what you should do is try to load everything at once by using the DQL API and JOINS.

The following example uses three queries for retrieving users, their phonenumbers and the groups they belong to.

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u');

$users = $q->execute();

Now lets load phonenumbers for all users:

// test.php

// ...
$users->loadRelated('Phonenumbers');

foreach($users as $user) {
    echo $user->Phonenumbers[0]->phonenumber;
    // no additional db queries needed here
}

The loadRelated() works an any relation, even associations:

// test.php

// ...
$users->loadRelated('Groups');

foreach($users as $user) {
    echo $user->Groups[0]->name;
}

The example below shows how to do this more efficiently by using the DQL API.

Write a Doctrine_Query that loads everything in one query:

// test.php

// ...
$q = Doctrine_Query::create()
    ->from('User u')
    ->leftJoin('u.Phonenumbers p')
    ->leftJoin('u.Groups g');

$users = $q->execute();

Now when we use the Phonenumbers and Groups no additional database queries are needed:

// test.php

// ...
foreach($users as $user) {
    echo $user->Phonenumbers[0]->phonenumber;
    echo $user->Groups[0]->name;
}

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::ATTR_VALIDATE, Doctrine::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::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 Data Validation 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(<fieldName>, <error code/identifier>)
            $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:

---
# schema.yml

# ...
Email:
  columns:
    address:
      type: string(150)
      email: true
      unique: true

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:

Validation failed in class User

  1 field had validation error:

    * 1 validator failed on username (length)

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.

// test.php

// ...
$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();
    echo $event->getName() . " " . sprintf("%f", $event->getElapsedSecs()) . "\n";
    echo $event->getQuery() . "\n";
    $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::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::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:

// test.php

// ...
$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:

// test.php

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

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

// test.php

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

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

// test.php

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

// test.php

// ...
$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 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.

Native SQL

Introduction

Doctrine_RawSql provides a convenient interface for building raw sql queries. Similar to Doctrine_Query, Doctrine_RawSql provides means for fetching arrays and objects. Whichever way you prefer.

Using raw sql for fetching might be useful when you want to utilize database specific features such as query hints or the CONNECT keyword in Oracle.

Creating a Doctrine_RawSql object is easy:

// test.php

// ...
$q = new Doctrine_RawSql();

Optionally a connection parameter can be given and it accepts an instance of Doctrine_Connection. You learned how to create connections in the Connections chapter.

// test.php

// ...
$conn = Doctrine_Manager::connection();
$q = new Doctrine_RawSql($conn);

Component Queries

The first thing to notice when using Doctrine_RawSql is that you always have to place the fields you are selecting in curly brackets {}. Also for every selected component you have to call addComponent().

The following example should clarify the usage of these:

// test.php

// ...
$q->select('{u.*}')
  ->from('user u')
  ->addComponent('u', 'User');

$users = $q->execute();
print_r($users->toArray());

Note above that we tell that user table is bound to class called User by using the addComponent() method.

Pay attention to following things:

  • Fields must be in curly brackets.
  • For every selected table there must be one addComponent() call.

Fetching from Multiple Components

When fetching from multiple components the addComponent() calls become a bit more complicated as not only do we have to tell which tables are bound to which components, we also have to tell the parser which components belongs to which.

In the following example we fetch all users and their phonenumbers. First create a new Doctrine_RawSql object and add the select parts:

// test.php

// ...
$q = new Doctrine_RawSql();
$q->select('{u.*}, {p.*}');

Now we need to add the FROM part to the query with the join to the phonenumber table from the user table and map everything together:

// test.php

// ...
$q->from('user u LEFT JOIN phonenumber p ON u.id = p.user_id')

Now we tell that user table is bound to class called User we also add an alias for User class called u. This alias will be used when referencing the User class.

// test.php

// ...
$q->addComponent('u', 'User u');

Now we add another component that is bound to table phonenumber:

// test.php

// ...
$q->addComponent('p', 'u.Phonenumbers p');

Notice how we reference that the Phonenumber class is the User's phonenumber.

Now we can execute the Doctrine_RawSql query just like if you were executing a Doctrine_Query object:

// test.php

// ...
$users = $q->execute();
echo get_class($users) . "\n";
echo get_class($users[0]) . "\n";
echo get_class($users[0]['Phonenumbers'][0]) . "\n";

The above example would output the following when executed:

$ php test.php
Doctrine_Collection
User
Phonenumber

Conclusion

This chapter may or may not be useful for you right now. In most cases the Doctrine Query Language is plenty sufficient for retrieving the complex data sets you require. But if you require something outside the scope of what Doctrine_Query is capable of then Doctrine_RawSql can help you.

In the previous chapters you've seen a lot of mention about YAML schema files and have been given examples of schema files but haven't really been trained on how to write your own. The next chapter explains in great detail how to maintain your models as YAML Schema Files.

YAML Schema Files

Introduction

The purpose of schema files is to allow you to manage your model definitions directly from a YAML file rather then editing php code. The YAML schema file is parsed and used to generate all your model definitions/classes. This makes Doctrine model definitions much more portable.

Schema files support all the normal things you would write with manual php code. Component to connection binding, relationships, attributes, templates/behaviors, indexes, etc.

Abbreviated Syntax

Doctrine offers the ability to specify schema in an abbreviated syntax. A lot of the schema parameters have values they default to, this allows us to abbreviate the syntax and let Doctrine just use its defaults. Below is an example of schema taking advantage of all the abbreviations.

The detect_relations option will attempt to guess relationships based on column names. In the example below Doctrine knows that User has one Contact and will automatically define the relationship between the models.

---
detect_relations: true

User:
  columns:
    username: string
    password: string
    contact_id: integer

Contact:
  columns:
    first_name: string
    last_name: string
    phone: string
    email: string
    address: string

Verbose Syntax

Here is the 100% verbose form of the above schema:

---
User:
  columns:
    username:
      type: string(255)
    password:
      type: string(255)
    contact_id:
      type: integer
  relations:
    Contact:
      class: Contact
      local: contact_id
      foreign: id
      foreignAlias: User
      foreignType: one
      type: one

Contact:
  columns:
    first_name:
      type: string(255)
    last_name:
      type: string(255)
    phone:
      type: string(255)
    email:
      type: string(255)
    address:
      type: string(255)
  relations:
    User:
      class: User
      local: id
      foreign: contact_id
      foreignAlias: Contact
      foreignType: one
      type: one

In the above example we do not define the detect_relations option, instead we manually define the relationships so we have complete control over the configuration of the local/foreign key, type and alias of the relationship on each side.

Relationships

When specifying relationships it is only necessary to specify the relationship on the end where the foreign key exists. When the schema file is parsed, it reflects the relationship and builds the opposite end automatically. If you specify the other end of the relationship manually, the auto generation will have no effect.

Detect Relations

Doctrine offers the ability to specify a detect_relations option as you saw earlier. This feature provides automatic relationship building based on column names. If you have a User model with a contact_id and a class with the name Contact exists, it will automatically create the relationships between the two.

Customizing Relationships

Doctrine only requires that you specify the relationship on the end where the foreign key exists. The opposite end of the relationship will be reflected and built on the opposite end. The schema syntax offers the ability to customize the relationship alias and type of the opposite end. This is good news because it means you can maintain all the relevant relationship information in one place. Below is an example of how to customize the alias and type of the opposite end of the relationship. It demonstrates the relationships User has one Contact and Contact has one User as UserModel. Normally it would have automatically generated User has one Contact and Contact has many User. The foreignType and foreignAlias options allow you to customize the opposite end of the relationship.

---
User:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)
  relations:
    Contact:
      foreignType: one
      foreignAlias: UserModel

Contact:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name:
      type: string(255)

You can quickly detect and create the relationships between two models with the detect_relations option like below.

---
detect_relations: true

User:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    avatar_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)

Avatar:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name:
      type: string(255)
    image_file:
      type: string(255)

The resulting relationships would be User has one Avatar and Avatar has many User.

One to One

---
User:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)
  relations:
    Contact:
      foreignType: one

Contact:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name:
      type: string(255)

One to Many

---
User:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)

Phonenumber:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name:
      type: string(255)
    user_id:
      type: integer(4)
  relations:
    User:
      foreignAlias: Phonenumbers

Many to Many

---
User:
  columns:
    id:
      type: integer(4)
      autoincrement: true
      primary: true
    username:
      type: string(255)
    password:
      type: string(255)
  attributes:
    export: all
    validate: true

Group:
  tableName: group_table
  columns:
    id:
      type: integer(4)
      autoincrement: true
      primary: true
    name:
      type: string(255)
  relations:
    Users:
      foreignAlias: Groups
      class: User
      refClass: GroupUser

GroupUser:
  columns:
    group_id:
      type: integer(4)
      primary: true
    user_id:
      type: integer(4)
      primary: true
  relations:
    Group:
      foreignAlias: GroupUsers
    User:
      foreignAlias: GroupUsers

This creates a set of models where User has many Groups, Group has many Users, GroupUser has one User and GroupUser has one Group.

Features & Examples

Connection Binding

If you're not using schema files to manage your models, you will normally use this code to bind a component to a connection name with the following code:

Create a connection with code like below:

Doctrine_Manager::connection('mysql://jwage:pass@localhost/connection1', 'connection1');

Now somewhere in your Doctrine bootstrapping of Doctrine you would bind the model to that connection:

Doctrine_Manager::connection()->bindComponent('User', 'conn1');

Schema files offer the ability to bind it to a specific connection by specifying the connection parameter. If you do not specify the connection the model will just use the current connection set on the Doctrine_Manager instance.

---
User:
  connection: connection1
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)

Attributes

Doctrine offers the ability to set attributes for your generated models directly in your schema files similar to how you would if you were manually writing your Doctrine_Record child classes.

---
User:
  connection: connection1
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)
  attributes:
    export: none
    validate: false

Enums

To use enum columns in your schema file you must specify the type as enum and specify an array of values for the possible enum values.

---
TvListing:
 tableName: tv_listing
 actAs: [Timestampable]
 columns:
   notes:
     type: string
   taping:
     type: enum
     length: 4
     values: ['live', 'tape']
   region:
     type: enum
     length: 4
     values: ['US', 'CA']

ActAs Behaviors

You can attach behaviors to your models with the actAs option. You can specify something like the following:

---
User:
  connection: connection1
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)
  actAs:
    Timestampable:
    Sluggable:
      fields: [username]
      name: slug    # defaults to 'slug'
      type: string  # defaults to 'clob'
      length: 255   # defaults to null. clob doesn't require a length

The options specified on the Sluggable behavior above are optional as they will use defaults values if you do not specify anything. Since they are defaults it is not necessary to type it out all the time.

---
User:
  connection: connection1
  columns:
# ...
  actAs: [Timestampable, Sluggable]

Listeners

If you have a listener you'd like attached to a model, you can specify them directly in the yml as well.

---
User:
  listeners: [ MyCustomListener ]
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)

The above syntax will generated a base class that looks something like the following:

class BaseUser extends Doctrine_Record
{
   // ...

   public setUp()
   {
       // ...
       $this->addListener(new MyCustomListener());
   }
}

Options

Specify options for your tables and when Doctrine creates your tables from your models the options will be set on the create table statement.

---
User:
  connection: connection1
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)
  options:
    type: INNODB
    collate: utf8_unicode_ci
    charset: utf8

Indexes

Please see the Indexes section of the chapter for more information about indexes and their options.

---
UserProfile:
  columns:
    user_id:
      type: integer
      length: 4
      primary: true
      autoincrement: true
    first_name:
      type: string
      length: 20
    last_name:
      type: string
      length: 20
  indexes:
    name_index:
      fields:
        first_name:
          sorting: ASC
          length: 10
          primary: true
        last_name: []
      type: unique

This is the PHP line of code that is auto-generated inside setTableDefinition() inside your base model class for the index definition used above:

$this->index('name_index', array(
        'fields' => array(
            'first_name' => array(
                'sorting'  => 'ASC',
                'length'   => '10',
                'primary'  => true
            ),
            'last_name' => array()),
        'type' => 'unique'
    )
);

Inheritance

Below we will demonstrate how you can setup the different types of inheritance using YAML schema files.

Simple Inheritance

---
Entity:
  columns:
    name: string(255)
    username: string(255)
    password: string(255)

User:
  inheritance:
    extends: Entity
    type: simple

Group:
  inheritance:
    extends: Entity
    type: simple

Any columns or relationships defined in models that extend another in simple inheritance will be moved to the parent when the PHP classes are built.

You can read more about this topic in the Simple chapter.

Concrete Inheritance

---
TextItem:
  columns:
    topic: string(255)

Comment:
  inheritance:
    extends: TextItem
    type: concrete
  columns:
    content: string(300)

You can read more about this topic in the Concrete chapter.

Column Aggregation Inheritance

Like simple inheritance, any columns or relationships added to the children will be automatically removed and moved to the parent when the PHP classes are built.

First lets defined a model named Entity that our other models will extend from:

---
Entity:
  columns:
    name: string(255)
    type: string(255)

The type column above is optional. It will be automatically added when it is specified in the child class.

Now lets create a User model that extends the Entity model:

---
User:
  inheritance:
    extends: Entity
    type: column_aggregation
    keyField: type
    keyValue: User
  columns:
    username: string(255)
    password: string(255)

The type option under the inheritance definition is optional as it is implied if you specify a keyField or keyValue. If the keyField is not specified it will default to add a column named type. The keyValue will default to the name of the model if you do not specify anything.

Again lets create another model that extends Entity named Group:

---
Group:
  inheritance:
    extends: Entity
    type: column_aggregation
    keyField: type
    keyValue: Group
  columns:
    description: string(255)

The User username and password and the Group description columns will be automatically moved to the parent Entity.

You can read more about this topic in the Column Aggregation.

Column Aliases

If you want the ability alias a column name as something other than the column name in the database this is easy to accomplish with Doctrine. We simple use the syntax "column_name as field_name" in the name of our column:

---
User:
  columns:
    login:
      name: login as username
      type: string(255)
    password:
      type: string(255)

The above example would allow you to access the column named login from the alias username.

Packages

Doctrine offers the "package" parameter which will generate the models in to sub folders. With large schema files this will allow you to better organize your schemas in to folders.

---
User:
  package: User
  columns:
    username: string(255)

The model files from this schema file would be put in a folder named User. You can specify more sub folders by doing "package: User.Models" and the models would be in User/Models

Package Custom Path

You can also completely by pass the automatic generation of packages to the appropriate path by specifying a completely custom path to generate the package files:

---
User:
  package: User
  package_custom_path: /path/to/generate/package
  columns:
    username: string(255)

Global Schema Information

Doctrine schemas allow you to specify certain parameters that will apply to all of the models defined in the schema file. Below you can find an example on what global parameters you can set for schema files.

List of global parameters:

Name Description
connection Name of connection to bind the models to.
attributes Array of attributes for models.
actAs Array of behaviors for the models to act as.
options Array of tables options for the models.
package Package to put the models in.
inheritance Array of inheritance information for models
detect_relations Whether or not to try and detect foreign key relations

Now here is an example schema where we use some of the above global parameters:

---
connection: conn_name1
actAs: [Timestampable]
options:
  type: INNODB
package: User
detect_relations: true

User:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    contact_id:
      type: integer(4)
    username:
      type: string(255)
    password:
      type: string(255)

Contact:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name:
      type: string(255)

All of the settings at the top will be applied to every model which is defined in that YAML file.

Custom Column Attribute

If you wish to store additional attributes on your model columns that aren't used by Doctrine internally you can use the extra attribute like the following.

---
User:
  columns:
    username:
      type: string(255)
      extra:
        test: 123
    password:
      type: string(255)

Now after you build your models from yaml you can now access the data with the following php code.

$username = Doctrine::getTable('User')->getDefinitionOf('username');
echo $username['extra']['test']; // 123

Using Schema Files

Once you have defined your schema files you need some code to build the models from the YAML definition.

$options = array(
  'packagesPrefix'  =>  'Plugin',
  'baseClassName'   =>  'MyDoctrineRecord',
  'suffix'          =>  '.php'
);

Doctrine::generateModelsFromYaml('/path/to/yaml', '/path/to/model', $options);

The above code will generate the models for schema.yml at /path/to/generate/models.

Below is a table containing the different options you can use to customize the building of models. Notice we use the packagesPrefix, baseClassName and suffix options above.

Name Default Description
packagesPrefix Package What to prefix the middle package models with.
packagesPath #models_path#/packages Path to write package files.
generateBaseClasses true Whether or not to generate abstract base models containing the definition and a top level class which is empty extends the base.
generateTableClasses true Whether or not to generate a table class for each model.
baseClassesDirectory generated Name of the folder to generate the base class definitions in.
baseClassName Doctrine_Record Name of the base Doctrine_Record class.
suffix .php Extension for your generated models.

Conclusion

Now that we have learned all about YAML Schema files we are ready to move on to a great topic regarding Data Validation. This is an important topic because if you are not validating user inputted data yourself then we want Doctrine to validate data before being persisted to the database.

Data Validation

Introduction

Doctrine allows you to define *portable* constraints on columns and tables. Constraints give you as much control over the data in your tables as you wish. If a user attempts to store data in a column that would violate a constraint, an error is raised. This applies even if the value came from the default value definition.

Doctrine constraints act as database level constraints as well as application level validators. This means double security: the database doesn't allow wrong kind of values and neither does the application.

Here is a full list of available validators within Doctrine:

validator(arguments) constraints description
notnull NOT NULL Ensures the 'not null' constraint in both application and database level
email Checks if value is valid email.
notblank NOT NULL Checks if value is not blank.
nospace Checks if value has no space chars.
past CHECK constraint Checks if value is a date in the past.
future Checks if value is a date in the future.
minlength(length) Checks if value satisfies the minimum length.
country Checks if value is a valid country code.
ip Checks if value is valid IP (internet protocol) address.
htmlcolor Checks if value is valid html color.
range(min, max) CHECK constraint Checks if value is in range specified by arguments.
unique UNIQUE constraint Checks if value is unique in its database table.
regexp(expression) Checks if value matches a given regexp.
creditcard Checks whether the string is a well formated credit card number
digits(int, frac) Precision and scale Checks if given value has int number of integer digits and frac number of fractional digits
date Checks if given value is a valid date.
readonly Checks if a field is modified and if it is returns false to force a field as readonly
unsigned Checks if given integer value is unsigned.
usstate Checks if given value is a valid US state code.

Below is an example of how you use the validator and how to specify the arguments for the validators on a column.

In our example we will use the minlength validator.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('username', 'string', 255, array(
                'minlength' => 12
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      minlength: 12
# ...

Examples

Not Null

A not-null constraint simply specifies that a column must not assume the null value. A not-null constraint is always written as a column constraint.

The following definition uses a notnull constraint for column name. This means that the specified column doesn't accept null values.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('username', 'string', 255, array(
                'notnull' => true,
                'primary' => true,
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      notnull: true
      primary: true
# ...

When this class gets exported to database the following SQL statement would get executed (in MySQL):

CREATE TABLE user (username VARCHAR(255) NOT NULL, 
PRIMARY KEY(username))

The notnull constraint also acts as an application level validator. This means that if Doctrine validators are turned on, Doctrine will automatically check that specified columns do not contain null values when saved.

If those columns happen to contain null values Doctrine_Validator_Exception is raised.

Email

The e-mail validator simply validates that the inputted value is indeed a valid e-mail address and that the MX records for the address domain resolve as a valid e-mail address.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('email', 'string', 255, array(
                'email'   => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    email:
      type: string(255)
      email: true
# ...

Now when we try and create a user with an invalid email address it will not validate:

// test.php

// ...
$user = new User();
$user->username = 'jwage';
$user->email = 'jonwage';

if ( ! $user->isValid()) {
    echo 'User is invalid!';
}

The above code will throw an exception because jonwage is not a valid e-mail address. Now we can take this even further and give a valid e-mail address format but an invalid domain name:

// test.php

// ...
$user = new User();
$user->username = 'jwage';
$user->email = 'jonwage@somefakedomainiknowdoesntexist.com';

if ( ! $user->isValid()) {
    echo 'User is invalid!';
}

Now the above code will still fail because the domain somefakedomainiknowdoesntexist.com does not exist and the php function checkdnsrr() returned false.

You may not always want to validate the mx record of an e-mail address. If this is the case then you can disable it by using the check_mx option and setting it to false.

// models/User.php

class User extends Doctrine_Record
{
    public function setTableDefinition()
    {
        // ...

        $this->hasColumn('email_address', 'string', 255, array('email' => array('check_mx' => false)));
    }
}

Not Blank

The not blank validator is similar to the not null validator except that it will fail on empty strings or strings with white space.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('username', 'string', 255, array(
                'notblank'   => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      notblank: true
# ...

Now if we try and save a User record with a username that is a single blank white space, validation will fail:

// test.php

// ...
$user = new User();
$user->username = ' ';

if ( ! $user->isValid()) {
    echo 'User is invalid!';
}

No Space

The no space validator is simple. It checks that the value doesn't contain any spaces.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('username', 'string', 255, array(
                'nospace'   => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      nospace: true
# ...

Now if we try and save a User with a username that has a space in it, the validation will fail:

$user = new User();
$user->username = 'jon wage';

if ( ! $user->isValid()) {
    echo 'User is invalid!';
}

Past

The past validator checks if the given value is a valid date in the past. In this example we'll have a User model with a birthday column and we want to validate that the date is in the past.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('birthday', 'timestamp', null, array(
                'past' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    birthday:
      type: timestamp
      past: true
# ...

Now if we try and set a birthday that is not in the past we will get a validation error.

Future

The future validator is the opposite of the past validator and checks if the given value is a valid date in the future. In this example we'll have a User model with a next_appointment_date column and we want to validate that the date is in the future.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('next_appointment_date', 'timestamp', null, array(
                'future' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    next_appointment_date:
      type: timestamp
      future: true
# ...

Now if we try and set an appointment date that is not in the future we will get a validation error.

Min Length

The min length does exactly what it says. It checks that the value string length is greater than the specified minimum length. In this example we will have a User model with a password column where we want to make sure the length of the password is at least 5 characters long.

// models/User.php

class User extends BaseUser
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...

        $this->hasColumn('password', 'timestamp', null, array(
                'minlength' => 5
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    password:
      type: timestamp
      minlength: 5
# ...

Now if we try and save a User with a password that is shorter than 5 characters, the validation will fail.

// test.php

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

if ( ! $user->isValid()) {
    echo 'User is invalid because "test" is only 4 characters long!';
}

Country

The country validator checks if the given value is a valid country code.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('country', 'string', 2, array(
                'country' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    country:
      type: string(2)
      country: true
# ...

Now if you try and save a User with an invalid country code the validation will fail.

// test.php

// ...
$user = new User();
$user->username = 'jwage';
$user->country_code = 'zz';

if ( ! $user->isValid()) {
    echo 'User is invalid because "zz" is not a valid country code!';
}

IP Address

The ip address validator checks if the given value is a valid ip address.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('ip_address', 'string', 15, array(
                'ip' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    ip_address:
      type: string(15)
      ip: true
# ...

Now if you try and save a User with an invalid ip address the validation will fail.

$user = new User();
$user->username = 'jwage';
$user->ip_address = '123.123';

if ( ! $user->isValid()) {
    echo 'User is invalid because "123.123" is not a valid ip address
}

HTML Color

The html color validator checks that the given value is a valid html hex color.

// models/User.php

class User extends BaseUser
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...

        $this->hasColumn('favorite_color', 'string', 7, array(
                'htmlcolor' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    favorite_color:
      type: string(7)
      htmlcolor: true
# ...

Now if you try and save a User with an invalid html color value for the favorite_color column the validation will fail.

// test.php

// ...
$user = new User();
$user->username = 'jwage';
$user->favorite_color = 'red';

if ( ! $user->isValid()) {
    echo 'User is invalid because "red" is not a valid hex color';
}

Range

The range validator checks if value is within given range of numbers.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('age', 'integer', 3, array(
                'range' => array(10, 100)
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    age:
      type: integer(3)
      range: [10, 100]
# ...

Now if you try and save a User with an age that is less than 10 or greater than 100, the validation will fail.

// test.php

// ...
$user = new User();
$user->username = 'jwage';
$user->age = '3';

if ( ! $user->isValid()) {
    echo 'User is invalid because "3" is less than the minimum of "10"';
}

You can use the range validator to validate max and min values by omitting either one of the 0 or 1 keys of the range array. Below is an example:

// models/User.php

class User extends BaseUser
{
    public function setTableDefinition()
    {
        parent::setTableDefinition();

        // ...

        $this->hasColumn('age', 'integer', 3, array(
                'range' => array(1 => 100)
            )
        );
    }
}

The above would make it so that age has a max of 100. To have a minimum value simple specify 0 instead of 1 in the range array.

The YAML syntax for this would look like the following:

---
# schema.yml

# ...
User:
  columns:
# ...
    age:
      type: integer(3)
      range:
        1: 100
# ...

Unique

Unique constraints ensure that the data contained in a column or a group of columns is unique with respect to all the rows in the table.

In general, a unique constraint is violated when there are two or more rows in the table where the values of all of the columns included in the constraint are equal. However, two null values are not considered equal in this comparison. That means even in the presence of a unique constraint it is possible to store duplicate rows that contain a null value in at least one of the constrained columns. This behavior conforms to the SQL standard, but some databases do not follow this rule. So be careful when developing applications that are intended to be portable.

The following definition uses a unique constraint for column name.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('username', 'string', 255, array(
                'unique' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      unique: true
# ....

You should only use unique constraints for columns other than the primary key because they are always unique already.

If you wish to add a unique constraint to more than one column then you can use the unique() mapping method.

class User extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('username', 'string', 255);
        $this->hasColumn('email_address', 'string', 255);

        $this->unique('username', 'email_address');
    }
}

Using the unique() method is a convenience method for adding a unique index for the specified fields. You can accomplish the same thing by manually adding the unique index for the specified fields.

Regular Expression

The regular expression validator is a simple way to validate column values against your own provided regular expression. In this example we will make sure the username contains only valid letters or numbers.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('username', 'string', 255, array(
                'regexp' => '/[a-zA-Z0-9]/'
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
    username:
      type: string(255)
      regexp: '/^[a-zA-Z0-9]+$/'
# ...

Now if we were to try and save a User with a username that has any other character than a letter or number in it, the validation will fail:

// test.php

// ...
$user = new User();
$user->username = '[jwage';

if ( ! $user->isValid()) {
    echo 'User is invalid because the username contains a [ character';
}

Credit Card

The credit card validator simply checks that the given value is indeed a valid credit card number.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this->hasColumn('cc_number', 'integer', 16, array(
                'creditcard' => true
            )
        );
    }
}

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

---
# schema.yml

# ...
User:
  columns:
# ...
    cc_number:
      type: integer(16)
      creditcard: true
# ...

Read Only

The read only validator will fail validation if you modify a column that has the readonly validator enabled on it.

// models/User.php

class User extends BaseUser
{
    // ...

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

        // ...

        $this