PHP Doctrine 2 Tutorial

PHP Doctrine 2 Tutorial

PHP Doctrine 2 Tutorial

This Tutotial illustrates why PHP Doctrine 2 is such a big help for the application developer. Step by step, we will implement, on our own, certain ORM features that are already included in PHP Doctrine 2. 

 

Loading an Entity

A domain model is a good thing. As long as the application developer acts in the familiar object-oriented world, there are no obstacles to designing and implementing a domain model. Design and implementation become harder, though, if it becomes necessary to persist objects of the domain model in relational databases or to load and reconstruct previously stored objects.

 

There is, for instance, the fact that objects are more than just dumb data structures. With their methods, they have behavior as well. Let’s consider the User entity definition of the Talking demo application:

 

<?php namespace Entity; class User { private $id; private $firstName; private $lastName; private $gender; private $namePrefix; const GENDER_MALE=0; const GENDER_FEMALE=1; const GENDER_MALE_DISPLAY_VALUE="Mr."; const GENDER_FEMALE_DISPLAY_VALUE="Ms."; public function assembleDisplayName() { $displayName=''; if ($this->gender == self::GENDER_MALE) {
$displayName .= self::GENDER_MALE_DISPLAY_VALUE;
} else if ($this->gender == self::GENDER_FEMALE) {
$displayName .= self::GENDER_FEMALE_DISPLAY_VALUE;
}
if ($this->namePrefix) {
$displayName .= ' ' . $this->namePrefix;
}
$displayName .= ' ' . $this->firstName . ' ' . $this->lastName;
return $displayName;
}
public function setFirstName($firstName)
{
$this->firstName = $firstName;
}
public function getFirstName()
{
return $this->firstName;
}
public function setGender($gender)
{
$this->gender = $gender;
}
public function getGender()
{
return $this->gender;
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setLastName($lastName)
{
$this->lastName = $lastName;
}
public function getLastName()
{
return $this->lastName;
}
public function setNamePrefix($namePrefix)
{
$this->namePrefix = $namePrefix;
}
public function getNamePrefix()
{
return $this->namePrefix;
}
}

 

Listing 1

Interesting here is the method assembleDisplayName(). It creates the “display name” for a user based on a user’s data. The display name is used to print a post’s author.

<?php include('../entity/User.php'); $user=new Entity\User(); $user->setFirstName('Max');

$user->setLastName('Mustermann');
$user->setGender(0);
$user->setNamePrefix('Prof. Dr');
echo $user->assembleDisplayName();

 

Listing 2

The code in Listing 2 results in:

The method assembleDisplayName() therefore defines a specific behavior of a User object.

If a user’s master data is retrieved from the database, it must be transformed in a way that allows the behavior described above to be attached to it. In other words, the user’s master data, retrieved from the database, must be transformed into a User object.

 

Within the application, we always want to deal with objects of our domain so that we can easily create a User’s display name by simply calling its assembleDisplayName() method. Let’s build that into our ORM tool.First, we set up the database structure:

 

CREATE TABLE users(
id int(10) NOT NULL auto_increment,
first_name varchar(50) NOT NULL,
last_name varchar(50) NOT NULL,
gender ENUM('0','1') NOT NULL,
name_prefix varchar(50) NOT NULL,
PRIMARY KEY (id)
);

 

Then, let’s add dummy data for “Max Mustermann” :

INSERT INTO users (first_name, last_name, gender, name_prefix)
VALUES('Max', 'Mustermann', '0', 'Prof. Dr.');
Now, if we want to reconstruct the User object from the database, we can do it like this:
<?php include('../entity/User.php'); $db=new \PDO('mysql:host=localhost;dbname=app', 'root', ''); $userData=$db->query('SELECT * FROM users WHERE id = 1')->fetch();
$user = new Entity\User();
$user->setId($userData['id']);
$user->setFirstName($userData['first_name']);
$user->setLastName($userData['last_name']);
$user->setGender($userData['gender']);
$user->setNamePrefix($userData['name_prefix']);
echo $user->assembleDisplayName();

 

Database credentials in live systems In a live system, you use strong keywords and don’t work with user “root,” right? The code shown above is just an example. With these lines of code, we started implementing our own ORM system, which allows reconstructing domain objects by fetching data from a database. Let’s further improve it.

 

To encapsulate the “data mapping” shown above, we move the code into its own class:

<?php namespace Mapper; class User { private $mapping=array( 'id'=> 'id',
'firstName' => 'first_name',
'lastName' => 'last_name',
'gender' => 'gender',
'namePrefix' => 'name_prefix'
);
public function populate($data, $user)
{
$mappingsFlipped = array_flip($this->mapping);
foreach($data as $key => $value) {
if(isset($mappingsFlipped[$key])) {
call_user_func_array(
array($user, 'set'. ucfirst
($mappingsFlipped[$key])),
array($value)
);
}
}
return $user;
}
}

 

The User mapper is not perfect, but it does the job. Now, our invoking code looks like this:

<?php include_once('../entity/User.php'); include_once('../mapper/User.php'); $db=new \PDO('mysql:host=localhost;dbname=app', 'root', ''); $userData=$db->query('SELECT * FROM users WHERE id = 1')->fetch();

$user = new Entity\User();
$userMapper = new Mapper\User();
$user = $userMapper->populate($userData, $user);
echo $user->assembleDisplayName();

 

However, we can make the mapping process even easier by moving the SQL statement into its own object, a so-called repository:

<?php namespace Repository; include_once('../entity/User.php'); include_once('../mapper/User.php'); use Mapper\User as UserMapper; use Entity\User as UserEntity; class User { private $em; private $mapper; public function __construct($em) { $this->mapper = new UserMapper;
$this->em = $em;
}
public function findOneById($id)
{
$userData = $this->em
->query('SELECT * FROM users WHERE id = ' . $id)
->fetch();
return $this->mapper->populate($userData, new
UserEntity());
}
}

 

Lastly, we move the code that connects to the database into a class called

EntityManager and make the new User repository available through it:

<?php include_once('../repository/User.php'); use Repository\User as UserRepository; class EntityManager { private $host; private $db; private $user; private $pwd; private $connection; private $userRepository; public function construct($host, $db, $user, $pwd) { $this->host = $host;

$this->user = $user;
$this->pwd = $pwd;
$this->connection = new \PDO(
"mysql:host=$host;dbname=$db",
$user,
$pwd);
$this->userRepository = null;
}
public function query($stmt)
{
return $this->connection->query($stmt);
}
public function getUserRepository()
{
if (!is_null($this->userRepository)) {
return $this->userRepository;
} else {
$this->userRepository = new UserRepository($this);
return $this->userRepository;
}
}
}

 

The EntityManager now acts as the main entry point; it opens the database connection as well making database queries available to client code. After this refactoring, the result remains the same: Mr. Prof. Dr. Max Mustermann

 

We wrote a whole bunch of code, and yet we can’t do anything more than read data from a database and make an object out of it. We didn’t really push our own application forward. Looks like building an ORM system is hard work and time-consuming. And we’ve just started. Let’s spend some more time enhancing our ORM so that, later, we will appreciate even more what Doctrine 2 can do for us.

 

Saving an Entity

Saving an Entity

So far, we have implemented a trivial use case: loading a single object from the database based on a given ID. But what about writing operations? Actually, there are two types of write operations: inserts and updates. Let’s first deal with the insert operation by adding an extract() method to the User mapper:

 

<?php namespace Mapper; class User { private $mapping=array( 'id'=> 'id',

'firstName' => 'first_name',
'lastName' => 'last_name',
'gender' => 'gender',
'namePrefix' => 'name_prefix'
);
public function extract($user)
{
$data = array();
foreach($this->mapping as $keyObject => $keyColumn) {
if ($keyColumn != 'id') {
$data[$keyColumn] = call_user_func(
array($user, 'get'. ucfirst($keyObject))
);
}
}
return $data;
}
public function populate($data, $user)
{
$mappingsFlipped = array_flip($this->mapping);
foreach($data as $key => $value) {
if(isset($mappingsFlipped[$key])) {
call_user_func_array(
array($user, 'set'. ucfirst(
$mappingsFlipped[$key])),
array($value)
);
}
}
return $user;
}
}

 

This is how we extract the data from the object. The EntityManager, extended by a saveUser() method, now can insert a new record into the database:

<?php include_once('../repository/User.php'); include_once('../mapper/User.php'); use Repository\User as UserRepository; use Mapper\User as UserMapper; class EntityManager { private $host; private $db; private $user; private $pwd; private $connection; private $userRepository; public function __construct($host, $db, $user, $pwd) { $this->host = $host;
$this->user = $user;
$this->pwd = $pwd;
$this->connection =
new \PDO("mysql:host=$host;dbname=$db", $user,
$pwd);
$this->userRepository = null;
}
public function query($stmt)
{
return $this->connection->query($stmt);
}
public function saveUser($user)
{
$userMapper = new UserMapper();
$data = $userMapper->extract($user);
$columnsString = implode(", ", array_keys($data));
$valuesString = implode(
"',
'",
array_map("mysql_real_escape_string", $data)
);
return $this->query(
"INSERT INTO users ($columnsString)
VALUES('$valuesString')"
);
}
public function getUserRepository()
{
if (!is_null($this->userRepository)) {
return $this->userRepository;
} else {
$this->userRepository = new UserRepository($this);
return $this->userRepository;
}
}
}
Adding a new record now works like this:
<?php include_once('../EntityManager.php'); $em=new EntityManager('localhost', 'app', 'root', ''); $user=$em->getUserRepository()->findOneById(1);
echo $user->assembleDisplayName() . '<br />';
$newUser = new Entity\User();
$newUser->setFirstName('Ute');
$newUser->setLastName('Mustermann');
$newUser->setGender(1);
$em->saveUser($newUser);
echo $newUser->assembleDisplayName();

 

So far, so good! But what if we want to update an existing record? How do we identify whether we are dealing with a new record or one that already exists? In our case, we might simply check to see whether the object already has a value for the given ID field. ID is an “auto_increment” field, so MySQL will populate it automatically for any new record.

 

For sure, this is not the most elegant solution one might come up with. We might use a so-called identity map that brings more advantages, such as re-reading an already-loaded database without querying the database again. An identity map is nothing more than an associative array holding references to already loaded entities based on IDs. A good place for an identity map is the entity manager and its saveUser() method:

<?php include_once('../repository/User.php'); include_once('../mapper/User.php'); use Repository\User as UserRepository; use Mapper\User as UserMapper; class EntityManager { private $host; private $db; private $user; private $pwd; private $connection; private $userRepository; private $identityMap; public function construct($host, $db, $user, $pwd) { $this->host = $host;

$this->user = $user;
$this->pwd = $pwd;
$this->connection =
new \PDO("mysql:host=$host;dbname=$db", $user,
$pwd);
$this->userRepository = null;
$this->identityMap = array('users' => array());
}
public function query($stmt)
{
return $this->connection->query($stmt);
}
public function saveUser($user)
{
$userMapper = new UserMapper();
$data = $userMapper->extract($user);
$userId = call_user_func(
array($user, 'get'. ucfirst($userMapper->
getIdColumn()))
);
if (array_key_exists($userId, $this->identityMap['users'])) {
$setString = '';
foreach ($data as $key => $value) {
$setString .= $key."='$value',";
}
return $this->query(
"UPDATE users SET " . substr($setString,
, -1) .
" WHERE " . $userMapper->getIdColumn() .
"=" . $userId
);
} else {
$columnsString = implode(", ", array_keys($data));
$valuesString = implode(
"',
'",
array_map("mysql_real_escape_string",
$data)
);
return $this->query(
"INSERT INTO users ($columnsString)
VALUES('$valuesString')"
);
}
}
public function getUserRepository()
{
if (!is_null($this->userRepository)) {
return $this->userRepository;
} else {
$this->userRepository = new UserRepository($this);
return $this->userRepository;
}
}
public function registerUserEntity($id, $user)
{
$this->identityMap['users'][$id] = $user;
return $user;
}
}
As you can see, we added a method, getIdColumn(), which returns “id.” Now the following code works nicely:
<?php include_once('../EntityManager.php'); $em=new EntityManager('localhost', 'app', 'root', ''); $user=$em->getUserRepository()->findOneById(1);
echo $user->assembleDisplayName() . '<br />';
$user->setFirstname('Moritz');
$em->saveUser($user);
The entity is updated in the database and no additional record is added.
Associations
Now we want to list all posts from a specific User. To do this, we would like to simply iterate over the Posts collection of a given User and print each Post’s title:
<?php include_once('../EntityManager.php'); $em=new EntityManager('localhost', 'app', 'root', ''); $user=$em->getUserRepository()->findOneById(1);
?>
<h1><?php echo $user->assembleDisplayName(); ?></h1>
<ul>
<?php foreach($user->getPosts() as $post) { ?>
<li><?php echo $post->getTitle(); ?></li>
<?php } ?>
</ul>
What do we have to do to make this happen?
First, we create a data structure for posts:
CREATE TABLE posts(
id int(10) NOT NULL auto_increment,
user_id int(10) NOT NULL,
title varchar(255) NOT NULL,
content text NOT NULL,
PRIMARY KEY (id)
);
We add a test User and some test Posts, so that we can actually test the implementation. Next, we need to extend a whole bunch of classes. The User entity gets a getPosts() method, which loads the User’s Posts on first invocation via the corresponding repository:
<?php namespace Entity; class User { // [..] private $postRepository; public function getPosts() { if (is_null($this->posts)) {
$this->posts = $this->postRepository->findByUser($this);
}
return $this->posts;
}
// [..]
}

 

This will work only if the User entity has access to the Post repository. To make it available, the User repository method findOneById() needs to be extended:

 

<?php namespace Repository; include_once('../entity/User.php'); include_once('../mapper/User.php'); use Mapper\User as UserMapper; use Entity\User as UserEntity; class User { private $em; private $mapper; public function __construct($em) { $this->mapper = new UserMapper;

$this->em = $em;
}
public function findOneById($id)
{
$userData = $this->em
->query('SELECT * FROM users WHERE id = ' . $id)
->fetchAll();
$newUser = new UserEntity();
$newUser->setPostRepository(
$this->em->getPostRepository());
return $this->em->registerUserEntity(
$id,
$this->mapper->populate($userData, $newUser)
);
}
}

 

The entity manager needs to be extended by the method getPostRepository() as well:

<?php // [..] class EntityManager { // [..] private $postRepository; public function getPostRepository() { if (!is_null($this->postRepository)) {
return $this->postRepository;
} else {
$this->postRepository = new
PostRepository($this);
return $this->postRepository;
}
}
}

 

Now, the Post entity and the Post mapper must be implemented:

<?php namespace Entity; class Post { private $id; private $title; private $content; public function setContent($content) { $this->content = $content;

}
public function getContent()
{
return $this->content;
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
}
And here is the mapper:
<?php namespace Mapper; class Post { private $mapping=array( 'id'=> 'id',
'title' => 'title',
'content' => 'content',
);
public function getIdColumn()
{
return 'id';
}
public function extract($user)
{
$data = array();
foreach ($this->mapping as $keyObject => $keyColumn) {
if ($keyColumn != $this->getIdColumn()) {
$data[$keyColumn] = call_user_func(
array($user, 'get'.
ucfirst($keyObject))
);
}
}
return $data;
}
public function populate($data, $user)
{
$mappingsFlipped = array_flip($this->mapping);
foreach ($data as $key => $value) {
if (isset($mappingsFlipped[$key])) {
call_user_func_array(
array($user, 'set'. ucfirst(
$mappingsFlipped[$key])),
array($value)
);
}
}
return $user;
}
}

 

Last but not least, the Post repository:

<?php namespace Repository; include_once('../entity/Post.php'); include_once('../mapper/Post.php'); use Mapper\Post as PostMapper; use Entity\Post as PostEntity; class Post { private $em; private $mapper; public function __construct($em) { $this->mapper = new PostMapper;

$this->em = $em;
}
public function findByUser($user)
{
$postsData = $this->em
->query('SELECT * FROM posts WHERE user_id = '
. $user->getId())
->fetchAll();
$posts = array();
foreach($postsData as $postData) {
$newPost = new PostEntity();
$posts[] = $this->mapper->populate($postData,
$newPost);
}
return $posts;
}
}

 

That’s it! Up to this point, the application’s files and folder structure looks like this:

EntityManager.php
entity/
Post.php
User.php
mapper/
Post.php
User.php
repository/
Post.php
User.php
public/
index.php

 

Next Steps

Great! We built our own little ORM tool. However, already, our code doesn’t look that good anymore. The fact that technical code is mixed up with domain-specific code is an issue. Our solutions to the problems faced are valid only to our concrete use case. Also, it smells like “copy & paste” in here! In fact, some refactoring already needs to be done.

 

And what about composite primary keys? Adding and deleting associations? Many-to-many associations? Inheritance, performance, caching and entities, mappers and repositories for the tons of yet-missing core elements of the application? Also, what happens if the data structures change? This would mean refactoring of multiple classes! Looks like it is time for Doctrine 2 to enter the stage.

 

PHP Doctrine 2

PHP Doctrine 2

Doctrine 2 instead provides full transparent persistence for PHP objects by implementing the so called “Data Mapper Pattern”.

Essentially, Doctrine allows the programmer to focus on the object-oriented business logic and takes the pain out of PHP object persistence. Don’t worry! We run through Doctrine 2 quickly in this blog, but we will look into each individual aspect of it in depth later in the blog.

 

Installation

The easiest way to install Doctrine 2 is by using “Composer,” which can be downloaded from its website. Store the PHP archive (phar) file in the root directory of the application. Then, create a file called composer.json with the following contents (you may need to adjust the version number given):

{


"require": {

"doctrine/orm": "2.3.1"

}

}

 

In the root directory of the application, execute the command 1 $ php composer.phar install

To download Doctrine 2 to the vendor subfolder. Composer makes it available to the application by configuring autoloading for its classes. If you receive a composer error or warning, you may want to update composer itself to its latest version first by running

$ php composer.phar self-update You then only need to add

<?php


// [..]

if (file_exists('vendor/autoload.php')) {

$loader = include 'vendor/autoload.php';

}

to the index.php file. Once downloaded, all Doctrine 2 classes are loaded automatically on demand. Nice!

 

A First Entity

Based on the first section of this blog, in which we developed our own little ORM system, we now can radically simplify the code needed by adding Doctrine 2 annotations to the User entity:

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { /** * @Id @Column(type="integer") * @GeneratedValue */ private $id; /** @Column(type="string", name="first_name", nullable=true) */ private $firstName; /** @Column(type="string", name="last_name", nullable=true) */ private $lastName; /** @Column(type="string", nullable=true) */ private $gender; /** @Column(type="string", name="name_prefix", nullable=true) */ private $namePrefix; const GENDER_MALE=0; const GENDER_FEMALE=1; const GENDER_MALE_DISPLAY_VALUE="Mr."; const GENDER_FEMALE_DISPLAY_VALUE="Ms."; public function assembleDisplayName() { $displayName=''; if ($this->gender == self::GENDER_MALE) {

$displayName .= self::GENDER_MALE_DISPLAY_VALUE;
} elseif ($this->gender == self::GENDER_FEMALE) {
$displayName .= self::GENDER_FEMALE_DISPLAY_
VALUE;
}
if ($this->namePrefix) {
$displayName .= ' ' . $this->namePrefix;
}
$displayName .= ' ' . $this->firstName . ' ' . $this->lastName;
return $displayName;
}
public function setFirstName($firstName)
{
$this->firstName = $firstName;
}
public function getFirstName()
{
return $this->firstName;
}
public function setGender($gender)
{
$this->gender = $gender;
}
public function getGender()
{
return $this->gender;
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setLastName($lastName)
{
$this->lastName = $lastName;
}
public function getLastName()
{
return $this->lastName;
}
public function setNamePrefix($namePrefix)
{
$this->namePrefix = $namePrefix;
}
public function getNamePrefix()
{
return $this->namePrefix;
}
}

 

The code in index.php now can be changed to the following lines of code to read and modify entities using Doctrine 2:

<?php include '../entity/User.php'; include '../vendor/autoload.php'; use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\EntityManager; $paths=array(__DIR__ . "/../entity/"); $isDevMode=true; $dbParams=array( 'driver'=> 'pdo_mysql',

'user' => 'root',
'password' => '',
'dbname' => 'app',
);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$em = EntityManager::create($dbParams, $config);
$user = $em->getRepository('Entity\User')->findOneById(1);
echo $user->assembleDisplayName() . '<br />';
$user->setFirstname('Moritz');
$em->persist($user);
$em->flush();

 

Please note that we replaced our custom entity manager with the one provided by Doctrine as well as the user repository with it findOneById method. All this code is available out-of-the-box. The only custom code still needed is the user entity definition.

That’s it! Easy!

 

A First Association

Dealing with the association is easy as well. The User entity has to be changed to:

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { // [..] /** * @OneToMany(targetEntity="Entity\Post", mappedBy="user") */ private $posts; // [..] public function __construct() { $this->posts = new \Doctrine\Common\Collections\

ArrayCollection();
}
// [..]
}
Now add some more annotations to the Post entity:
<?php namespace Entity; /** * @Entity * @Table(name="posts") */ class Post { /** * @Id @Column(type="integer") * @GeneratedValue */ private $id; /** * @ManyToOne(targetEntity="Entity\User", inversedBy="posts") * @JoinColumn(name="user_id", referencedColumnName="id") */ private $user; /** @Column(type="string") */ private $title; /** @Column(type="string") */ private $content; public function setUserId($user_id) { $this->user_id = $user_id;
}
public function getUserId()
{
return $this->user_id;
}
public function setContent($content)
{
$this->content = $content;
}
public function getContent()
{
return $this->content;
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
}
Once the User is loaded, you can iterate over the User’s posts as before:
<?php // [..] $user=$em->getRepository('Entity\User')->findOneById(1);
?>
<h1><?echo $user->assembleDisplayName(); ?></h1>
<ul>
<?php foreach($user->getPosts() as $post) {?>
<li><?php echo $post->getTitle(); ?></li>
<?php } ?>
</ul>

 

In fact, with Doctrine 2 included, we don’t need most of the code we wrote to implement the exact same functionality by hand:

entity/

Post.php
User.php
public/
index.php

It couldn’t be much easier—thank you, Doctrine 2! Before we add more features to our demo app, we will now look at some basic concepts of Doctrine 2.

 

Core Concepts at a Glance

Core Concepts at a Glance

Before we take a deep dive into the details of Doctrine 2, let’s step back and look at its core concepts.

We already defined an entity as an object with identity that is managed by Doctrine 2 and persisted in a database. Entities are the domain objects of an application. Saving entities and retrieving them from the database is essential to an application.

 

The term “entity” is used with two different meanings throughout this blog: on one hand, it is used for single, persistent object; on the other hand, it is used for PHP classes that act as templates for persistent objects. With Doctrine 2, a persistent object, an entity, is always in a specific state. This state can be “NEW,” “MANAGED,” “DETACHED,” or “REMOVED.” We will learn about this later in the blog.

 

The entity manager is Doctrine’s core object. As an application developer, one does interact a lot with the entity manager. The entity manager is responsible for persisting new entities, and also takes care of updating and deleting them. Repositories are retrieved via the entity manager.

 

Simply speaking, a repository is a container for all entities of a specific type. It allows you to look-up entities via so-called finder methods or finders. Some finders are available by default, others may be added manually if needed.

 

Doctrine’s database access layer (DBAL) is the foundation for the Doctrine 2 ORM library. It’s a stand-alone component that can be used even without using Doctrine’s ORM capabilities. Technically, DBAL is a wrapper for PHP’s PHP data objects (PDO) extension that makes dealing with databases even easier compared to PHP alone. And again, Doctrine 2 ORM makes dealing with databases even easier than with DBAL alone.

 

Defining Entities

Defining Entities

We have already learned how easy it is to map member variables to fields in a database table. Let’s take a look at exactly how this works.

 

Mapping Formats

In general, mappings can be defined using annotations within the entities themselves, or with external XML or YAML files. In terms of performance, there is no difference between the different ways of implementing the mapping when using a cache. Therefore, using annotations is often preferred, simply because they make coding easier, as all the persistence metadata is located in the same file. In the following examples, annotations are used exclusively.

 

Annotations are meta information used to tell Doctrine how to persist data. Syntactically, Doctrine annotations follow the PHPDoc standard which is also used to auto-generate code documentation using compatible tools. PHPDoc uses individual DocBlocks to provide meta information on certain code elements. In several IDEs, annotations are also used to provide improved code completion, type hinting and to support debugging.

 

Mapping Objects to Tables

First of all, a class that acts as a template for persistent objects needs to have the @Entity annotation:

<?php

/**
* @Entity
*/
class User
{
// [..]
}

A DocBlock is an extended PHP comment that begins with "/**" and has an "*" at the beginning of every line. Simply adding @Entity without additional configuration means that Doctrine 2 looks for a database table named user, in this case. If the table has another name, the table name can be configured like this:

<?php

/**
* @Entity
* @Table(name="users")
*/
class User
{
// [..]
}

 

An optional attribute, “indexes,” of the @Table annotation can be used to create indexes automatically using Doctrine’s schema tool, which we will discuss later in the blog. For now, one should remember that this attribute has no meaning during runtime. Some persistence metadata is important only in conjunction with offline tools such as the schema tool, while other metadata is evaluated during runtime. An index definition to the users table goes like this:

 

<?php
/**
* @Entity
* @Table(name="users",
*indexes={@Index(name="search_idx", columns={"name", "email"})}
* )
*/
class User
{
// [..]
}

 

The indexes attribute of annotation @Table itself has an annotation with attributes as its value. The annotation @Index needs the name attribute (name of the index) and the list of columns to be considered. Multiple values may be given using curly braces.

The same applies to another optional attribute called uniqueConstraints which defines special unique value indexes:

<?php

/**
* @Entity
* @Table(name="users",
* uniqueConstraints={@UniqueConstraint(name="user_unique",
* columns={"username"})},
* indexes={@Index(name="user_idx", columns={"email"})}
* )
*/
class User
{
// [..]
}
Mapping Scalar Member Variables to Fields
Mapping scalar-valued member variables (numbers or strings, for example) to a database table is super simple:
<?php
/ [..]
/** @Column(type="string") */
private $lastName;

 

First, it’s important that all member variables mapped to database fields are declared as private or protected. Doctrine 2 often applies a technique called lazy loading, which means that data is loaded just in the moment it is needed, but not earlier. To make this happen, Doctrine 2 implements so-called proxy objects which rely on the entities’ getter methods. Therefore, one will want to make sure they are set-up.

 

The main annotation here is @Column. Its attribute type is the only mandatory attribute and is important to make persistence work correctly. If a wrong type is defined, you may lose information while saving to or reading from the database. In addition, there are many other optional attributes, such as name, which allows you to configure a table column name.

 

It defaults to the name of the member variableThe length attribute is needed only for strings, where it defines the maximum length of a string value. It defaults to 255 if not defined differently. If one encounters truncated values in the database, a wrong length configuration often is the root cause. The attributes precision and scale are valid only for values of type decimal, while unique ensures uniqueness, and nullable tells Doctrine 2 whether a NULL value is allowed (true) or not (false):

<?php

// [..]
/**
* @Column(type="string",
* name="last_name", length=32, unique=true, nullable=false)
*/

protected $lastName;

 

Data Types

data type

Doctrine 2 ships with a set of data types that map PHP data types to SQL data types.

  • string: Type that maps an SQL VARCHAR to a PHP string
  • integer: Type that maps an SQL INT to a PHP integer
  • smallint: Type that maps a database SMALLINT to a PHP integer
  • bigint: Type that maps a database BIGINT to a PHP string
  • boolean: Type that maps an SQL boolean to a PHP boolean
  • decimal: Type that maps an SQL DECIMAL to a PHP double
  • date: Type that maps an SQL DATETIME to a PHP DateTime object
  • time: Type that maps an SQL TIME to a PHP DateTime object
  • datetime: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object
  • text: Type that maps an SQL CLOB to a PHP string
  • object: Type that maps an SQL CLOB to a PHP object using serialize() and unserialize()
  • array: Type that maps an SQL CLOB to a PHP object using serialize() and unserialize()
  • float: Type that maps an SQL Float (Double Precision) to a PHP double. IMPORTANT: Type float works only with locale settings that use decimal points as separators.

 

Custom data types If needed, you can define custom data types, mappings between PHP data types and SQL data types. The official documentation covers this topic in depth.

 

Entity Identifier

An entity class must define a unique identifier. The @Id annotation is used for this:

<?php

// [..]

/**

* @Id @Column(type="integer")

*/

 

private $id;

If the @GeneratedValue annotation is used as well, the unique identifier doesn’t need to be given by the application developer but is assigned automatically by the database system. The way this value is generated depends on the strategy defined. By default, Doctrine 2 identifies the best approach on its own.

 

When using MySQL, for instance, Doctrine 2 uses the auto_increment function. There are additional strategies that can be used; however, they usually don’t work across platforms and therefore should be avoided.

 

By the way, the @Id annotation can be used with multiple member variables to define composite keys. Clearly, the @GeneratedValue annotation then cannot be used anymore. In this case, the application developer needs to take care in assigning unique keys.

 

Inheritance

Inheritance

Doctrine 2 supports persistence of hereditary structures with three different strategies:

  • Single Table Inheritance
  • Class Table Inheritance
  • Mapped Superclass

 

Single Table Inheritance

The single table inheritance strategy uses a single database table with a so-called “discriminator column” to differentiate between the different types. All classes of a hierarchy go into the same table. Let’s say our demo application differentiates between different types of posts. The supported types are:

  • Post: A simple, text based post
  • ImagePost: A post with text and image
  • VideoPost: A post with text and video

 

Both ImagePost and VideoPost extend Post and add an image or video URL. To set up this hierarchy, the topmost Post class holds the configuration needed as well as all member variables and associations that are shared between the three types:

<?php

namespace Entity;
/**
* @Entity(repositoryClass="Repository\Post")
* @Table(name="posts")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"text"="Post","video"="VideoPost","image"= "ImagePost"})
*/
class Post
{
// member variables and associations shared between all types
}

 

Since the Post itself can be an entity, it has the typical entity-related annotations. In addition, via the @InheritanceType annotation, it configures single table inheritance. The @DiscriminatorColumn annotation is used to label the column which indicates the type persisted. Lastly, @DiscriminatorMap defines the values that can be present in the discriminator column.

 

In our case, Doctrine 2 writes “text” into the discriminator column if the entity is a Post. If the entity is a VideoPost, Doctrine 2 writes “video,” and if the entity is an ImagePost, the value used is “image.”

The ImagePost itself is straightforward:

<?php namespace Entity; /** * @Entity */ class ImagePost extends Post { /** @Column(type="string") */ protected $imageUrl; /** * @param mixed $imageUrl */ public function setImageUrl($imageUrl) { $this->imageUrl = $imageUrl;
}
/**
* @return mixed
*/
public function getImageUrl()
{
return $this->imageUrl;
}
}
The same is true for the VideoPost:
<?php namespace Entity; /** * @Entity */ class VideoPost extends Post { /** @Column(type="string") */ protected $videoUrl; /** * @param mixed $videoUrl */ public function setVideoUrl($videoUrl) { $this->videoUrl = $videoUrl;
}
/**
* @return mixed
*/
public function getVideoUrl()
{
return $this->videoUrl;
}
}

 

Now all three types of entities can easily be persisted. The downside of this approach is that, depending on how complex the hierarchy is and how different the types involved are, the table grows and grows, with many fields never used by certain types. In our case, the videoUrl won’t ever be used by entities of type ImagePost. The imageUrl won’t be used by VideoPost entities, and for Post entities, both fields will remain blank.

 

The upside is that this strategy—due to its very nature—allows for very efficient queueing across all types in the hierarchy without any joins.

 

Class Table Inheritance

While single table inheritance uses only one table for all types, with class table inheritance each class in the hierarchy is mapped to several tables. While this gives the best flexibility of the three strategies, this also means that reconstructing entities will require joins in the database. Since joins are usually expensive operations, the performance with class table inheritance is not as good as with single table inheritance.

 

The main difference in configuration is in Post:

<?php
namespace Entity;
/**
* @Entity(repositoryClass="Repository\Post")
* @Table(name="posts")
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"text"="Post","video"="VideoPost","image"= "ImagePost"})
*/
class Post
{
// member variables and associations shared between all types
}

 

Instead of inheritance type “SINGLE_TABLE,” now “JOINED” is used. This tells Doctrine 2 to apply the class table inheritance strategy. Since tables will be used for each type, configuring the table names may be useful for subclasses:

<?php namespace Entity; /** * @Entity * @Table(name="posts_image") */ class ImagePost extends Post { /** @Column(type="string") */ protected $imageUrl; /** * @param mixed $imageUrl */ public function setImageUrl($imageUrl) { $this->imageUrl = $imageUrl;

}
/**
* @return mixed
*/
public function getImageUrl()
{
return $this->imageUrl;
}
}
The table name posts_image is handy for ImagesPost entities. We add the same style of configuration to VideoPost entities as well:
<?php namespace Entity; /** * @Entity * @Table(name="posts_video") */ class VideoPost extends Post { /** @Column(type="string") */ protected $videoUrl; /** * @param mixed $videoUrl */ public function setVideoUrl($videoUrl) { $this->videoUrl = $videoUrl;
}
/**
* @return mixed
*/
public function getVideoUrl()
{
return $this->videoUrl;
}
}

 

When letting Doctrine 2 create the database schema, we will end up with three tables for this hierarchy, while the posts table holds the main information:

By evaluating the foreign key reference, entities of type ImagePost and VideoPost can be reconstructed by table joins.

 

Mapped Superclass

Lastly, a mapped superclass strategy can be used. The main difference from the other two strategies already discussed is that the superclass itself cannot be an entity. Taking the example above, this means that class Post cannot itself be an entity. It simply provides member variables and associations to be inherited by its subclasses.

To configure the mapped superclass strategy means adding the @MappedSuperclass annotation to the Post class:

<?php


namespace Entity;

/** @MappedSuperclass */

class Post

{

// member variables and associations shared between all types

}

 

The two subclasses, ImagePost and VideoPost, do not need to be changed. However, with the mapped superclass strategy applied, we cannot persist objects of type Post anymore, which mainly consisted of text. We could fix this by adding another new type called TextPost:

<?php
namespace Entity;
/**
* @Entity
* @Table(name="posts_text")
*/
class TextPost extends Post
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
protected $id;
}

 

Now we have three concrete entity types and one superclass that is not an entity.

 

This results in three tables in the database: posts_text, posts_images and posts_video. As shown above, it’s important that each entity specifies its ID column, while the mapped superclass does not. It only imparts member variables and associations, but not the ID column definition.

 

When inheriting attributes or associations as shown above, one may want to overwrite certain definitions. Let’s assume we don’t want to talk about content in a VideoPost, but call it closed_ caption. This requires the following configuration:

<?php

namespace Entity;
/**
* @Entity
* @Table(name="posts_video")
* @AttributeOverrides({
*@AttributeOverride(name="content",
*column=@Column(
* name = "closed_caption",
* type = "text"
* )
* )
* })
*/
class VideoPost extends Post
{
// [..]
}

 

While Doctrine 2 requires member variables to be either private or protected, it’s important to remember that member variables need to be protected here; otherwise they won’t be available in the subclass. When overwriting the property content with closed_caption as shown above, this leads to a closed_caption column in the database for this type of entity.

Also, entities of type VideoPost are organized in categories called channels. We can overwrite the association as shown below:

<?php

namespace Entity;
/**
* @Entity
* @Table(name="posts_video")
* @AssociationOverrides({
* @AssociationOverride(name="category",
* joinColumns=@JoinColumn(
* name="channel_id", referencedColumnName="id"
* )
* )
* })
*/
class VideoPost extends Post
{
// [..]

 

References Between Entities

References

Entities usually are part of a bigger, interconnected object graph. They have references to other entities. A User holds references to its Posts, while a Post references a Category which references back to the User, who (in turn) sets up the Category, and so forth. As we will see, there are many different ways to establish connections between entities. Connections are characterized by the number of items they connect and the association’s direction. Let’s take a look!

 

Domain model of the demo app Talking The following code samples are related to the domain model of the demo app introduced earlier in the blog. The drawing of the domain model might be helpful as a reference throughout this blog.

 

One-to-One Relationship, Unidirectional

In contrast to foreign key relationships in a database, which always join two tables in both directions, this is not true for objects. Therefore, Doctrine 2 differentiates between unidirectional and bidirectional associations between objects. Unidirectional means that one objects points to the other, but the latter does not have a pointer back.

 

To set up this type of a relationship between two entities, we simply need to pick a member variable acting as the pointer:

<?php

namespace Entity;
/**
* @Entity
* @Table(name="users")
*/
class User
{
// [..]
/**
* @OneToOne(targetEntity="Entity\ContactData")
*/
private $contactData;
// [..]
}

 

The @OneToOne annotation defines the type of the relationship: a one-to-one relationship. The targetEntity attribute defines the entity class to which the pointer points. The class given with the targetEntity attribute needs to be fully qualified, including a namespace if applicable. In any case, you must not add a leading backslash.

 

It’s now already possible to reach a referenced entity (ContactData) through a loaded User, if a getter method has been added:

<?php var_dump($user->getContactData());

However, the simple persistence configuration shown above only works because
ContactData has a member variable called id:
<?php
namespace Entity;
/**
* @Entity
* @Table(name="contact_data")
*/
class ContactData
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
// [..]
}
If the member variable has a different name, such as contactDataId or something similar, we need to tell Doctrine 2 about it:
<?php
namespace Entity;
/**
* @Entity
* @Table(name="users")
*/
class User
{
// [..]
/**
* @OneToOne(targetEntity="Entity\ContactData")
* @JoinColumn(name="id", referencedColumnName="contactDataId")
*/
private $contactData;
// [..]
}

 

Later in this blog we will learn more about t the @JoinColumn annotation.

Loading a User and its ContactData as shown above works only if the data structure in the database has also been set up beforehand, in addition to the PHP class and the persistence configuration. If not, all this won’t work and errors are reported by Doctrine Based on this situation, one of the following will be true:

We have an existing data structure given in the database and we adapt the persistence configuration to it. We do not have an existing data structure and the persistence configuration has no external restrictions. In this case, the Doctrine 2 schema tool can create the data structure in the database based simply on the persistence configuration. 

 

In any case, it’s important that the data structure and the Doctrine 2 persistence mappings match. If not, we will be in trouble for sure. The schema tool can also be used to verify that data structure and persistence mappings match.

 

In the following, let’s assume that we always create the data structure for the demo application using the schema tool. In this case, the users table will be created after invoking the proper schema tool commands:

 

One-to-One Relationship, Bidirectional

One-to-One Relationship

In contrast to the unidirectional one-to-one relationship, in a bidirectional relationship two pointers exist: one pointing from object A to object B, and another pointing from object B to object A. It is important that we talk about two separate pointers here, as we will see in a minute. Let’s take a User entity again with a reference to a UserInfo entity:

<?php

namespace Entity;
/**
* @Entity
* @Table(name="users")
*/
class User
{
// [..]
/**
* @OneToOne(targetEntity="Entity\UserInfo")
*/
private $userInfo;
// [..]
}
If we now want the UserInfo entity to point back to the User entity, we need to extend the configuration of the User entity by adding inversedBy="user":
<?php
namespace Entity;
/**
* @Entity
* @Table(name="users")
*/
class User
{
// [..]
/**
* @OneToOne(targetEntity="Entity\UserInfo", inversedBy="user")
*/
private $userInfo;
// [..]
}

 

We configured the UserInfo with a pointer called $user to a UserInfo entity. The UserInfo entity itself looks like this:

<?php namespace Entity; /** * @Entity * @Table(name="user_info") */ class UserInfo { /** * @Id @Column(type="integer") * @GeneratedValue */ private $id; /** @Column(type="datetime", nullable=true) */ private $signUpDate; /** @Column(type="datetime", nullable=true) */ private $signOffDate=null; /** * @OneToOne(targetEntity="Entity\User", mappedBy="userInfo") */ private $user; public function setId($id) { $this->id = $id;

}
public function getId()
{
return $this->id;
}
public function setSignOffDate($signOffDate)
{
$this->signOffDate = $signOffDate;
}
public function getSignOffDate()
{
return $this->signOffDate;
}
public function setSignUpDate($signUpDate)
{
$this->signUpDate = $signUpDate;
}
public function getSignUpDate()
{
return $this->signUpDate;
}
public function setUser($user)
{
$this->user = $user;
}
public function getUser()
{
return $this->user;
}
}

 

In the UserInfo entity we define the target entity class via targetEntity="Entity\ User" and let Doctrine 2 know by adding mappedBy="contactData" that the User entity references back via contactData. So far, so good! We just established a bidirectional connection between two entities.

 

Let’s now talk about the inversedBy and mappedBy configuration.

For a moment, let’s image there exists a User entity and a UserInfo entity, belonging together and pointing to each other in a bidirectional manner: $userInfo points to a UserInfo instance and $user points to a User instance. If we now want to disconnect the two, we remove both pointers:

<?php // [..] $user=$em->getRepository('Entity\User')->findOneById($id);

$user->getUserInfo()->setUser(null);
$user->setUserInfo(null);
$em->flush();

 

But what if we remove only one of the two pointers? We would create an inconsistency. Which reference tells Doctrine 2 the truth about the two objects and their association?

 

One entity says “yes, we are connected,” the other says “no, we aren’t connected at all.” Remember that this problem exists only in the object oriented world, not in the relational database universe. In a relational database, references are always bidirectional. There is no concept of a unidirectional relationship.

 

Back to the inconsistency issue: to which entity should Doctrine 2 listen when taking care of persistence? The solution looks like this: when in doubt, Doctrine 2 listens to the entity which carries the inversedBy attribute. This means that the following code would not remove the association between the two entities:

<?php // [..] $user=$em->getRepository('Entity\User')->findOneById($id);

$user->getUserInfo()->setUser(null);
$em->flush();
In contrast, the following code would remove the association:
<?php // [..] $user=$em->getRepository('Entity\User')->findOneById($id);
$user->setUserInfo(null);
$em->flush();

The reason is that the User entity has the inversedBy attribute and acts as the so-called owning side of the connection. The owning side is the side Doctrine 2 checks to determine whether a connection exists. The other side, the so-called inverse side, doesn’t matter here. Doctrine 2 doesn’t care what the inverse side says.

 

However, even if we correctly cut the connection between the two from the owning side, this change is durable only after flushing:

<?php use Doctrine\ORM\EntityManager; // [..] $em=EntityManager::create($dbParams, $config); $em->flush();
Until flushing happens, we still have an inconsistency in the running program:
<?php $user=$em->getRepository('Entity\User')->findOneById($id);
$userInfo = $user->getUserInfo();
$user->setUserInfo(null);
var_dump($user->getUserInfo()); // NULL
var_dump($userInfo->getUser()); // object(Entity\User)
$em->flush();
var_dump($user->getUserInfo()); // NULL
var_dump($userInfo->getUser()); // NULL

 

Only once $em->flush() has been executed will var_dump($userInfo->getUser()) finally return NULL. Before that, it returns the referenced object. In some cases, this situation may lead to difficult-to-debug issues. A piece of good advice here is to always make sure that both references are removed simultaneously so that the running program stays intact:

 

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { // [..] public function removeUserInfo() { $this->userInfo->setUser(null);
$this->userInfo = null;
}
}
The following code now always deals with a consistent state:
<?php $user=$em->getRepository('Entity\User')->findOneById($id);
$userInfo = $user->getUserInfo();
$user->removeUserInfo();
var_dump($user->getUserInfo()); // NULL
var_dump($userInfo->getUser()); // NULL
$em->flush();
var_dump($user->getUserInfo()); // NULL
var_dump($userInfo->getUser()); // NULL

 

As a good practice, you should always add a persistence supporting method on the owning side.

Let’s recap: A bidirectional relationship always has an owning side and an inverse side. Unidirectional relationships have only an owning side, which also does not need to be declared explicitly. Regarding associations, only changes to the owning side are relevant for persistence. Doctrine 2 doesn’t care about the inverse side in this concern.

 

The data base table of the entity declared as the owning side holds the foreign key. Which side of the connection is defined as the owning side is up to the application developer. Given a bidirectional association, the owning side can be identified by spotting the inversedBy attribute, while the inverse side carries the mapped By attribute.

 

One-to-Many Relationship, Bidirectional

One-to-Many Relationship

In the blog “Hello, Doctrine 2!” we added a bidirectional one-to-many relationship to the demo app between a User and its Posts. Let’s take a close look at how we got that working. The User entity has a member variable called $posts:

<?php

namespace Entity;
/**
* @Entity
* @Table(name="users")
*/
class User
{
/**
* @OneToMany(targetEntity="Entity\Post", mappedBy="user")
*/
private $posts;
// [..]
}

The @OneToMany annotation defines the one-to-many relationship to the Post entity (targetEntity). As the mappedBy attribute is given, it’s a bidirectional relationship. The Post entity looks like this:

<?php
namespace Entity;
/**
* @Entity
* @Table(name="posts")
*/
class Post
{
/**
* @ManyToOne(targetEntity="Entity\User", inversedBy="posts")
*/
private $user;
// [..]
}

 

Here we find the counterpart annotation @ManyToOne. In a one-to-one relationship, the application developer can decide freely which side to declare as the owning side. That is not the case here. The entity carrying the @ManyToOne annotation must become the owning side and get the inversedBy attribute. If (as before) we use the schema tool to create the data structure based on the persistence configuration, the following tables are set up:

 

As we can see, table user_info holds the foreign key, which may occur more than once. When accessing a User’s Posts from a given User entity, Doctrine uses a Doctrine\ ORM\PersistentCollection to provide the referenced Post entities. It extends other classes such as \Countable, \IteratorAggregate and \ArrayAccess, which makes a Doctrine\ORM\PersistentCollection very similar to a regular PHP array, meaning it can easily be used, for example, in PHP foreach loops.

 

If the referenced Posts haven’t been accessed at least once, the member variable $user has the value null and has not yet been assigned an object of class Doctrine\ORM\ PersistentCollection. If one wants to work with the collection before it has been set up by Doctrine 2, this could be an issue. Therefore, as a good practice, a member variable holding a persistent association should always be initialized:

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { // [..] public function construct() { $this->posts = new \Doctrine\Common\Collections\
ArrayCollection();
}
// [..]
}

 

Many-to-Many Relationship, Unidirectional

many to many

In our demo app, one User may act in different Roles with different access rights. Let’s distinguish between base- and pro-users and administrators. User with the base Role can read Posts, but can’t write Posts. Pro-users can also write Posts. Administrators can manage the overall application and various settings.

 

We design the relationship so that, from given a User entity, we can access the User’s Roles, but not the other way around. To do so, we simply need to add the following persistence configuration:

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { // [..] /** * @ManyToMany(targetEntity="Entity\Role") **/ private $roles; // [..] } The Role entity definitions are straightforward as well: <?php namespace Entity; /** * @Entity * @Table(name="role") */ class Role { /** * @Id @Column(type="integer") * @GeneratedValue */ private $id; /** @Column(type="string") */ private $label; public function setId($id) { $this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setLabel($label)
{
$this->label = $label;
}
public function getLabel()
{
return $this->label;
}
}

 

Running the schema tool to create the data structure in the database, we now have a new table called role:

 

Many-to-Many Relationship, Bidirectional

The author of a Post (a User) should be able to assign one or more Tags. A Tag itself may be reused in different Posts. In contrast to the relationship between a User and its Roles, we design the association to be bidirectional. First, let’s set up the new Tag entity:

 

<?php namespace Entity; /** * @Entity * @Table(name="tag") */ class Tag { /** * @Id @Column(type="integer") * @GeneratedValue */ private $id; /** @Column(type="string") */ private $label; /** * @ManyToMany(targetEntity="Entity\Post", mappedBy="tags") */ private $posts; public function __construct() { $this->posts = new \Doctrine\Common\Collections\
ArrayCollection();
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setLabel($label)
{
$this->label = $label;
}
public function getLabel()
{
return $this->label;
}
public function setPosts($posts)
{
$this->posts = $posts;
}
public function getPosts()
{
return $this->posts;
}
}
The Post entity needs to be extended, as well, by a new member variable:
<?php namespace Entity; /** * @Entity * @Table(name="posts") */ class Post { // [..] /** * @ManyToMany(targetEntity="Entity\Tag", inversedBy="posts") */ private $tags; public function __construct() { $this->tags = new \Doctrine\Common\Collections\
ArrayCollection();
}
// [..]
}

 

When setting up a many-to-many relationship, you can freely choose the owning side. The schema tool, once more, creates the join table post_tag for us, if we run the proper commands:

 

One-to-Many Relationship, Unidirectional

One-to-Many Relationship

A User can set up multiple Category entities for its Posts. We will use a unidirectional association for this domain model aspect, which requires—in contrast to the bidirectional one-to-many relationship—a join table as well. Also, we need to use the @ManyToMany annotation, even though we don’t set up a many-to-many annotation (this might be somewhat confusing).

 

With the help of a unique constraint, it finally results in a one-to-many relationship. The User entity is extended by the $categories member variable:

 

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { // [..] /** * @ManyToMany(targetEntity="Entity\Category") * @JoinTable(name="users_categories", * joinColumns={@JoinColumn(name="user", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="category", * referencedColumnName="id", unique=true)} * ) */ private $categories; public function __construct() { // [..] $this->categories = new \Doctrine\Common\Collections\
ArrayCollection();
}
public function setCategories($categories)
{
$this->categories = $categories;
}
public function getCategories()
{
return $this->categories;
}
// [..]
}

 

The annotations and attributes used are already known from the many-to-many relationship. The unique=true configuration, which makes it a one-to-many relationship, is new. The Category entity is simple:

 

<?php namespace Entity; /** * @Entity * @Table(name="category") */ class Category { /** * @Id @Column(type="integer") * @GeneratedValue */ private $id; /** @Column(type="string") */ private $label; public function setId($id) { $this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setLabel($label)
{
$this->label = $label;
}
public function getLabel()
{
return $this->label;
}
}

 

Many-to-One Relationship, Unidirectional

Many-to-One Relationship

Multiple posts can be grouped in a Category, but each Post can be in only one Category.

To allow access to a Category from a Post, the Post must be extended:

<?php namespace Entity; /** * @Entity * @Table(name="posts") */ class Post { // [..] /** * @ManyToOne(targetEntity="Entity\Category") * @JoinColumn(name="category_id", referencedColumnName="id") **/ private $category; // [..] public function setCategory($category) { $this->category = $category;

}
public function getCategory()
{
return $this->category;
}
}

 

And that’s it—no more configuration is needed, as it’s a unidirectional association. The Category doesn’t need to be extended. In fact, even the @JoinColumn annotation is redundant, because its configuration is identical with Doctrine’s defaults.

 

One-to-One Relationship, Self-Referencing

One-to-One Relationship

Doctrine 2 allows us to define associations between entities of the same type, so-called self-referencing relations. In our demo app, a User can declare another User as its life partner. Both ends of the association allow us to access the referenced life partner. The persistence configuration needed for a self-referencing association looks like this:

 

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { /** * @OneToOne(targetEntity="Entity\User") **/ private $lifePartner; // [..] public function setLifePartner($lifePartner) { $this->lifePartner = $lifePartner;
}
public function getLifePartner()
{
return $this->lifePartner;
}
}

 

If the database structure is created by the schema tool, Doctrine 2 automatically adds a column called lifeParter_id to the users table to maintain the reference. Again, Doctrine’s defaults are at play here. If needed, you can add the @JoinColumn annotation and overwrite the defaults:

 

<?php
// [..]
/**
* @OneToOne(targetEntity="Entity\User")
* @JoinColumn(name="partner", referencedColumnName="id")
**/

private $lifePartner;

// [..]

 

One-to-Many Relationship, Self-Referencing

One-to-Many Relationship

With a self-referencing one-to-many relationship, a category tree can be built. A Category may have multiple child categories and one parent category (the root node won’t have a parent category). The persistence configuration for a self-referencing one-to-many relationship is similar to a regular bidirectional one-to-many relationship:

<?php namespace Entity; /** * @Entity * @Table(name="category") */ class Category { /** * @OneToMany(targetEntity="Entity\Category", mappedBy="parent") **/ private $children; /** * @ManyToOne(targetEntity="Entity\Category", inversedBy="children") * @JoinColumn(name="parent_id", referencedColumnName="id") **/ private $parent; // [..] public function __construct() { $this->children = new \Doctrine\Common\Collections\

ArrayCollection();
}
public function setParent($parent)
{
$this->parent = $parent;
}
public function getParent()
{
return $this->parent;
}
// [..]
}

 

The configuration shown above extends table category by column parent_id, if the already developed data structure is extended using Doctrine’s schema tool. The relationship is designed in a way that allows us to reach a parent and children from a given Category.

 

Many-to-many Relationship, Self-Referencing

Many-to-many self-referencing relationships can be defined as well. In our demo app, this type of a relationship is used to describe the social network of a User:

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { // [..] /** * @ManyToMany(targetEntity="Entity\User") * @JoinTable(name="friends", * joinColumns={@JoinColumn(name="user_id", * referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="friend_user_id", * referencedColumnName="id")} * ) **/ private $myFriends; public function __construct() { // [..] $this->myFriends = new \Doctrine\Common\Collections\

ArrayCollection();
}
// [..]
public function setMyFriends($myFriends)
{
$this->myFriends = $myFriends;
}
public function getMyFriends()
{
return $this->myFriends;
}
}

 

If the schema tool is applied, it will report an error here, because Doctrine 2 wants to label both columns of the join table with “user_id,” which is invalid. In this case, we will need to add the @JoinTable annotation to provide a different persistence configuration. Also, by default, the join table will be called users_users, if not defined otherwise.

 

In our case, we tell Doctrine 2 to call it friends, a more meaningful table name in our case. If you are dealing with an existing data structure, custom configuration would be necessary anyway.

 

Creating a New Entity

Once the domain model has been constructed, it is time to use it. A new entity can be created based simply on a new object of an entity class:

<?php $newPost=new \Entity\Post(); $newPost->setTitle('A new post!');

$newPost->setContent('This is the body of the new post.');
$em->persist($newPost);
$em->flush();

 

The code shown above anticipates that $em references a ready-to-use entity manager. First, a new Post is created, then data is assigned, and the object is passed to the entity manager for persistence.

 

One must remember that the persist() method call does not yet cause an SQL INSERT statement to be issued. The entity is only scheduled for persistence with the next flushing. As long as no flushing has taken place, the entity is in a state called MANAGED, meaning that the entity manager recognizes the new entity.

 

Only when the flush() method is invoked on the entity manager is a new record written to the database. Otherwise, the entity will be lost after the script has finished.

 

Loading an Existing Entity

There are two main ways to load an existing entity: either by querying and retrieving it from its corresponding repository or by accessing it through an association given by another, already loaded entity.

 

Using a Repository

Repository

We already learned that a repository is a container for all entities of a specific type. A repository provides finder methods to search for entities based on a query. While several finder methods are available out-of-the-box, custom finder methods can also be added later on. A custom finder method can be imagined as a “quick access” to a typical query. With the help of finder methods, you can look up an entity, for example by its ID, like this:

 

  • <?php
  • $post = $em->getRepository('Entity\Post')->findOneById($id);

First we request the repository from the entity manager and then we execute a finder method on it. By default, four finder methods are available. To find a single entity based on its ID, use the find() method:

  • <?php
  • public function find($id, $lockMode = LockMode::NONE, $lockVersion = null);
  • To find a single entity based on criteria, use the findOneBy() method:
  • <?php

public function findOneBy(array $criteria, array $orderBy = null); To find all entities of a specific type, use the findAll() method:

<?php
public function findAll();
To find multiple entities based on criteria, use the findBy() method:
<?php
public function findBy(
array $criteria,
array $orderBy = null,
$limit = null,
$offset = null
);

 

You can define the order, limit, and offset values when using findBy(). Also helpful are the so-called magic finders. They allow you to include search criteria directly in a finder method’s name. The next two statements produce the same result:

<?php


$tag = $em->getRepository('Entity\Tag')->findOneBy(array('label'=>$label));

$tag = $em->getRepository('Entity\Tag')->findOneByLabel($label);

 

Adding a custom repository with individual finder methods is a two-step process.

First, a new repository class needs to be set up:

<?php

namespace Repository;
use Doctrine\ORM\EntityRepository;
class Post extends EntityRepository
{
public function findAllPostsWithTag($tag)
{
// DQL statement goes here
}
}
Next, Doctrine 2 needs to know about the new repository. This is done through the corresponding entity:
<?php
namespace Entity;
/**
* @Entity(repositoryClass="Repository\Post")
* @Table(name="posts")
*/
class Post
{
// [..]
}

 

That’s all! The findAllPostsWithTag() finder method can now be easily invoked:

  • <?php
  • $posts = $em->getRepository('Entity\Post')->findAllPostsWithTag($tag);

We will learn more about DQL, the Doctrine Query Language, later in the blog. It is used to phrase a query.

 

Using an Association

Let’s assume we already have a loaded User entity available. Instead of loading the User's contact data by using its repository, we can also reach this entity from the given User entity:

<?php namespace Entity; /** * @Entity * @Table(name="users") */ class User { // [..] /** * @OneToOne(targetEntity="Entity\ContactData") */ private $contactData; // [..] public function getContactData() { return $this->contactData;

}
}

 

In this case, when calling getContactData(), the referenced ContactData entity is loaded on demand by Doctrine’s proxy mechanism.

We could even make sure that the referenced ContactData entity is already loaded when loading the User itself, and save an additional database query:

<?php


// [..]

/**

* @OneToOne(targetEntity="Entity\ContactData", fetch="EAGER")

*/

private $contactData;

By using fetch="EAGER," we tell Doctrine 2 to always load the referenced ContactData entity when the User itself is loaded.

 

Loading eagerly sometimes has its advantages, especially when it’s likely to access a referenced entity later in the process. If the fetch attribute is omitted, Doctrine 2 fetches LAZY by default. This means it loads the entity on first access. The third option is EXTRA_ LAZY, which is helpful for huge datasets.

 

Even if one decides to lazy load references, the referenced entities are still all loaded fully into RAM. Depending on the amount and size of the entities referenced, this could be a serious performance issue. When EXTRA_LAZY is used, several methods can be executed on the collection of referenced entities without fully loading them into the RAM right away. This is true for:

contains()

count()
offsetSet()
add()
count()
slice()

In this way, a pagination feature, for example, can be built without performance issues.

 

Changing an Existing Entity

Modifying an existing, already loaded entity is easy. All changes made to such an entity are auto-detected by Doctrine 2 when flushing the entity manager:

<?php // [..] $post=$em->getRepository('Entity\Post')->find(1);
$post->setTitle("New title");
$em->flush();

 

There is no need for explicitly telling Doctrine 2 again about the fact that this entity has been changed. The method persist($entity) does not need to be called on the entity manager again.

 

Removing an Entity

Removing an existing entity can easily be done through the entity manager, if a handle to a loaded entity is available:

  • <?php
  • $em->remove($post);
  • $em->flush();

The SQL statement needed to physically delete the record in the database is not issued as long as flush() has not yet been invoked.

 

Sorting an Association

When accessing entities via an association, the order of the entities retrieved is not defined. As a part of the entity mapping definitions, one can define the order of entities using the @OrderBy annotation. If we get back to our demo application, we could define the order of a User’s Posts in such a way that the most recent Post is shown first in its list of Posts, simply by modifying the mapping configuration in the User entity:

<?php
namespace Entity;
/**
* @Entity
* @Table(name="users")
*/
class User
{
// [..]
/**
* @OneToMany(targetEntity="Entity\Post", mappedBy="user")
* @OrderBy({"id" = "DESC"})
*/
private $posts;
// [..]
}

Alternatively, you can sort a collection usng PHP after retrieving the entities from the database.

 

Removing an Association

Removing an association is as straightforward as removing an entity:

<?php $newPost=new \Entity\Post(); $newPost->setTitle('A new post!');

$newPost->setContent('This is the body of the new post.');
$user = $em->getRepository('Entity\User')->findOneById(1);
$newPost->setUser($user);
$em->flush();
$newPost->setUser(null);
$em->flush();

 

In the example above, a new Post entity is created. Then, an existing User entity is loaded and associated with the new post. After flushing, the reference has been persisted to the database. Next, we remove the association again by setting User to null. After the next flushing, the reference is gone in the database.

 

We need to keep in mind here that we have established a bidirectional relationship between the User and its Posts, and the Post entity is the owning side of the association. If, for example, we would take the User entity and remove the Post from its collection $posts, nothing would happen on flushing:

<?php

// [..]


$user->getPosts()->removeElement($newPost);

$em->flush();

 

The removeElement() method, which is used to remove an element from a given Doctrine 2 collection based on an entity loaded, is without the desired effect. However, there is an effect. While the change won’t be persisted, the element has been removed from the collection in RAM. One won’t find the element anymore when looking it up in the collection.

 

Lifecycle Events

Lifecycle Events

When working with entities, several events are triggered by Doctrine 2:

  • preRemove: Occurs for a given entity before the respective EntityManager remove operation for that entity is executed.
  • postRemove: Occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations.
  • prePersist: Occurs for a given entity before the respective EntityManager persist operation for that entity is executed.
  • postPersist: Occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations.
  • preUpdate: Occurs before the database update operations to entity data.
  • postUpdate: Occurs after the database update operations to entity data.
  • postLoad: Occurs for an entity after the entity has been loaded from the database.

 

With loadClassMetadata, onFlush, and onClear, additional events are triggered that are persistence-related, but are not specific to a single entity. With these events available, you can hook into persistence processing, with a so-called lifecycle callback, which is implemented as a method of an entity class.

 

Let’s assume we want to add login data for each User to our demo application. While the username can be picked by the user, the password is auto-generated on signup. This can be achieved by adding a lifecycle callback to the User class:

 style="margin:0;width:957px;height:106px"><?php namespace Entity; /** * @Entity * @HasLifecycleCallbacks * @Table(name="users") */ class User { // [..] const GENERATED_PASSWORD_LENGTH=6; // [..] /** @PrePersist */ public function generatePassword() { for($i=1; $i <=self::GENERATED_PASSWORD_LENGTH; $i++) { $this->password .= chr(rand(65, 90)); // 65 ->
A, 90 -> Z
}
}
}

 

First, we need to declare that lifecycle callbacks are present by using the @ HasLifecycleCallbacks annotation. Then we add the lifecycle annotation @PrePersist to the generatePassword() method.That’s it! Now, before persisting a new entity, this method is called automatically, and the User’s password is auto-generated.

 

Cascading Operations

Cascading Operations

When creating a new entity or modifying an existing one, all operations by default affect only a single entity. A powerful, but somewhat dangerous, feature is the option to define “operation cascades.” Let’s consider the following example. If we delete a User in our demo application, we also want all of its Posts to be deleted. This can be achieved by adding the cascade attribute to the association definition:

<?php namespace Entity; /** * @Entity * @HasLifecycleCallbacks * @Table(name="users") */ class User { // [..] /** * @OneToMany(targetEntity="Entity\Post", mappedBy="user", cascade={"remove"}) */ private $posts; // [..] } Now, when removing a user via <?php $user=$em->getRepository('Entity\User')->find($id);
$em->remove($user);
$em->flush();

 

the User is gone, and so are all its Posts. When setting cascade to value all, the cascade will be applied on other operations, such as persist, as well.

 

When adding the cascade attribute, the side matters. In the code shown above, all referenced Post entities are removed when a User is removed. When adding cascade to the Post entity as shown in the following code, the User entity will be removed if one of its referenced Post entities is deleted:

<?php

namespace Entity;
/**
* @Entity(repositoryClass="Repository\Post")
* @Table(name="posts")
*/
class Post
{
// [..]
/**
* @ManyToOne(targetEntity="Entity\User", inversedBy="posts", cascade={"remove"})
* @JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
// [..]
}

 

Assuming that this is not the desired behavior, it is absolutely crucial to verify the cascade configuration to prevent data loss.Another way to achieve automatic deletion of referenced entities as shown above is orphan removal for one-to-one and one-to-many associations. Orphan removal means Doctrine 2 will automatically remove referenced entities without a parent entity:

/**

* @Entity
* @HasLifecycleCallbacks
* @Table(name="users")
*/
class User
{
/**
* @OneToMany(targetEntity="Entity\Post", mappedBy="user", orphanRemoval=true)
private $posts;
// [..]
}

 

Again, when the User is gone, all of its Posts are also gone. While cascading operation are useful, they can be expensive. The reason is that all operations on the referenced entities happen in RAM. The entities must first be loaded and reconstructed from the database, and then modified. Depending on the size of the collection, this could be resource intensive.

 

Luckily, Doctrine 2 also offers “database level” cascading operations for updates and deletes via the @JoinColumn annotation:

<?php

namespace Entity;
/**
* @Entity(repositoryClass="Repository\Post")
* @Table(name="posts")
*/
class Post
{
/**
* @ManyToOne(targetEntity="Entity\User", inversedBy="posts")
* @JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
// [..]
}

 

Transactions

Transactions

A transaction is an atomic unit of one or more database statements. All insert, update, or delete operations done through the entity manager are queued, as long as the flush() method has been called on the entity manager. Technically speaking, the queue is an implementation of the so-called unit of work1 pattern.

 

When calling flush(), all queued operations in the unit of work are fired against the database as a single transaction. If one of these operations fails Doctrine 2 automatically rolls back the entire transaction—that is, all operations queued—and then quits, itself, to prevent data loss due to inconsistencies.

 

P of EAA: Unit of Work

Doctrine 2 offers a convenient way to wrap several database operations into a single transaction. The following code demonstrates how to “reset” a User in the demo application. First, the existing User is deleted, then a new one is created by using its current first and last name:

<?php // [..] $em->transactional(function($em) {

$oldUser = $em->getRepository('Entity\User')->find(1);
$newUser = new Entity\User();
$newUser->setFirstName($oldUser->getFirstname());
$newUser->setLastName($oldUser->getLastname());
$em->persist($newUser);
$em->remove($oldUser);
});

 

Both operations take effect only if no exception was thrown for either of them. Otherwise, both operations are rolled back.

Another issue may arise when two or more persons simultaneously work on the same sets of data, which is not unlikely for web applications. Doctrine 2 fully supports a strategy called optimistic locking. The core idea behind optimistic locking is that multiple users can all read data sets, however, whenever changing data, only the first person writing is privileged to persist its changes.

 

All other users, now working with an outdated version of the entity, will get an exception when trying to persist their individual changes. As this strategy allows for concurrent reading operations and controls only write operations, read-intensive applications won’t be slowed down, compared to a pessimistic locking strategy.

 

To make optimistic locking happen, Doctrine 2 allows us to add a special integer or datetime version field to an entity. The current value of this field is compared to the value loaded before, and if it doesn’t match on write, an OptimisticLockException is thrown.

If this happens, another user must have already modified the entity. A version field can be set up by adding the @Version annotation:

<?php

namespace Entity;
/**
* @Entity(repositoryClass="Repository\Post")
* @Table(name="posts")
*/
class Post
{
// [..]
/** @Version @Column(type="integer") */
protected $version;
// [..]
}

 

When creating the corresponding data schema via Doctrine 2, a column called version is added to the posts table. For every new Post entity, Doctrine 2 automatically assigns a value of 1. The value is increased by one with each subsequent modification:

$post = $em->getRepository('Entity\Post')->find(1);

$post->setTitle("New title");
$em->flush();
$em->clear();
$post = $em->getRepository('Entity\Post')->find(1);
$post->setTitle("Again, a new title");
$em->flush();

 

The code shown above modifies the Post entity two times with an ID value of 1. After both changes have been applied, the value column has a value of 3.

 

The $em->clear() statement is important here: if we omit it, we would produce an OptimisticLockException ourselves. The reason is that changing the title to “New title” and flushing the entity manager increments the version value for this entity by one in the database. However, the value stored in the Post entity object in RAM still has the old value of 1, and therefore is now outdated—it is not being updated to the latest version value automatically.

 

Therefore, trying to modifying the title again will fail with an OptimisticLockException. The same will be true if we try to persist changes to an entity which has been modified in the meantime by somebody else. How an OptimisticLockException situation is handled is fully up to the application developer.

 

Doctrine Query Language

Earlier in this blog, we learned about repositories, containers for entities of a specific type. They are used to look up entities by specific criteria, do updates or deletes, and so on. They do their work with the help of finder methods, which are implemented using Doctrine’s own entity query language DQL, the Doctrine Query Language. Strictly speaking, DQL is not bound to finder methods or repositories; however, it is usually good practice to put all DQL statements there, just to keep things organized.

 

DQL itself is a language to query entities. It looks much like SQL, which makes learning DQL easier, but it isn’t SQL. While DQL statements can be written as a string like this

<?php // [..] $query=$em->createQuery(

'SELECT u FROM Entity\User u WHERE u.lastName = "Mustermann"'
);
$users = $query->getResult();
it is much more convenient to use the query builder, especially when constructing dynamic queries:
<?php // [..] $qb=$this->_em->createQueryBuilder();
$qb->select('u')
->from('Entity\User', 'u')
->where($qb->expr()->eq('u.lastName', '?1'))
->setParameter(1, "Mustermann");
$users = $qb->getQuery()->getResult();

 

We assume that $this->_em holds a reference to the entity manager (which is true for every repository that extends Doctrine\ORM\EntityRepository). The entity manager is capable of providing a query builder, which in turn can be used to programmatically construct a query. Thanks to its fluent interface, the code looks pretty elegant. In contrast to the first example, we also utilize value parameters and the Expr class, which we will look at in detail in a minute.

 

Retrieving Results

Retrieving Results

When executing a query, multiple options exist for retrieving results. When calling getResult() on a query object, a PHP array is returned containing all matching entity objects. Alternatively, getArrayResult() can be used to get all data in the form of an array. No objects are returned, only all entities’ data as an array in a container array.

 

This is useful when dealing with large datasets or for simple display tasks, where no objects are needed in the processing. The method getScalarResult() returns a similar result, but fully flat, not nested at all. When a single result is desired, calling getSingleResult() or getSingleScalarResult() will do. Method getOneOrNullResult() may be used if null is desired when no match was found.

 

If a query includes objects as well as scalar values as well, the result set returned is called “mixed”:

<?php // [..] $qb=$this->_em->createQueryBuilder();
$qb->select('u')
->addSelect($qb->expr()->concat('u.firstName', 'u.lastName'))
->from('Entity\User', 'u')
->where($qb->expr()->eq('u.lastName', '?1'))
->setParameter(1, "Mustermann");
$users = $qb->getQuery()->getResult();
In this query, we not only retrieve entities, but also concatenate the user’s first and last names. The result of the query looks like this:
array
[0]
[0] => Object
[1] => "Max Mustermann"
[1]
// ..

 

The result set can be limited, via setFirstResult($offset) and setMaxResults($limit), as when building a pagination feature. Another feature Doctrine 2 offers is retrieving partial objects, entities which have been only partially recreated from the database. To retrieve a partial object, a special syntax is required:

 

<?php // [..] $qb=$this->_em->createQueryBuilder();
$qb->select('partial u.{id, firstName}')
->from('Entity\User', 'u')
->where($qb->expr()->eq('u.lastName', '?1'))
->setParameter(1, "Mustermann");
$users = $qb->getQuery()->getResult();
This way, we get back partly reconstituted User objects from the database. When omitting the partial syntax and simply stating individual fields, the result is a plain array without objects:
array(1) {
[0]=>
array(2) {
["id"]=> int(1)
["firstName"]=> string(3) "Max"
}
}

 

It’s important to always include the identifier (id in this case) in a partial object definition. Otherwise an exception is thrown.

While Partial objects can be very helpful, such as while tweaking the performance of an app, they can be problematic as well. Code dealing with partial objects needs to be aware of the fact that no “real” entities are returned, and certain fields or associations might not be available. Use partial objects with care.

 

Constructing Basic Queries

The query builder provides methods for the different parts of a query, such as the one shown above:

public function select($select = null);
public function delete($delete = null, $alias = null);
public function update($update = null, $alias = null);
public function set($key, $value);
public function from($from, $alias = null);
public function where($where);
public function andWhere($where);
public function orWhere($where);
public function groupBy($groupBy);
public function addGroupBy($groupBy);
public function having($having);
public function andHaving($having);
public function orHaving($having);
public function orderBy($sort, $order = null);
public function addOrderBy($sort, $order = null);

 

Expressions like the ones shown above are built using an Expr object, which is provided by the query builder when calling its expr() method. The Expr object provides several methods with which to construct an expression:

public function andX($x = null);

 

Expressions are used in the SELECT, WHERE, HAVING or GROUP part of a query. However, the query shown above can also be created without using the Expr class:

<?php $qb=$this->_em->createQueryBuilder();
$qb->select('u')
->addSelect("CONCAT(u.firstName, u.lastName)")
->from('Entity\User', 'u')
->where('u.lastName = ?1')
->setParameter(1, "Mustermann");
$users = $qb->getQuery()->getResult();

 

This might be necessary sometimes, since not every function or arithmetic operator can be constructed via the Expr class. The following aggregate functions are allowed in SELECT and GROUP BY clauses:

AVG

COUNT
MIN
MAX
SUM
The following functions are supported in SELECT, WHERE, and HAVING clauses:
IDENTITY
ABS(arithmetic_expression)
CONCAT(str1, str2)
CURRENT_DATE()
CURRENT_TIME()
CURRENT_TIMESTAMP()
LENGTH(str)
LOCATE(needle, haystack [, offset])
LOWER(str)
MOD(a, b)
SIZE(collection)
SQRT(q)
SUBSTRING(str, start [, length])
TRIM([LEADING | TRAILING | BOTH] [“trchar” FROM] str)
UPPER(str)
DATE_ADD(date, days, unit)
DATE_SUB(date, days, unit)
DATE_DIFF(date1, date2)

 

Values can be given for placeholders within queries via setParameter() or setParameters($array). In the example shown above, number placeholders are used (starting with a “?” symbol). Alternatively, a string placeholder may be used (starting with a “:” symbol):

<?php // [..] $qb=$this->_em->createQueryBuilder();
$qb->select('u')
->addSelect($qb->expr()->concat('u.firstName', 'u.lastName'))
->from('Entity\User', 'u')
->where($qb->expr()->eq('u.lastName', ':lastName'))
->setParameter("lastName", "Mustermann");
$users = $qb->getQuery()->getResult();

 

Whichever way is preferred, one needs to stick to it within a query. Mixing is not allowed.

 

Constructing Join Queries

Doctrine 2 supports two different types of joins. While regular joins are needed, for example, to limit results via a WHERE clause, so-called fetch joins are used to fetch related entities for further usage:

<?php // [..] $qb=$this->_em->createQueryBuilder();

$qb->select('u', 'c')
->from('Entity\User', 'u')
->leftJoin('u.contactData', 'c')
->where($qb->expr()->eq('u.lastName', '?1'))
->setParameter(1, "Mustermann");
$users = $qb->getQuery()->getResult();

 

The select('u', 'c') makes the join a fetch join. The referenced ContactData object is part of the result set. However, when omitting the 'c' in the select clause, the ContactData object is not part of the result anymore, however, it can still be used, for example, within the where clause:

<?php // [..] $qb=$this->_em->createQueryBuilder();

$qb->select('u')
->from('Entity\User', 'u')
->leftJoin('u.contactData', 'c')
->where($qb->expr()->eq('u.lastName', '?1'))
->andWhere($qb->expr()->eq('c.email', '?2'))
->setParameter(1, "Mustermann")
->setParameter(2, "max.mustermann@example.com");
$users = $qb->getQuery()->getResult();

 

Command Line Tools

Command Line Tools

Doctrine 2 ships with powerful command line support. The command line tools can broadly be divided into those that support a database abstraction layer (DBAL-related operations) and those that provide object-relational mapping (ORM-related operations). A list of all commands available can be printed via the list command:

 

$ ./doctrine list

The help command, or the command parameter --help in conjunction with other commands, prints the instructions for a given command. For example, if we need help dealing with the orm:info command, we can ask for help like this:

  • $ ./doctrine orm:info --help This will also work:
  • $ ./doctrine help orm:info

Both commands print the identical output to the console.

 

Setting Up the Command Line Tools

Before Doctrine 2 can be used on the command line, some basic configuration code is needed. Like the application itself, the command line tools require a ready-to-go database connection and entity manager if ORM-related commands are needed.

 

The configuration of the command line tools first looks somewhat strange. Doctrine 2 requires that a file called cli-config.php exists within the folder in which the tools are executed on the command line. If Doctrine 2 was installed using Composer, the command line tools are located in vendor/bin. Therefore, this is the place where a file called cli-config.php needs to be set up with the following basic code:

<?php include '../../vendor/autoload.php'; use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\EntityManager; $paths=array(__DIR__ . "/../../entity/"); $isDevMode=true; $dbParams=array( 'driver'=> 'pdo_mysql',

'user' => 'root',
'password' => '',
'dbname' => 'app',
);
$config = Setup::createAnnotationMetadataConfiguration($paths,$isDevMode);
$em = EntityManager::create($dbParams, $config);
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper(
$em->getConnection()
),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper(
$em
)
));

 

The code shown above is similar to the index.php file used in the demo app. First, autoloading is configured, then the entity manager is instantiated. If both clients, the web application and the command line tools, share the same credentials, externalizing this data may be helpful.

 

When the command line tools are invoked, Doctrine 2 automatically includes the cli-config.php file and also looks for a so-called helper set defined in the global namespace. The helper set provides the command line tools with the database connection via key db (needed for the DBAL commands) as well as the entity manager via key em (needed for the ORM commands).

 

DBAL Commands

DBAL Commands

Execute an SQL Statement

SQL commands can easily be executed via the command line with Doctrine’s command line tooling. The command dbal:run-sql requires a single parameter, a valid SQL statement:

$ ./doctrine dbal:run-sql "SELECT * FROM users;" The result of the query is printed to the console.

 

Import SQL Files

If you need to execute multiple statements, importing an SQL file via the./doctrine dbal:import command might be a great option. It takes one or more paths to SQL files, delimited by space:

$ ./doctrine dbal:import /tmp/import-data.sql

 

ORM Commands

Validate Persistence Configuration

One of the most helpful commands is orm:validate-schema, which validates the current persistence configuration and makes sure that it matches the existing data schema in the database. If all is good, the following command prints a positive message to the console:

  • $ ./doctrine orm:validate-schema
  • > [Mapping] OK - The mapping files are correct.
  • > [Database] OK - The database schema is in sync with the mapping files.

 

The command orm:info prints an overview of the application entities known to Doctrine:

  • $ ./doctrine orm:info
  • > Found 2 mapped entities:
  • > [OK] Entity\Post
  • > [OK] Entity\User

 

The Schema Tool

With the help of the commands

  • \ 1.\ orm:schema-tool:create,
  • \ 2.\ orm:schema-tool:drop
  • \3.\ orm:schema-tool:update

 

one can manipulate a database data schema based on the entity persistence configuration. However, when running these commands, nothing actually happens—it’s just a “dry run”. If the commands are executed with parameter --dump-sql, again, only a dry run occurs. However, this time, the schema tool prints all SQL statements, which would otherwise have been fired against the database, to the screen. Only if one uses the parameter --force, does the schema tool finally execute the statements:

 

$ ./doctrine orm:schema-tool:drop --force

The command shown above makes all tables and data disappear:

  • > Dropping database schema...
  • > Database schema dropped successfully!
  • Danger! Potential loss of data! The schema tool can delete tables and/or data.

Use these commands with caution.

 

The execution of

$ ./doctrine orm:schema-tool:create creates the data structure from scratch, while

$ ./doctrine orm:schema-tool:update

migrates an existing data schema from status quo to match the current persistence configuration, if the existing data schema is not yet up-to-date.

 

Generate Commands

Generate Commands

With the help of the command orm:generate-proxies, the proxy classes for the entities defined can be created, which otherwise are created by Doctrine 2 automatically when needed. Via the command orm:generate-entities, the entity classes themselves can be generated; this is especially helpful if the persistence configuration is given via XML or YAML. Executing the command

 

$ ./doctrine orm:generate-entities /tmp

creates the entity classes in /tmp, or to be precise, in /tmp/Entity. More options for the command orm:generate-entities can be identified running the following command:

$ ./doctrine orm:generate-entities --help

With the help of the command orm:generate-repositories, repository classes can also be auto-generated.

 

Execute a DQL Command

Similar to dbal:run-sql, DQL statements can also easily be executed using the command orm:run-dql.

Cache-Related Commands

With the commands

  • \ 1.\ orm:clear-cache:metadata,
  • \ 2.\ orm:clear-cache:query
  • \3.\ orm:clear-cache:result

the various Doctrine caches can be purged. These commands are especially helpful when deploying a new application version. They make sure that no outdated configuration or data is used in conjunction with a new application version—usually, this would lead to unrecoverable errors.

 

Converting Commands

Rarely used, but very helpful, are the commands orm:convert-d1-schema and orm:convert-mapping. The command orm:convert-d1-schema is used to transform the old Doctrine 1 persistence configuration format to the one used by Doctrine 2, which is very handy when upgrading an existing application. The command orm:convert-mapping, on the other hand, allows you to go from one Doctrine 2 mapping format to another, such as from XML to YAML.

 

Production-Ready Configuration

The following command validates that Doctrine’s configuration is ready for production usage:

$ ./doctrine orm:ensure-production-settings

If this is the case, it prints a positive message on the console:

> Environment is correctly configured for production

 

Many configuration aspects can cause a negative result. This could be missing proxy classes created by running the proper command or missing caches. Both are considered by Doctrine 2 to be a requirement for usage in productive systems.

 

Custom commands Doctrine 2 allows you to develop custom commands that can be hooked into the command line tooling. More information about this topic can be found in the official documentation.

 

Introduction to ORM Cache Types

Due to its nature and the way Doctrine 2 works, applications using Doctrine 2 naturally run a bit slower than others. However, with good caching strategies applied, this issue can be almost completely eliminated. Doctrine 2 ORM brings native support for three different types of caches: the “meta cache,” the “query cache,” and the “result cache.”

 

When setting up the entity manager, the different caches can be added to the entity manager’s configuration. In addition, the cache instance might be used for custom values as well.

 

Caching Backends

Cached data can be stored in different so-called caching backends. Doctrine 2 supports multiple technologies to be used as caching backends:

ApcCache (requires ext/apc)
ArrayCache (in memory, lifetime of the request)
FilesystemCache (not optimal for high concurrency)
MemcacheCache (requires ext/memcache)
MemcachedCache (requires ext/memcached)
PhpFileCache (not optimal for high concurrency)
RedisCache.php (requires ext/phpredis)
WinCacheCache.php (requires ext/wincache)
XcacheCache.php (requires ext/xcache)
ZendDataCache.php (requires Zend Server Platform)

 

Based on the situation or technologies used, you can choose between the different technologies.

 

Metadata Cache

The metadata cache holds the entity mapping data given as annotations or external XML or YAML files. Caching this data means that Doctrine 2 does not need to perform reflection or XML or YAML parsing for every single request, which saves a significant amount of processing time.

When setting up the entity manager, the cache can easily be configured:

 

<?php // [..] $paths=array(__DIR__ . "/../class='lazy' data-src/Entity/"); $isDevMode=false; $dbParams=array( 'driver'=> 'pdo_mysql',

'user' => 'root',
'password' => '',
'dbname' => 'app',
);
$config = Setup::createAnnotationMetadataConfiguration(
$paths, $isDevMode
);
$config->setMetadataCacheImpl(
new \Doctrine\Common\Cache\FilesystemCache('/tmp/doctrine2')
);
$em = EntityManager::create($dbParams, $config);

 

In the configuration shown above, we tell Doctrine 2 to cache metadata locally in the filesystem. We could use other caching backends here as well. The caching path needs to be given when using the FilesystemCache. Next time, when entities are processed, Doctrine 2 starts setting up a somewhat cryptic files and folders structure in the given caching path.

 

Luckily, you don’t need to care about it—Doctrine 2 takes care of all things caching. We only need to make sure that the caching path given is writable to Doctrine. When caching via Memcached or Redis, these services need to be up and running and accessible to Doctrine 2, as well. If not, an exception will be raised.

 

Query Cache

The query cache ensures that DQL statements need to be translated into SQL only once. This again speeds up a Doctrine 2 application significantly:

<?php // [..] $paths=array(__DIR__ . "/../class='lazy' data-src/Entity/"); $isDevMode=false; $dbParams=array( 'driver'=> 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'app',
);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$cachingBackend = new \Doctrine\Common\Cache\FilesystemCache('/tmp/ doctrine2');
$config->setMetadataCacheImpl($cachingBackend);
$config->setQueryCacheImpl($cachingBackend);
$em = EntityManager::create($dbParams, $config);

 

In the configuration shown above, we first set up a general caching backend, which now powers both the metadata cache and the query cache.

 

Result Cache

Last but not least, there is the result cache. The use of a result cache prevents executing the same queries against the database again and again:

 <?php // [..] $paths=array(__DIR__ . "/../class='lazy' data-src/Entity/"); $isDevMode=false; $dbParams=array( 'driver'=> 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'app',
);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$cachingBackend = new \Doctrine\Common\Cache\FilesystemCache('/tmp/ doctrine2');
$config->setMetadataCacheImpl($cachingBackend);
$config->setQueryCacheImpl($cachingBackend);
$config->setResultCacheImpl($cachingBackend);
$em = EntityManager::create($dbParams, $config);

 

Via method setResultCacheImpl(), the result cache now is ready for action. In contrast to the two other caching types, you have to actively tell Doctrine 2 to cache results for a given query:

<?php

// [..]

$query = $em->createQuery($dqlString);

$query->useResultCache(true);

Now, the result is cached.

 

Framework Integrations

Framework Integrations

If one of the popular PHP frameworks is used for an application, integration of Doctrine mostly can be easily done. As an example, we will walk through the process of integrating Doctrine into a Zend Framework 2 application. If you are using a different framework, the official framework documentation or a quick web search usually brings up a suited tutorial.

 

The easiest way to use to Doctrine 2 with Zend Framework 2 (ZF2) is a Composer based installation. The ZF2 module doctrine-orm-module1 not only ships the glue code to make both libraries work together, but also ensures that the Doctrine 2 library itself is downloaded and installed in an existing ZF2 application. One simply needs to add the following line to the require block of the projects’ composer.json file:

 

"doctrine/doctrine-orm-module": "dev-master"

You must also add the following line above the require block:

"minimum-stability": "alpha"

If this configuration is missing, Composer might refuse to install the module. In fact, with this configuration, we tell Composer to install even non-stable modules.

 

The following commands start the download and the installation process:

$ php composer.phar update

 

As you can see, a whole bunch of other ZF2 modules and additional libraries are downloaded. Last but not least, two ZF2 modules must be activated via the application. config.php:

<?php return array( 'modules'=> array(
'Application',
doctrine/DoctrineORMModule
'DoctrineModule',
'DoctrineORMModule'
),
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
'module_paths' => array(
'./module',
'./vendor',
),
),
);

 

The modules register several services in Main Service Manager, all starting with the label “doctrine,” such as doctrine.cache.apc and doctrine.sqlloggercollector. ormdefault.

 

The entity manager can be obtained using a long-winded label:

<?php

// [..]

$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');

 

But before this will work, some additional configuration is needed. Doctrine 2 needs to know where the entity classes are located and what the caching strategy looks like. The following example from the module.php tells Doctrine that the mappings are given as annotation, the entities are located in __DIR__ . '/../class='lazy' data-src/Application /Entity', and caching takes place via PHP arrays:

 

<?php // [..] 'doctrine'=> array(
'driver' => array(
'my_annotation_driver' => array(
'class' => 'Doctrine\ORM\Mapping\Driver\
AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../class='lazy' data-src/Application/
Entity')
)
)
)
// [..]

 

To allow other ZF2 modules to provide entities to the application, a so-called Driver Chain allows us to combine multiple entity sources even with different mapping formats and caching strategies:

 

<?php // [..] 'doctrine'=> array(

'driver' => array(
'my_annotation_driver' => array(
'class' => 'Doctrine\ORM\Mapping\Driver\
AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../class='lazy' data-src/
Application/Entity')
),
'orm_default' => array(
'drivers' => array(
'Application\Entity' => 'my_annotation_driver'
)
)
)
)
// [..]
Now the database connection must be configured. Usually, a dedicated config file in the autoload folder is used, e.g. db.local.php:
<?php return array( 'doctrine'=> array(
'connection' => array(
'orm_default' => array(
'driverClass' => 'Doctrine\DBAL\Driver\
PDOMySql\Driver',
'params' => array(
'host' => 'localhost',
'port' => '3306',
'user' => 'username',
'password' => 'password',
'dbname' => 'database',
)
)
)
),
);
Once done, we can start dealing with entities (e.g. a Product entity) through Doctrine 2:
<?php namespace Application\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="product") */ class Product { /** * @ORM\Id @ORM\Column(type="integer") * @ORM\GeneratedValue */ protected $productId; /** @ORM\Column(type="string", nullable=true) */ protected $name; /** @ORM\Column(type="integer") */ protected $stock; /** @ORM\Column(type="string", nullable=true) */ protected $description; /** @ORM\Column(type="string", nullable=true) */ protected $features; public function setDescription($description) { $this->description = $description;
}
public function getDescription()
{
return $this->description;
}
public function setFeatures($features)
{
$this->features = $features;
}
public function getFeatures()
{
return $this->features;
}
public function setProductId($productId)
{
$this->productId = $productId;
}
public function getProductId()
{
return $this->productId;
}
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function setStock($stock)
{
$this->stock = $stock;
}
public function getStock()
{
return $this->stock;
}
}
From a controller, a finder method provided by a repository can be accessed like this:
<?php // [..] $this->getServiceLocator()->get('doctrine.entitymanager.orm_default')
->getRepository('Application\Entity\Product')
->findOneByProductId($id);
// [..]

 

The Doctrine 2 command line tools are available as well. You simply bring up a command line and change to the project’s root folder:

$ php vendor/bin/doctrine-module orm:validate-schema

 

Native SQL Statements

Doctrine 2 ships with a special class called NativeQuery that allows you to execute native SQL select statements and to map the results returned to entity objects. The same operations that work out-of-the-box with Doctrine 2 can be implemented by hand in cases where native queries are needed or where they are the better solution to a problem.

 

NativeQuery allows you to retrieve “raw data” and then subsequently work with entity objects. The official documentation2 holds further information about how to properly implement native SQL statements. Lastly, in general, one needs to remember that it’s always possible to execute arbitrary SQL statements via the underlying database connection:

<?php

// [..]

$em->getConnection()->exec('DELETE FROM posts');

 

While this is possible, it should always be the last resort. It bypasses the Entity Manager and might produce hard-to-debug issues and data inconsistencies.

 

Doctrine 2 Extensions

While Doctrine 2 already ships with tons and tons of features, there is even more. In the Doctrine 2 extension repository3 on GitHub, you will find several extensions with solutions to typical problems which otherwise must be solved individually by each application developer again and again:

  • Tree: Automates the tree handling process and adds some tree-specific functions on repositories.
  • Translatable: Gives a very handy solution for translating records into different languages.
  • Sluggable: Takes a specified field from an entity and makes it compatible for URLs.

 

  • Timestampable: Updates date fields on creates, updates, and even property changes.
  • Blameable: Updates string or reference fields on creates, updates, and even property changes with a string or object (e.g. user).
  • Loggable: Helps tracking changes and history of objects, also supports version management.

 

  • Sortable: Makes any entity sortable.
  • Translator: Supports handling translations.
  • Softdeleteable: Allows you to mark entities as deleted, without physically deleting them.

 

  • Uploadable: Provides file upload handling to entity fields.
  • References: Supports linking entities in Documents and vice versa.