1
0
mirror of synced 2025-01-17 22:11:41 +03:00

Merge ReST branch into master, ByeBye Markdown.

This commit is contained in:
beberlei 2010-12-11 12:31:31 +01:00
commit 5d58d9171e
76 changed files with 12830 additions and 8408 deletions

14
convert.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
FILES=`find -iname *.txt -print`
for FILE in $FILES
do
# replace the + to # chars
sed -i -r 's/^([+]{4})\s/#### /' $FILE
sed -i -r 's/^([+]{3})\s/### /' $FILE
sed -i -r 's/^([+]{2})\s/## /' $FILE
sed -i -r 's/^([+]{1})\s/# /' $FILE
sed -i -r 's/(\[php\])/<?php/' $FILE
# convert markdown to reStructured Text
pandoc -f markdown -t rst $FILE > ${FILE%.txt}.rst
done

View File

@ -1,11 +0,0 @@
+ Getting Started XML-Edition
+ Implementing ArrayAccess for domain objects
+ Implementing the NOTIFY change-tracking policy
+ Validation of Entities
+ Implementing wakeup or clone
+ Integrating with CodeIgniter
+ DQL Custom Walkers
+ DQL User Defined Functions
+ SQL Table Prefixes
+ Strategy Cookbook Introduction
+ Aggregate Fields

View File

@ -1,318 +0,0 @@
# Aggregate Fields
You will often come across the requirement to display aggregate values of data that
can be computed by using the MIN, MAX, COUNT or SUM SQL functions. For any ORM this is a tricky issue traditionally. Doctrine 2 offers several ways to get access to these values and this article will describe all of them from different perspectives.
You will see that aggregate fields can become very explicit
features in your domain model and how this potentially complex business rules
can be easily tested.
## An example model
Say you want to model a bank account and all their entries. Entries
into the account can either be of positive or negative money values.
Each account has a credit limit and the account is never allowed
to have a balance below that value.
For simplicity we live in a world were money is composed of integers
only. Also we omit the receiver/sender name, stated reason for transfer
and the execution date. These all would have to be added on the `Entry`
object.
Our entities look like:
[php]
namespace Bank\Entities;
/**
* @Entity
*/
class Account
{
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/** @Column(type="string", unique=true) */
private $no;
/**
* @OneToMany(targetEntity="Entry", mappedBy="entries", cascade={"persist"})
*/
private $entries;
/**
* @Column(type="integer")
*/
private $maxCredit = 0;
public function __construct($no, $maxCredit = 0)
{
$this->no = $no;
$this->maxCredit = $maxCredit;
$this->entries = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/**
* @Entity
*/
class Entry
{
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/**
* @ManyToOne(targetEntity="Account", inversedBy="entries")
*/
private $account;
/**
* @Column(type="integer")
*/
private $amount;
public function __construct($account, $amount)
{
$this->account = $account;
$this->amount = $amount;
// more stuff here, from/to whom, stated reason, execution date and such
}
public function getAmount()
{
return $this->amount;
}
}
## Using DQL
The Doctrine Query Language allows you to select for aggregate values computed from
fields of your Domain Model. You can select the current balance of your account by
calling:
[php]
$dql = "SELECT SUM(e.amount) AS balance FROM Bank\Entities\Entry e " .
"WHERE e.account = ?1";
$balance = $em->createQuery($dql)
->setParameter(1, $myAccountId)
->getSingleScalarResult();
The `$em` variable in this (and forthcoming) example holds the Doctrine `EntityManager`.
We create a query for the SUM of all amounts (negative amounts are withdraws) and
retrieve them as a single scalar result, essentially return only the first column
of the first row.
This approach is simple and powerful, however it has a serious drawback. We have
to execute a specific query for the balance whenever we need it.
To implement a powerful domain model we would rather have access to the balance from
our `Account` entity during all times (even if the Account was not persisted
in the database before!).
Also an additional requirement is the max credit per `Account` rule.
We cannot reliably enforce this rule in our `Account` entity with the DQL retrieval
of the balance. There are many different ways to retrieve accounts. We cannot
guarantee that we can execute the aggregation query for all these use-cases,
let alone that a userland programmer checks this balance against newly added
entries.
## Using your Domain Model
`Account` and all the `Entry` instances are connected through a collection,
which means we can compute this value at runtime:
[php]
class Account
{
// .. previous code
public function getBalance()
{
$balance = 0;
foreach ($this->entries AS $entry) {
$balance += $entry->getAmount();
}
return $balance;
}
}
Now we can always call `Account::getBalance()` to access the current account balance.
To enforce the max credit rule we have to implement the "Aggregate Root" pattern as
described in Eric Evans book on Domain Driven Design. Described with one sentence,
an aggregate root controls the instance creation, access and manipulation of its children.
In our case we want to enforce that new entries can only added to the `Account` by
using a designated method. The `Account` is the aggregate root of this relation.
We can also enforce the correctness of the bi-directional `Account` <-> `Entry`
relation with this method:
[php]
class Account
{
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
return $e;
}
}
Now look at the following test-code for our entities:
[php]
class AccountTest extends \PHPUnit_Framework_TestCase
{
public function testAddEntry()
{
$account = new Account("123456", $maxCredit = 200);
$this->assertEquals(0, $account->getBalance());
$account->addEntry(500);
$this->assertEquals(500, $account->getBalance());
$account->addEntry(-700);
$this->assertEquals(-200, $account->getBalance());
}
public function testExceedMaxLimit()
{
$account = new Account("123456", $maxCredit = 200);
$this->setExpectedException("Exception");
$account->addEntry(-1000);
}
}
To enforce our rule we can now implement the assertion in `Account::addEntry`:
[php]
class Account
{
private function assertAcceptEntryAllowed($amount)
{
$futureBalance = $this->getBalance() + $amount;
$allowedMinimalBalance = ($this->maxCredit * -1);
if ($futureBalance < $allowedMinimalBalance) {
throw new Exception("Credit Limit exceeded, entry is not allowed!");
}
}
}
We haven't talked to the entity manager for persistence of our account example before.
You can call `EntityManager::persist($account)` and then `EntityManager::flush()`
at any point to save the account to the database. All the nested `Entry` objects
are automatically flushed to the database also.
[php]
$account = new Account("123456", 200);
$account->addEntry(500);
$account->addEntry(-200);
$em->persist($account);
$em->flush();
The current implementation has a considerable drawback. To get the balance, we
have to initialize the complete `Account::$entries` collection, possibly a very
large one. This can considerably hurt the performance of your application.
## Using an Aggregate Field
To overcome the previously mentioned issue (initializing the whole entries collection)
we want to add an aggregate field called "balance" on the Account and adjust the
code in `Account::getBalance()` and `Account:addEntry()`:
[php]
class Account
{
/**
* @Column(type="integer")
*/
private $balance = 0;
public function getBalance()
{
return $this->balance;
}
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
$this->balance += $amount;
return $e;
}
}
This is a very simple change, but all the tests still pass. Our account entities return
the correct balance. Now calling the `Account::getBalance()` method will not occur the
overhead of loading all entries anymore. Adding a new Entry to the `Account::$entities`
will also not initialize the collection internally.
Adding a new entry is therefore very performant and explicitly hooked into the domain model.
It will only update the account with the current balance and insert the new entry into the database.
## Tackling Race Conditions with Aggregate Fields
Whenever you denormalize your database schema race-conditions can potentially lead to
inconsistent state. See this example:
[php]
// The Account $accId has a balance of 0 and a max credit limit of 200:
// request 1 account
$account1 = $em->find('Bank\Entities\Account', $accId);
// request 2 account
$account2 = $em->find('Bank\Entities\Account', $accId);
$account1->addEntry(-200);
$account2->addEntry(-200);
// now request 1 and 2 both flush the changes.
The aggregate field `Account::$balance` is now -200, however the SUM over all
entries amounts yields -400. A violation of our max credit rule.
You can use both optimistic or pessimistic locking to save-guard
your aggregate fields against this kind of race-conditions. Reading Eric Evans
DDD carefully he mentions that the "Aggregate Root" (Account in our example)
needs a locking mechanism.
Optimistic locking is as easy as adding a version column:
[php]
class Amount
{
/** @Column(type="integer") @Version */
private $version;
}
The previous example would then throw an exception in the face of whatever request
saves the entity last (and would create the inconsistent state).
Pessimistic locking requires an additional flag set on the `EntityManager::find()`
call, enabling write locking directly in the database using a FOR UPDATE.
[php]
use Doctrine\DBAL\LockMode;
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
## Keeping Updates and Deletes in Sync
The example shown in this article does not allow changes to the value in `Entry`,
which considerably simplifies the effort to keep `Account::$balance` in sync.
If your use-case allows fields to be updated or related entities to be removed
you have to encapsulate this logic in your "Aggregate Root" entity and adjust
the aggregate field accordingly.
## Conclusion
This article described how to obtain aggregate values using DQL or your domain model.
It showed how you can easily add an aggregate field that offers serious performance
benefits over iterating all the related objects that make up an aggregate value.
Finally I showed how you can ensure that your aggregate fields do not get out
of sync due to race-conditions and concurrent access.

View File

@ -1,174 +0,0 @@
# Extending DQL in Doctrine 2: Custom AST Walkers
The Doctrine Query Language (DQL) is a proprietary sql-dialect that substitutes
tables and columns for Entity names and their fields. Using DQL you write a query
against the database using your entities. With the help of the metadata you
can write very concise, compact and powerful queries that are then translated
into SQL by the Doctrine ORM.
In Doctrine 1 the DQL language was not implemented using a real parser. This
made modifications of the DQL by the user impossible. Doctrine 2 in contrast
has a real parser for the DQL language, which transforms the DQL statement
into an [Abstract Syntax Tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree)
and generates the appropriate SQL statement for it. Since this process is
deterministic Doctrine heavily caches the SQL that is generated from any given DQL query,
which reduces the performance overhead of the parsing process to zero.
You can modify the Abstract syntax tree by hooking into DQL parsing process
by adding a Custom Tree Walker. A walker is an interface that walks each
node of the Abstract syntax tree, thereby generating the SQL statement.
There are two types of custom tree walkers that you can hook into the DQL parser:
- An output walker. This one actually generates the SQL, and there is only ever one of them. We implemented the default SqlWalker implementation for it.
- A tree walker. There can be many tree walkers, they cannot generate the sql, however they can modify the AST before its rendered to sql.
Now this is all awfully technical, so let me come to some use-cases fast
to keep you motivated. Using walker implementation you can for example:
* Modify the AST to generate a Count Query to be used with a paginator for any given DQL query.
* Modify the Output Walker to generate vendor-specific SQL (instead of ANSI).
* Modify the AST to add additional where clauses for specific entities (example ACL, country-specific content...)
* Modify the Output walker to pretty print the SQL for debugging purposes.
In this cookbook-entry I will show examples on the first two points. There
are probably much more use-cases.
## Generic count query for pagination
Say you have a blog and posts all with one category and one author. A query
for the front-page or any archive page might look something like:
[sql]
SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now in this query the blog post is the root entity, meaning its the one that
is hydrated directly from the query and returned as an array of blog posts.
In contrast the comment and author are loaded for deeper use in the object tree.
A pagination for this query would want to approximate the number of posts that
match the WHERE clause of this query to be able to predict the number of pages
to show to the user. A draft of the DQL query for pagination would look like:
[sql]
SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now you could go and write each of these queries by hand, or you can use a tree
walker to modify the AST for you. Lets see how the API would look for this use-case:
[php]
$pageNum = 1;
$query = $em->createQuery($dql);
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
$totalResults = Paginate::count($query);
$results = $query->getResult();
The `Paginate::count(Query $query)` looks like:
[php]
class Paginate
{
static public function count(Query $query)
{
/* @var $countQuery Query */
$countQuery = clone $query;
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
return $countQuery->getSingleScalarResult();
}
}
It clones the query, resets the limit clause first and max results and registers the `CountSqlWalker`
customer tree walker which will modify the AST to execute a count query. The walkers
implementation is:
[php]
class CountSqlWalker extends TreeWalkerAdapter
{
/**
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkSelectStatement(SelectStatement $AST)
{
$parent = null;
$parentName = null;
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
break;
}
}
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array(
$parent['metadata']->getSingleIdentifierFieldName())
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, true), null
)
);
}
}
This will delete any given select expressions and replace them with a distinct count
query for the root entities primary key. This will only work if your entity has
only one identifier field (composite keys won't work).
## Modify the Output Walker to generate Vendor specific SQL
Most RMDBS have vendor-specific features for optimizing select query
execution plans. You can write your own output walker to introduce certain
keywords using the Query Hint API. A query hint can be set via `Query::setHint($name, $value)`
as shown in the previous example with the `HINT_CUSTOM_TREE_WALKERS` query hint.
We will implement a custom Output Walker that allows to specify the SQL_NO_CACHE
query hint.
[php]
$dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...";
$query = $m->createQuery($dql);
$query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker');
$query->setQueryHint("mysqlWalker.sqlNoCache", true);
$results = $query->getResult();
Our `MysqlWalker` will extend the default `SqlWalker`. We will modify the generation
of the SELECT clause, adding the SQL_NO_CACHE on those queries that need it:
[php]
class MysqlWalker extends SqlWalker
{
/**
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
*
* @param $selectClause
* @return string The SQL.
*/
public function walkSelectClause($selectClause)
{
$sql = parent::walkSelectClause($selectClause);
if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
if ($selectClause->isDistinct) {
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
} else {
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
}
}
return $sql;
}
}
Writing extensions to the Output Walker requires a very deep understanding
of the DQL Parser and Walkers, but may offer your huge benefits with using vendor specific
features. This would still allow you write DQL queries instead of NativeQueries
to make use of vendor specific features.

View File

@ -1,198 +0,0 @@
By default DQL supports a limited subset of all the vendor-specific SQL functions
common between all the vendors. However in many cases once you have decided on a
specific database vendor, you will never change it during the life of your project.
This decision for a specific vendor potentially allows you to make use of powerful
SQL features that are unique to the vendor.
> **Note**
>
> It is worth to mention that Doctrine 2 also allows you to handwrite your SQL instead of extending
> the DQL parser, which is sort of an advanced extension point. You can map arbitrary SQL to your
> objects and gain access to vendor specific functionalities using the `EntityManager#createNativeQuery()` API.
The DQL Parser has hooks to register functions that can then be used in your DQL queries and transformed into SQL,
allowing to extend Doctrines Query capabilities to the vendors strength. This post explains the
Used-Defined Functions API (UDF) of the Dql Parser and shows some examples to give you
some hints how you would extend DQL.
There are three types of functions in DQL, those that return a numerical value,
those that return a string and those that return a Date. Your custom method
has to be registered as either one of those. The return type information
is used by the DQL parser to check possible syntax errors during the parsing
process, for example using a string function return value in a math expression.
## Registering your own DQL functions
You can register your functions adding them to the ORM configuration:
[php]
$config = new \Doctrine\ORM\Configuration();
$config->addCustomStringFunction($name, $class);
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
The `$name` is the name the function will be referred to in the DQL query. `$class` is a
string of a class-name which has to extend `Doctrine\ORM\Query\Node\FunctionNode`.
This is a class that offers all the necessary API and methods to implement
a UDF.
In this post we will implement some MySql specific Date calculation methods,
which are quite handy in my opinion:
## Date Diff
[Mysql's DateDiff function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff)
takes two dates as argument and calculates the difference in days with `date1-date2`.
The DQL parser is a top-down recursive descent parser to generate the
Abstract-Syntax Tree (AST) and uses a TreeWalker approach to generate the appropriate
SQL from the AST. This makes reading the Parser/TreeWalker code manageable
in a finite amount of time.
The `FunctionNode` class I referred to earlier requires you to implement
two methods, one for the parsing process (obviously) called `parse` and
one for the TreeWalker process called `getSql()`. I show you the code for
the DateDiff method and discuss it step by step:
[php]
/**
* DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
*/
class DateDiff extends FunctionNode
{
// (1)
public $firstDateExpression = null;
public $secondDateExpression = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // (2)
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(Lexer::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', ' .
$this->secondDateExpression->dispatch($sqlWalker) .
')'; // (7)
}
}
The Parsing process of the DATEDIFF function is going to find two expressions
the date1 and the date2 values, whose AST Node representations will be saved
in the variables of the DateDiff FunctionNode instance at (1).
The parse() method has to cut the function call "DATEDIFF" and its argument
into pieces. Since the parser detects the function using a lookahead the
T_IDENTIFIER of the function name has to be taken from the stack (2), followed
by a detection of the arguments in (4)-(6). The opening and closing parenthesis
have to be detected also. This happens during the Parsing process and leads
to the generation of a DateDiff FunctionNode somewhere in the AST of the
dql statement.
The `ArithmeticPrimary` method call is the most common denominator of valid
EBNF tokens taken from the [DQL EBNF grammar](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf)
that matches our requirements for valid input into the DateDiff Dql function.
Picking the right tokens for your methods is a tricky business, but the EBNF
grammar is pretty helpful finding it, as is looking at the Parser source code.
Now in the TreeWalker process we have to pick up this node and generate SQL
from it, which apparently is quite easy looking at the code in (7). Since
we don't know which type of AST Node the first and second Date expression
are we are just dispatching them back to the SQL Walker to generate SQL from
and then wrap our DATEDIFF function call around this output.
Now registering this DateDiff FunctionNode with the ORM using:
[php]
$config = new \Doctrine\ORM\Configuration();
$config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff');
We can do fancy stuff like:
[sql]
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7
## Date Add
Often useful it the ability to do some simple date calculations in your DQL query
using [MySql's DATE_ADD function](http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add).
I'll skip the blah and show the code for this function:
[php]
/**
* DateAddFunction ::=
* "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")"
*/
class DateAdd extends FunctionNode
{
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_IDENTIFIER);
/* @var $lexer Lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATE_ADD(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' .
$this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit .
')';
}
}
The only difference compared to the DATEDIFF here is, we additionally need the `Lexer` to access
the value of the `T_IDENTIFIER` token for the Date Interval unit, for example the MONTH in:
[sql]
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created
The above method now only supports the specification using `INTERVAL`, to also
allow a real date in DATE_ADD we need to add some decision logic to the parsing
process (makes up for a nice exercise).
Now as you see, the Parsing process doesn't catch all the possible SQL errors,
here we don't match for all the valid inputs for the interval unit.
However where necessary we rely on the database vendors SQL parser to show us further errors
in the parsing process, for example if the Unit would not be one of the supported values
by MySql.
## Conclusion
Now that you all know how you can implement vendor specific SQL functionalities in DQL,
we would be excited to see user extensions that add vendor specific function packages,
for example more math functions, XML + GIS Support, Hashing functions and so on.
For 2.0 we will come with the current set of functions, however for a future
version we will re-evaluate if we can abstract even more vendor sql functions
and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be found
[in my Github DoctrineExtensions repository](http://github.com/beberlei/DoctrineExtensions).

View File

@ -1,783 +0,0 @@
Doctrine 2 is a project that aims to handle the persistence of the domain model in a non-interfering way.
The Data Mapper pattern is at the heart of this project, aiming for a complete separation of the domain/business logic
from the persistence in a relational database management system. The benefit of Doctrine for the programmer is the
possibility can focus solely on the business and worry about persistence only as a secondary task. This doesn't mean
persistence is not important to Doctrine 2, however it is our belief that there are considerable benefits for object-oriented
programming, if persistence and entities are kept perfectly separated.
## What are Entities?
Entities are lightweight PHP Objects that don't need to extend any abstract base class or interface.
An entity class must not be final or contain final methods. Additionally it must not implement __clone
nor __wakeup or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone).
An entity contains persistable properties. A persistable property is an instance variable of the entity
that contains the data which is persisted and retrieved by Doctrine's data mapping capabilities.
## An Example Model: Bug Tracker
For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html)
documentation. Reading their documentation we can extract the requirements to be:
* A Bugs has a description, creation date, status, reporter and engineer
* A bug can occur on different products (platforms)
* Products have a name.
* Bug Reporter and Engineers are both Users of the System.
* A user can create new bugs.
* The assigned engineer can close a bug.
* A user can see all his reported or assigned bugs.
* Bugs can be paginated through a list-view.
> **WARNING**
>
> This tutorial is incrementally building up your Doctrine 2 knowledge and even lets you make some mistakes, to
> show some common pitfalls in mapping Entities to a database. Don't blindly copy-paste the examples here, it
> is not production ready without the additional comments and knowledge this tutorial teaches.
## A first prototype
A first simplified design for this domain model might look like the following set of classes:
[php]
class Bug
{
public $id;
public $description;
public $created;
public $status;
public $products = array();
public $reporter;
public $engineer;
}
class Product
{
public $id;
public $name;
}
class User
{
public $id;
public $name;
public $reportedBugs = array();
public $assignedBugs = array();
}
> **WARNING**
>
> This is only a prototype, please don't use public properties with Doctrine 2 at all,
> the "Queries for Application Use-Cases" section shows you why. In combination with proxies
> public properties can make up for pretty nasty bugs.
Because we will focus on the mapping aspect, no effort is being made to encapsulate the business logic in this example.
All persistable properties are public in visibility. We will soon see that this is not the best solution in combination
with Doctrine 2, one restriction that actually forces you to encapsulate your properties. For persistence Doctrine 2
actually uses Reflection to access the values in all your entities properties.
Many of the fields are single scalar values, for example the 3 ID fields of the entities, their names, description,
status and change dates. Doctrine 2 can easily handle these single values as can any other ORM. From a point of our
domain model they are ready to be used right now and we will see at a later stage how they are mapped to the database.
There are also several references between objects in this domain model, whose semantics are discussed case by case at this point
to explain how Doctrine handles them. In general each OneToOne or ManyToOne Relation in the Database is replaced by an
instance of the related object in the domain model. Each OneToMany or ManyToMany Relation is replaced by a collection
of instances in the domain model.
If you think this through carefully you realize Doctrine 2 will load up the complete database in memory if you access
one object. However by default Doctrine generates Lazy Load proxies of entities or collections of all the relations
that haven't been explicitly retrieved from the database yet.
To be able to use lazyload with collections, simple PHP arrays have to be replaced by a generic collection
interface Doctrine\Common\Collections\Collection which tries to act as array as much as possible using ArrayAccess,
IteratorAggregate and Countable interfaces. The class \Doctrine\Common\Collections\ArrayCollection is the most simple
implementation of this interface.
Now that we know this, we have to clear up our domain model to cope with the assumptions about related collections:
[php]
use Doctrine\Common\Collections\ArrayCollection;
class Bug
{
public $products = null;
public function __construct()
{
$this->products = new ArrayCollection();
}
}
class User
{
public $reportedBugs = null;
public $assignedBugs = null;
public function __construct()
{
$this->reportedBugs = new ArrayCollection();
$this->assignedBugs = new ArrayCollection();
}
}
Whenever an entity is recreated from the database, an Collection implementation of the type
Doctrine\ORM\PersistantCollection is injected into your entity instead of an array. Compared
to the ArrayCollection this implementation helps the Doctrine ORM understand the changes that
have happened to the collection which are noteworthy for persistence.
> **Warning**
> Lazy load proxies always contain an instance of Doctrine's EntityManager and all its dependencies. Therefore a var_dump()
> will possibly dump a very large recursive structure which is impossible to render and read. You have to use
> `Doctrine\Common\Util\Debug::dump()` to restrict the dumping to a human readable level. Additionally you should be aware
> that dumping the EntityManager to a Browser may take several minutes, and the Debug::dump() method just ignores any
> occurrences of it in Proxy instances.
Because we only work with collections for the references we must be careful to implement a bidirectional reference in
the domain model. The concept of owning or inverse side of a relation is central to this notion and should always
be kept in mind. The following assumptions are made about relations and have to be followed to be able to work with Doctrine 2.
These assumptions are not unique to Doctrine 2 but are best practices in handling database relations and Object-Relational Mapping.
* Changes to Collections are saved or updated, when the entity on the *owning* side of the collection is saved or updated.
* Saving an Entity at the inverse side of a relation never triggers a persist operation to changes to the collection.
* In a one-to-one relation the entity holding the foreign key of the related entity on its own database table is *always* the owning side of the relation.
* In a many-to-many relation, both sides can be the owning side of the relation. However in a bi-directional many-to-many relation only one is allowed to be.
* In a many-to-one relation the Many-side is the owning side by default, because it holds the foreign key.
* The OneToMany side of a relation is inverse by default, since the foreign key is saved on the Many side. A OneToMany relation can only be the owning side, if its implemented using a ManyToMany relation with join table and restricting the one side to allow only UNIQUE values per database constraint.
> **Important**
>
> Consistency of bi-directional references on the inverse side of a relation have to be managed in userland application code.
> Doctrine cannot magically update your collections to be consistent.
In the case of Users and Bugs we have references back and forth to the assigned and reported bugs from a user,
making this relation bi-directional. We have to change the code to ensure consistency of the bi-directional reference:
[php]
class Bug
{
protected $engineer;
protected $reporter;
public function setEngineer($engineer)
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter($reporter)
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
}
public function getEngineer()
{
return $this->engineer;
}
public function getReporter()
{
return $this->reporter;
}
}
class User
{
public function addReportedBug($bug)
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug($bug)
{
$this->assignedBugs[] = $bug;
}
}
I chose to name the inverse methods in past-tense, which should indicate that the actual assigning has already taken
place and the methods are only used for ensuring consistency of the references. You can see from `User::addReportedBug()`
and `User::assignedToBug()` that using this method in userland alone would not add the Bug to the collection of the owning
side in Bug::$reporter or Bug::$engineer. Using these methods and calling Doctrine for persistence would not update
the collections representation in the database.
Only using `Bug::setEngineer()` or `Bug::setReporter()` correctly saves the relation information. We also set both collection
instance variables to protected, however with PHP 5.3's new features Doctrine is still able to use Reflection to set and get values
from protected and private properties.
The `Bug::$reporter` and `Bug::$engineer` properties are Many-To-One relations, which point to a User. In a normalized
relational model the foreign key is saved on the Bug's table, hence in our object-relation model the Bug is at the owning
side of the relation. You should always make sure that the use-cases of your domain model should drive which side
is an inverse or owning one in your Doctrine mapping. In our example, whenever a new bug is saved or an engineer is assigned
to the bug, we don't want to update the User to persist the reference, but the Bug.
This is the case with the Bug being at the owning side of the relation.
Bugs reference Products by a uni-directional ManyToMany relation in the database that points from from Bugs to Products.
[php]
class Bug
{
protected $products = null; // Set protected for encapsulation
public function assignToProduct($product)
{
$this->products[] = $product;
}
public function getProducts()
{
return $this->products;
}
}
We are now finished with the domain model given the requirements. From the simple model with public properties only
we had to do quite some work to get to a model where we encapsulated the references between the objects to make sure
we don't break its consistent state when using Doctrine.
However up to now the assumptions Doctrine imposed on our business objects have not restricting us much in our domain
modelling capabilities. Actually we would have encapsulated access to all the properties anyways by using
object-oriented best-practices.
## Metadata Mappings for our Entities
Up to now we have only implemented our Entities as Data-Structures without actually telling Doctrine how to persist
them in the database. If perfect in-memory databases would exist, we could now finish the application using these entities
by implementing code to fulfil all the requirements. However the world isn't perfect and we have to persist our
entities in some storage to make sure we don't loose their state. Doctrine currently serves Relational Database Management Systems.
In the future we are thinking to support NoSQL vendors like CouchDb or MongoDb, however this is still far in the future.
The next step for persistence with Doctrine is to describe the structure of our domain model entities to Doctrine
using a metadata language. The metadata language describes how entities, their properties and references should be
persisted and what constraints should be applied to them.
Metadata for entities are loaded using a `Doctrine\ORM\Mapping\Driver\Driver` implementation and Doctrine 2 already comes
with XML, YAML and Annotations Drivers. In this Getting Started Guide I will use the XML Mapping Driver. I think XML
beats YAML because of schema validation, and my favorite IDE netbeans offers me auto-completion for the XML mapping files
which is awesome to work with and you don't have to look up all the different metadata mapping commands all the time.
Since we haven't namespaced our three entities, we have to implement three mapping files called Bug.dcm.xml,
Product.dcm.xml and User.dcm.xml and put them into a distinct folder for mapping configurations.
The first discussed definition will be for the Product, since it is the most simple one:
[xml]
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Product" table="zf_products">
<id name="id" type="integer" column="product_id">
<generator strategy="AUTO" />
</id>
<field name="name" column="product_name" type="string" />
</entity>
</doctrine-mapping>
The top-level `entity` definition tag specifies information about the class and table-name. The
primitive type `Product::$name` is defined as `field` attributes. The Id property is defined with the `id` tag.
The id has a `generator` tag nested inside which defines that the primary key generation mechanism
automatically uses the database platforms native id generation strategy, for example AUTO INCREMENT
in the case of MySql or Sequences in the case of PostgreSql and Oracle.
We then go on specifying the definition of a Bug:
[xml]
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Bug" table="zf_bugs">
<id name="id" type="integer" column="bug_id">
<generator strategy="AUTO" />
</id>
<field name="description" column="bug_description" type="text" />
<field name="created" column="bug_created" type="datetime" />
<field name="status" column="bug_status" type="string" />
<many-to-one target-entity="User" field="reporter" inversed-by="reportedBugs">
<join-column name="reporter_id" referenced-column-name="account_id" />
</many-to-one>
<many-to-one target-entity="User" field="engineer" inversed-by="assignedBugs">
<join-column name="engineer_id" referenced-column-name="account_id" />
</many-to-one>
<many-to-many target-entity="Product" field="products">
<join-table name="zf_bugs_products">
<join-columns>
<join-column name="bug_id" referenced-column-name="bug_id" />
</join-columns>
<inverse-join-columns>
<join-column name="product_id" referenced-column-name="product_id" />
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
</doctrine-mapping>
Here again we have the entity, id and primitive type definitions.
The column names are used from the Zend_Db_Table examples and have different names than the properties
on the Bug class. Additionally for the "created" field it is specified that it is of the Type "DATETIME",
which translates the YYYY-mm-dd HH:mm:ss Database format into a PHP DateTime instance and back.
After the field definitions the two qualified references to the user entity are defined. They are created by
the `many-to-one` tag. The class name of the related entity has to be specified with the `target-entity`
attribute, which is enough information for the database mapper to access the foreign-table. The
`join-column` tags are used to specify how the foreign and referenced columns are named, an information
Doctrine needs to construct joins between those two entities correctly. Since `reporter` and `engineer`
are on the owning side of a bi-directional relation we also have to specify the `inversed-by` attribute.
They have to point to the field names on the inverse side of the relationship.
The last missing property is the `Bug::$products` collection. It holds all products where the specific
bug is occurring in. Again you have to define the `target-entity` and `field` attributes on the `many-to-many`
tag. Furthermore you have to specify the details of the many-to-many join-table and its foreign key columns.
The definition is rather complex, however relying on the XML auto-completion I got it working easily, although
I forget the schema details all the time.
The last missing definition is that of the User entity:
[xml]
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="User" table="zf_accounts">
<id name="id" type="integer" column="account_id">
<generator strategy="AUTO" />
</id>
<field name="name" column="account_name" type="string" />
<one-to-many target-entity="Bug" field="reportedBugs" mapped-by="reporter" />
<one-to-many target-entity="Bug" field="assignedBugs" mapped-by="engineer" />
</entity>
</doctrine-mapping>
Here are some new things to mention about the `one-to-many` tags. Remember that we discussed about
the inverse and owning side. Now both reportedBugs and assignedBugs are inverse relations,
which means the join details have already been defined on the owning side. Therefore we only
have to specify the property on the Bug class that holds the owning sides.
This example has a fair overview of the most basic features of the metadata definition language.
## Obtaining the EntityManager
Doctrine's public interface is the EntityManager, it provides the access point to the complete
lifecycle management of your entities and transforms entities from and back to persistence. You
have to configure and create it to use your entities with Doctrine 2. I will show the configuration
steps and then discuss them step by step:
[php]
// Setup Autoloader (1)
// See the ORM Documentation Chapter "Configuration" for the up to day information
// on autoloading Doctrine 2.
$config = new Doctrine\ORM\Configuration(); // (2)
// Proxy Configuration (3)
$config->setProxyDir(__DIR__.'/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
$config->setAutoGenerateProxyClasses((APPLICATION_ENV == "development"));
// Mapping Configuration (4)
$driverImpl = new Doctrine\ORM\Mapping\Driver\XmlDriver(__DIR__."/config/mappings");
$config->setMetadataDriverImpl($driverImpl);
// Caching Configuration (5)
if (APPLICATION_ENV == "development") {
$cache = new \Doctrine\Common\Cache\ArrayCache();
} else {
$cache = new \Doctrine\Common\Cache\ApcCache();
}
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// database configuration parameters (6)
$conn = array(
'driver' => 'pdo_sqlite',
'path' => __DIR__ . '/db.sqlite',
);
// obtaining the entity manager (7)
$evm = new Doctrine\Common\EventManager()
$entityManager = \Doctrine\ORM\EntityManager::create($conn, $config, $evm);
The first block sets up the autoloading capabilities of Doctrine. I am registering the Doctrine
namespace to the given path. To add your own namespace you can instantiate another `ClassLoader`
with different namespace and path arguments. There is no requirement to use the Doctrine `ClassLoader`
for your autoloading needs, you can use whatever suits you best.
The second block contains of the instantiation of the ORM Configuration object. Besides the
configuration shown in the next blocks there are several others with are all explained
in the [Configuration section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options).
The Proxy Configuration is a required block for your application, you have to specify where
Doctrine writes the PHP code for Proxy Generation. Proxies are children of your entities generated
by Doctrine to allow for type-safe lazy loading. We will see in a later chapter how exactly this works.
Besides the path to the proxies we also specify which namespace they will reside under as well as
a flag `autoGenerateProxyClasses` indicating that proxies should be re-generated on each request,
which is recommended for development. In production this should be prevented at all costs,
the proxy class generation can be quite costly.
The fourth block contains the mapping driver details. We will use XML Mapping in this example, so
we configure the `XmlDriver` instance with a path to mappings configuration folder where we put
the Bug.dcm.xml, Product.dcm.xml and User.dcm.xml.
In the 5th block the caching configuration is set. In production we use caching only on a per request-basis
using the ArrayCache. In production it is literally required to use Apc, Memcache or XCache to get the full
speed out of Doctrine. Internally Doctrine uses caching heavily for the Metadata and DQL Query Language
so make sure you use a caching mechanism.
The 6th block shows the configuration options required to connect to a database, in my case a file-based
sqlite database. All the configuration options for all the shipped drivers are given in the [DBAL Configuration
section of the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/dbal).
The last block shows how the `EntityManager` is obtained from a factory method, Here we also pass
in an `EventManager` instance which is optional. However using the EventManager you can hook in to the lifecycle
of entities, which is a common use-case, so you know how to configure it already.
## Generating the Database Schema
Now that we have defined the Metadata Mappings and bootstrapped the EntityManager
we want to generate the relational database schema from it.
Doctrine has a Command-Line-Interface that allows you to access the SchemaTool, a component that generates
the required tables to work with the metadata.
For the command-line tool to work a cli-config.php file has to be present in the project root directory,
where you will execute the doctrine command. Its a fairly simple file:
[php]
$helperSet = new \Symfony\Components\Console\Helper\HelperSet(array(
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager)
));
$cli->setHelperSet($helperSet);
You can then change into your project directory and call the Doctrine command-line tool:
[console]
doctrine@my-desktop> cd myproject/
doctrine@my-desktop> doctrine orm:schema-tool:create
> **NOTE**
>
> The `doctrine` command will only be present if you installed Doctrine from PEAR.
> Otherwise you will have to dig into the `bin/doctrine.php` code of your Doctrine 2
> directory to setup your doctrine command-line client.
>
> See the [Tools section of the manual](http://www.doctrine-project.org/projects/orm/2.0/docs/reference/tools/en)
> on how to setup the Doctrine console correctly.
During the development you probably need to re-create the database several times when changing the Entity
metadata. You can then either re-create the database:
[console]
doctrine@my-desktop> doctrine orm:schema-tool:drop
doctrine@my-desktop> doctrine orm:schema-tool:create
Or use the update functionality:
[console]
doctrine@my-desktop> doctrine orm:schema-tool:update
The updating of databases uses a Diff Algorithm for a given Database Schema, a cornerstone of the `Doctrine\DBAL`
package, which can even be used without the Doctrine ORM package. However its not available in SQLite since
it does not support ALTER TABLE.
## Writing Entities into the Database
Having created the schema we can now start and save entities in the database. For starters we need a create user use-case:
[php]
$newUsername = "beberlei";
$user = new User();
$user->name = $newUsername;
$entityManager->persist($user);
$entityManager->flush();
Products can also be created:
[php]
$newProductName = "My Product";
$product = new Product();
$product->name = $newProductName;
$entityManager->persist($product);
$entityManager->flush();
So what is happening in those two snippets? In both examples the class creation is pretty standard, the interesting
bits are the communication with the `EntityManager`. To notify the EntityManager that a new entity should be inserted
into the database you have to call `persist()`. However the EntityManager does not act on this, its merely notified.
You have to explicitly call `flush()` to have the EntityManager write those two entities to the database.
You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the
UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which
is executed when flush is called. Using this approach the write-performance is significantly faster than
in a scenario where updates are done for each entity in isolation. In more complex scenarios than the
previous two, you are free to request updates on many different entities and all flush them at once.
Doctrine's UnitOfWork detects entities that have changed after retrieval from the database automatically when
the flush operation is called, so that you only have to keep track of those entities that are new or to be removed and pass them to
`EntityManager#persist()` and `EntityManager#remove()` respectively. This comparison to find dirty
entities that need updating is using a very efficient algorithm that has almost no additional
memory overhead and can even save you computing power by only updating those database columns
that really changed.
We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this:
[php]
$reporter = $entityManager->find("User", $theReporterId);
$engineer = $entityManager->find("User", $theDefaultEngineerId);
$bug = new Bug();
$bug->description = "Something does not work!";
$bug->created = new DateTime("now");
$bug->status = "NEW";
foreach ($productIds AS $productId) {
$product = $entityManager->find("Product", $productId);
$bug->assignToProduct($product);
}
$bug->setReporter($reporter);
$bug->setEngineer($engineer);
$entityManager->persist($bug);
$entityManager->flush();
echo "Your new Bug Id: ".$bug->id."\n";
This is the first contact with the read API of the EntityManager, showing that a call to
`EntityManager#find($name, $id)` returns a single instance of an entity queried by primary key.
Besides this we see the persist + flush pattern again to save the Bug into the database.
See how simple relating Bug, Reporter, Engineer and Products is done by using the discussed methods
in the "A first prototype" section. The UnitOfWork will detect this relations when flush
is called and relate them in the database appropriately.
## Queries for Application Use-Cases
### List of Bugs
Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying
mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first
read-only use-case:
[php]
$dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ORDER BY b.created DESC";
$query = $entityManager->createQuery($dql);
$query->setMaxResults(30);
$bugs = $query->getResult();
foreach($bugs AS $bug) {
echo $bug->description." - ".$bug->created->format('d.m.Y')."\n";
echo " Reported by: ".$bug->getReporter()->name."\n";
echo " Assigned to: ".$bug->getEngineer()->name."\n";
foreach($bug->getProducts() AS $product) {
echo " Platform: ".$product->name."\n";
}
echo "\n";
}
The DQL Query in this example fetches the 30 most recent bugs with their respective engineer and reporter
in one single SQL statement. The console output of this script is then:
Something does not work! - 02.04.2010
Reported by: beberlei
Assigned to: beberlei
Platform: My Product
> **NOTE**
>
> **Dql is not Sql**
>
> You may wonder why we start writing SQL at the beginning of this use-case. Don't we use an ORM to get rid
> of all the endless hand-writing of SQL? Doctrine introduces DQL which is best described
> as **object-query-language** and is a dialect of [OQL](http://en.wikipedia.org/wiki/Object_Query_Language) and
> similar to [HQL](http://www.hibernate.org) or [JPQL](http://en.wikipedia.org/wiki/Java_Persistence_Query_Language).
> It does not know the concept of columns and tables, but only those
> of Entity-Class and property. Using the Metadata we defined before it allows for very short distinctive
> and powerful queries.
>
> An important reason why DQL is favourable to the Query API of most ORMs is its similarity to SQL. The DQL language
> allows query constructs that most ORMs don't, GROUP BY even with HAVING, Sub-selects, Fetch-Joins of nested
> classes, mixed results with entities and scalar data such as COUNT() results and much more. Using
> DQL you should seldom come to the point where you want to throw your ORM into the dumpster, because it
> doesn't support some the more powerful SQL concepts.
>
> Besides handwriting DQL you can however also use the `QueryBuilder` retrieved by calling `$entityManager->createQueryBuilder()`
> which is a Query Object around the DQL language.
>
> As a last resort you can however also use Native SQL and a description of the result set to retrieve
> entities from the database. DQL boils down to a Native SQL statement and a `ResultSetMapping` instance itself.
> Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable
> database queries like PostgreSql's recursive queries.
### Array Hydration of the Bug List
In the previous use-case we retrieved the result as their respective object instances.
We are not limited to retrieving objects only from Doctrine however. For a simple list view
like the previous one we only need read access to our entities and can switch the hydration
from objects to simple PHP arrays instead. This can obviously yield considerable performance benefits for read-only requests.
Implementing the same list view as before using array hydration we can rewrite our code:
[php]
$dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
"JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
$query = $em->createQuery($dql);
$bugs = $query->getArrayResult();
foreach ($bugs AS $bug) {
echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n";
echo " Reported by: ".$bug['reporter']['name']."\n";
echo " Assigned to: ".$bug['engineer']['name']."\n";
foreach($bug['products'] AS $product) {
echo " Platform: ".$product['name']."\n";
}
echo "\n";
}
There is one significant difference in the DQL query however, we have
to add an additional fetch-join for the products connected to a bug. The resulting
SQL query for this single select statement is pretty large, however still
more efficient to retrieve compared to hydrating objects.
### Find by Primary Key
The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause,
however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already
seen in the write scenarios:
[php]
$bug = $entityManager->find("Bug", (int)$theBugId);
However we will soon see another problem with our entities using this approach. Try displaying the engineer's name:
[php]
echo "Bug: ".$bug->description."\n";
echo "Engineer: ".$bug->getEngineer()->name."\n";
It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistence
code of Doctrine. What is it then? You walked in the public property trap.
Since we only retrieved the bug by primary key both the engineer and reporter are not immediately loaded
from the database but are replaced by LazyLoading proxies. Sample
code of this proxy generated code can be found in the specified Proxy Directory, it looks like:
[php]
namespace MyProject\Proxies;
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
*/
class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy
{
// .. lazy load code here
public function addReportedBug($bug)
{
$this->_load();
return parent::addReportedBug($bug);
}
public function assignedToBug($bug)
{
$this->_load();
return parent::assignedToBug($bug);
}
}
See how upon each method call the proxy is lazily loaded from the database? Using public properties however
we never call a method and Doctrine has no way to hook into the PHP Engine to detect a direct access to a public property
and trigger the lazy load. We need to rewrite our entities, make all the properties private or protected and add getters
and setters to get a working example:
[php]
echo "Bug: ".$bug->getDescription()."\n";
echo "Engineer: ".$bug->getEngineer()->getName()."\n";
/**
Bug: Something does not work!
Engineer: beberlei
*/
Being required to use private or protected properties Doctrine 2 actually enforces you to encapsulate
your objects according to object-oriented best-practices.
## Dashboard of the User
For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or
was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters:
[php]
$dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ".
"WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC";
$myBugs = $entityManager->createQuery($dql)
->setParameter(1, $theUserId)
->setMaxResults(15)
->getResult();
foreach ($myBugs AS $bug) {
echo $bug->getDescription()."\n";
}
That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers
being able to close a bug.
## Number of Bugs
Until now we only retrieved entities or their array representation. Doctrine also supports the retrieval
of non-entities through DQL. These values are called "scalar result values" and may even be aggregate
values using COUNT, SUM, MIN, MAX or AVG functions.
We will need this knowledge to retrieve the number of open bugs grouped by product:
[php]
$dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
"JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
$productBugs = $em->createQuery($dql)->getScalarResult();
foreach($productBugs as $productBug) {
echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n";
}
## Updating Entities
There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like:
[php]
$bug = $entityManager->find("Bug", (int)$theBugId);
$bug->close();
$entityManager->flush();
When retrieving the Bug from the database it is inserted into the IdentityMap inside the UnitOfWork of Doctrine.
This means your Bug with exactly this id can only exist once during the whole request no matter how often you
call `EntityManager#find()`. It even detects entities that are hydrated using DQL and are already present in
the Identity Map.
When flush is called the EntityManager loops over all the entities in the identity map and performs a comparison
between the values originally retrieved from the database and those values the entity currently has. If at
least one of these properties is different the entity is scheduled for an UPDATE against the database.
Only the changed columns are updated, which offers a pretty good performance improvement compared to updating
all the properties.
This tutorial is over here, I hope you had fun. Additional content will be added to this tutorial
incrementally, topics will include:
* Entity Repositories
* More on Association Mappings
* Lifecycle Events triggered in the UnitOfWork
* Ordering of Collections
Additional details on all the topics discussed here can be found in the respective manual chapters.

View File

@ -1,63 +0,0 @@
As explained in the [restrictions for entity classes in the manual](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities),
it is usually not allowed for an entity to implement `__wakeup` or `__clone`, because Doctrine
makes special use of them. However, it is quite easy to make use of these methods in a safe way
by guarding the custom wakeup or clone code with an entity identity check, as demonstrated in the following sections.
++ Safely implementing __wakeup
To safely implement `__wakeup`, simply enclose your implementation code in an identity check
as follows:
[php]
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __wakeup()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
++ Safely implementing __clone
Safely implementing `__clone` is pretty much the same:
[php]
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __clone()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
++ Summary
As you have seen, it is quite easy to safely make use of `__wakeup` and `__clone` in your entities
without adding any really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine invokes these methods,
the entities never have an identity (yet). Furthermore, it is possibly a good idea to check
for the identity in your code anyway, since it's rarely the case that you want to unserialize
or clone an entity with no identity.

View File

@ -1,51 +0,0 @@
This recipe is intended as an example of implementing a loadClassMetadata listener to provide a Table Prefix option for your application. The method used below is not a hack, but fully integrates into the Doctrine system, all SQL generated will include the appropriate table prefix.
In most circumstances it is desirable to separate different applications into individual databases, but in certain cases, it may be beneficial to have a table prefix for your Entities to separate them from other vendor products in the same database.
++ Implementing the listener
The listener in this example has been set up with the DoctrineExtensions namespace. You create this file in your library/DoctrineExtensions directory, but will need to set up appropriate autoloaders.
[php]
<?php
namespace DoctrineExtensions;
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefix
{
protected $_prefix = '';
public function __construct($prefix)
{
$this->_prefix = (string) $prefix;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$classMetadata->setTableName($this->_prefix . $classMetadata->getTableName());
}
}
++ Telling the EntityManager about our listener
A listener of this type must be set up before the EntityManager has been initialised, otherwise an Entity might be created or cached before the prefix has been set.
> **Note**
> If you set this listener up, be aware that you will need to clear your caches
> and drop then recreate your database schema.
[php]
<?php
// $connectionOptions and $config set earlier
$evm = new \Doctrine\Common\EventManager;
// Table Prefix
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);

View File

@ -1,114 +0,0 @@
Doctrine 2 does not ship with any internal validators, the reason being that
we think all the frameworks out there already ship with quite decent ones that can be integrated
into your Domain easily. What we offer are hooks to execute any kind of validation.
> **Note**
> You don't need to validate your entities in the lifecycle events. Its only
> one of many options. Of course you can also perform validations in value setters
> or any other method of your entities that are used in your code.
Entities can register lifecycle event methods with Doctrine that are called on
different occasions. For validation we would need to hook into the
events called before persisting and updating. Even though we don't support
validation out of the box, the implementation is even simpler than in Doctrine 1
and you will get the additional benefit of being able to re-use your validation
in any other part of your domain.
Say we have an `Order` with several `OrderLine` instances. We never want to
allow any customer to order for a larger sum than he is allowed to:
[php]
class Order
{
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines AS $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
}
}
Now this is some pretty important piece of business logic in your code, enforcing
it at any time is important so that customers with a unknown reputation don't
owe your business too much money.
We can enforce this constraint in any of the metadata drivers. First Annotations:
[php]
/**
* @Entity
* @HasLifecycleCallbacks
*/
class Order
{
/**
* @PrePersist @PreUpdate
*/
public function assertCustomerAllowedBuying() {}
}
In XML Mappings:
[xml]
<doctrine-mapping>
<entity name="Order">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
</lifecycle-callbacks>
</entity>
</doctirne-mapping>
YAML needs some little change yet, to allow multiple lifecycle events for one method,
this will happen before Beta 1 though.
Now validation is performed whenever you call `EntityManager#persist($order)`
or when you call `EntityManager#flush()` and an order is about to be updated.
Any Exception that happens in the lifecycle callbacks will be cached by the
EntityManager and the current transaction is rolled back.
Of course you can do any type of primitive checks, not null, email-validation, string size,
integer and date ranges in your validation callbacks.
[php]
class Order
{
/**
* @PrePersist @PreUpdate
*/
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}
}
}
What is nice about lifecycle events is, you can also re-use the methods at other places
in your domain, for example in combination with your form library.
Additionally there is no limitation in the number of methods you register
on one particular event, i.e. you can register multiple methods for validation in "PrePersist"
or "PreUpdate" or mix and share them in any combinations between those two events.
There is no limit to what you can and can't validate in "PrePersist" and "PreUpdate" as long as
you don't create new entity instances. This was already discussed in the previous
blog post on the Versionable extension, which requires another type of event called "onFlush".
Further readings:
* [Doctrine 2 Manual: Events](http://www.doctrine-project.org/documentation/manual/2_0/en/events#lifecycle-events)

89
en/Makefile Normal file
View File

@ -0,0 +1,89 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

199
en/_templates/layout.html Normal file
View File

@ -0,0 +1,199 @@
{%- block doctype -%}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
{%- endblock %}
{%- set reldelim1 = reldelim1 is not defined and ' &raquo;' or reldelim1 %}
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
{%- set url_root = pathto('', 1) %}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- macro relbar() %}
<div class="related">
<h3>{{ _('Navigation') }}</h3>
<ul>
{%- for rellink in rellinks %}
<li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
<a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags }}"
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
{%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
{%- endfor %}
{%- block rootrellink %}
<li><a href="http://www.doctrine-project.org">Doctrine Homepage</a> &raquo;</li>
<li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
{%- endblock %}
{%- for parent in parents %}
<li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
{%- endfor %}
{%- block relbaritems %} {% endblock %}
</ul>
</div>
{%- endmacro %}
{%- macro sidebar() %}
{%- if not embedded %}{% if not theme_nosidebar|tobool %}
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
{%- block sidebarlogo %}
{%- if logo %}
<p class="logo"><a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
</a></p>
{%- endif %}
{%- endblock %}
{%- block sidebartoc %}
{%- if display_toc %}
<h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
{{ toc }}
{%- endif %}
{%- endblock %}
{%- block sidebarrel %}
{%- if prev %}
<h4>{{ _('Previous topic') }}</h4>
<p class="topless"><a href="{{ prev.link|e }}"
title="{{ _('previous chapter') }}">{{ prev.title }}</a></p>
{%- endif %}
{%- if next %}
<h4>{{ _('Next topic') }}</h4>
<p class="topless"><a href="{{ next.link|e }}"
title="{{ _('next chapter') }}">{{ next.title }}</a></p>
{%- endif %}
{%- endblock %}
{%- block sidebarsourcelink %}
{%- if show_source and has_source and sourcename %}
<h3>{{ _('This Page') }}</h3>
<ul class="this-page-menu">
<li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
rel="nofollow">{{ _('Show Source') }}</a></li>
</ul>
{%- endif %}
{%- endblock %}
{%- if customsidebar %}
{% include customsidebar %}
{%- endif %}
{%- block sidebarsearch %}
{%- if pagename != "search" %}
<div id="searchbox" style="display: none">
<h3>{{ _('Quick search') }}</h3>
<form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" size="18" />
<input type="submit" value="{{ _('Go') }}" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
<p class="searchtip" style="font-size: 90%">
{{ _('Enter search terms or a module, class or function name.') }}
</p>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
{%- endif %}
{%- endblock %}
</div>
</div>
{%- endif %}{% endif %}
{%- endmacro %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
{{ metatags }}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
{%- else %}
{%- set titlesuffix = "" %}
{%- endif %}
<title>{{ title|striptags }}{{ titlesuffix }}</title>
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
{%- if not embedded %}
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '{{ url_root }}',
VERSION: '{{ release|e }}',
COLLAPSE_MODINDEX: false,
FILE_SUFFIX: '{{ file_suffix }}',
HAS_SOURCE: {{ has_source|lower }}
};
</script>
{%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml"
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %}
{%- if favicon %}
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{%- endif %}
{%- endif %}
{%- block linktags %}
{%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
{%- endif %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
{%- endif %}
{%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
{%- endif %}
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
{%- if parents %}
<link rel="up" title="{{ parents[-1].title|striptags }}" href="{{ parents[-1].link|e }}" />
{%- endif %}
{%- if next %}
<link rel="next" title="{{ next.title|striptags }}" href="{{ next.link|e }}" />
{%- endif %}
{%- if prev %}
<link rel="prev" title="{{ prev.title|striptags }}" href="{{ prev.link|e }}" />
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
</head>
<body>
{%- block header %}{% endblock %}
{%- block relbar1 %}{{ relbar() }}{% endblock %}
{%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
<div class="document">
{%- block document %}
<div class="documentwrapper">
{%- if not embedded %}{% if not theme_nosidebar|tobool %}
<div class="bodywrapper">
{%- endif %}{% endif %}
<div class="body">
{% block body %} {% endblock %}
</div>
{%- if not embedded %}{% if not theme_nosidebar|tobool %}
</div>
{%- endif %}{% endif %}
</div>
{%- endblock %}
{%- block sidebar2 %}{{ sidebar() }}{% endblock %}
<div class="clearer"></div>
</div>
{%- block relbar2 %}{{ relbar() }}{% endblock %}
{%- block footer %}
<div class="footer">
{%- if hasdoc('copyright') %}
{% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
{%- else %}
{% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
{%- endif %}
{%- if last_updated %}
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
{%- endif %}
{%- if show_sphinx %}
{% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
{%- endif %}
</div>
{%- endblock %}
</body>
</html>

194
en/conf.py Normal file
View File

@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
#
# Doctrine 2 ORM documentation build configuration file, created by
# sphinx-quickstart on Fri Dec 3 18:10:24 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.append(os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Doctrine 2 ORM'
copyright = u'2010, Doctrine Project Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0'
# The full version, including alpha/beta/rc tags.
release = '2.0.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
language = 'php'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'Doctrine2ORMdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation',
u'Doctrine Project Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True

View File

@ -0,0 +1,376 @@
Aggregate Fields
================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
You will often come across the requirement to display aggregate
values of data that can be computed by using the MIN, MAX, COUNT or
SUM SQL functions. For any ORM this is a tricky issue
traditionally. Doctrine 2 offers several ways to get access to
these values and this article will describe all of them from
different perspectives.
You will see that aggregate fields can become very explicit
features in your domain model and how this potentially complex
business rules can be easily tested.
An example model
----------------
Say you want to model a bank account and all their entries. Entries
into the account can either be of positive or negative money
values. Each account has a credit limit and the account is never
allowed to have a balance below that value.
For simplicity we live in a world were money is composed of
integers only. Also we omit the receiver/sender name, stated reason
for transfer and the execution date. These all would have to be
added on the ``Entry`` object.
Our entities look like:
.. code-block:: php
<?php
namespace Bank\Entities;
/**
* @Entity
*/
class Account
{
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/** @Column(type="string", unique=true) */
private $no;
/**
* @OneToMany(targetEntity="Entry", mappedBy="entries", cascade={"persist"})
*/
private $entries;
/**
* @Column(type="integer")
*/
private $maxCredit = 0;
public function __construct($no, $maxCredit = 0)
{
$this->no = $no;
$this->maxCredit = $maxCredit;
$this->entries = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/**
* @Entity
*/
class Entry
{
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/**
* @ManyToOne(targetEntity="Account", inversedBy="entries")
*/
private $account;
/**
* @Column(type="integer")
*/
private $amount;
public function __construct($account, $amount)
{
$this->account = $account;
$this->amount = $amount;
// more stuff here, from/to whom, stated reason, execution date and such
}
public function getAmount()
{
return $this->amount;
}
}
Using DQL
---------
The Doctrine Query Language allows you to select for aggregate
values computed from fields of your Domain Model. You can select
the current balance of your account by calling:
.. code-block:: php
<?php
$dql = "SELECT SUM(e.amount) AS balance FROM Bank\Entities\Entry e " .
"WHERE e.account = ?1";
$balance = $em->createQuery($dql)
->setParameter(1, $myAccountId)
->getSingleScalarResult();
The ``$em`` variable in this (and forthcoming) example holds the
Doctrine ``EntityManager``. We create a query for the SUM of all
amounts (negative amounts are withdraws) and retrieve them as a
single scalar result, essentially return only the first column of
the first row.
This approach is simple and powerful, however it has a serious
drawback. We have to execute a specific query for the balance
whenever we need it.
To implement a powerful domain model we would rather have access to
the balance from our ``Account`` entity during all times (even if
the Account was not persisted in the database before!).
Also an additional requirement is the max credit per ``Account``
rule.
We cannot reliably enforce this rule in our ``Account`` entity with
the DQL retrieval of the balance. There are many different ways to
retrieve accounts. We cannot guarantee that we can execute the
aggregation query for all these use-cases, let alone that a
userland programmer checks this balance against newly added
entries.
Using your Domain Model
-----------------------
``Account`` and all the ``Entry`` instances are connected through a
collection, which means we can compute this value at runtime:
.. code-block:: php
<?php
class Account
{
// .. previous code
public function getBalance()
{
$balance = 0;
foreach ($this->entries AS $entry) {
$balance += $entry->getAmount();
}
return $balance;
}
}
Now we can always call ``Account::getBalance()`` to access the
current account balance.
To enforce the max credit rule we have to implement the "Aggregate
Root" pattern as described in Eric Evans book on Domain Driven
Design. Described with one sentence, an aggregate root controls the
instance creation, access and manipulation of its children.
In our case we want to enforce that new entries can only added to
the ``Account`` by using a designated method. The ``Account`` is
the aggregate root of this relation. We can also enforce the
correctness of the bi-directional ``Account`` <-> ``Entry``
relation with this method:
.. code-block:: php
<?php
class Account
{
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
return $e;
}
}
Now look at the following test-code for our entities:
.. code-block:: php
<?php
class AccountTest extends \PHPUnit_Framework_TestCase
{
public function testAddEntry()
{
$account = new Account("123456", $maxCredit = 200);
$this->assertEquals(0, $account->getBalance());
$account->addEntry(500);
$this->assertEquals(500, $account->getBalance());
$account->addEntry(-700);
$this->assertEquals(-200, $account->getBalance());
}
public function testExceedMaxLimit()
{
$account = new Account("123456", $maxCredit = 200);
$this->setExpectedException("Exception");
$account->addEntry(-1000);
}
}
To enforce our rule we can now implement the assertion in
``Account::addEntry``:
.. code-block:: php
<?php
class Account
{
private function assertAcceptEntryAllowed($amount)
{
$futureBalance = $this->getBalance() + $amount;
$allowedMinimalBalance = ($this->maxCredit * -1);
if ($futureBalance < $allowedMinimalBalance) {
throw new Exception("Credit Limit exceeded, entry is not allowed!");
}
}
}
We haven't talked to the entity manager for persistence of our
account example before. You can call
``EntityManager::persist($account)`` and then
``EntityManager::flush()`` at any point to save the account to the
database. All the nested ``Entry`` objects are automatically
flushed to the database also.
.. code-block:: php
<?php
$account = new Account("123456", 200);
$account->addEntry(500);
$account->addEntry(-200);
$em->persist($account);
$em->flush();
The current implementation has a considerable drawback. To get the
balance, we have to initialize the complete ``Account::$entries``
collection, possibly a very large one. This can considerably hurt
the performance of your application.
Using an Aggregate Field
------------------------
To overcome the previously mentioned issue (initializing the whole
entries collection) we want to add an aggregate field called
"balance" on the Account and adjust the code in
``Account::getBalance()`` and ``Account:addEntry()``:
.. code-block:: php
<?php
class Account
{
/**
* @Column(type="integer")
*/
private $balance = 0;
public function getBalance()
{
return $this->balance;
}
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
$this->balance += $amount;
return $e;
}
}
This is a very simple change, but all the tests still pass. Our
account entities return the correct balance. Now calling the
``Account::getBalance()`` method will not occur the overhead of
loading all entries anymore. Adding a new Entry to the
``Account::$entities`` will also not initialize the collection
internally.
Adding a new entry is therefore very performant and explicitly
hooked into the domain model. It will only update the account with
the current balance and insert the new entry into the database.
Tackling Race Conditions with Aggregate Fields
----------------------------------------------
Whenever you denormalize your database schema race-conditions can
potentially lead to inconsistent state. See this example:
.. code-block:: php
<?php
// The Account $accId has a balance of 0 and a max credit limit of 200:
// request 1 account
$account1 = $em->find('Bank\Entities\Account', $accId);
// request 2 account
$account2 = $em->find('Bank\Entities\Account', $accId);
$account1->addEntry(-200);
$account2->addEntry(-200);
// now request 1 and 2 both flush the changes.
The aggregate field ``Account::$balance`` is now -200, however the
SUM over all entries amounts yields -400. A violation of our max
credit rule.
You can use both optimistic or pessimistic locking to save-guard
your aggregate fields against this kind of race-conditions. Reading
Eric Evans DDD carefully he mentions that the "Aggregate Root"
(Account in our example) needs a locking mechanism.
Optimistic locking is as easy as adding a version column:
.. code-block:: php
<?php
class Amount
{
/** @Column(type="integer") @Version */
private $version;
}
The previous example would then throw an exception in the face of
whatever request saves the entity last (and would create the
inconsistent state).
Pessimistic locking requires an additional flag set on the
``EntityManager::find()`` call, enabling write locking directly in
the database using a FOR UPDATE.
.. code-block:: php
<?php
use Doctrine\DBAL\LockMode;
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
Keeping Updates and Deletes in Sync
-----------------------------------
The example shown in this article does not allow changes to the
value in ``Entry``, which considerably simplifies the effort to
keep ``Account::$balance`` in sync. If your use-case allows fields
to be updated or related entities to be removed you have to
encapsulate this logic in your "Aggregate Root" entity and adjust
the aggregate field accordingly.
Conclusion
----------
This article described how to obtain aggregate values using DQL or
your domain model. It showed how you can easily add an aggregate
field that offers serious performance benefits over iterating all
the related objects that make up an aggregate value. Finally I
showed how you can ensure that your aggregate fields do not get out
of sync due to race-conditions and concurrent access.

View File

@ -0,0 +1,218 @@
Extending DQL in Doctrine 2: Custom AST Walkers
===============================================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
The Doctrine Query Language (DQL) is a proprietary sql-dialect that
substitutes tables and columns for Entity names and their fields.
Using DQL you write a query against the database using your
entities. With the help of the metadata you can write very concise,
compact and powerful queries that are then translated into SQL by
the Doctrine ORM.
In Doctrine 1 the DQL language was not implemented using a real
parser. This made modifications of the DQL by the user impossible.
Doctrine 2 in contrast has a real parser for the DQL language,
which transforms the DQL statement into an
`Abstract Syntax Tree <http://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
and generates the appropriate SQL statement for it. Since this
process is deterministic Doctrine heavily caches the SQL that is
generated from any given DQL query, which reduces the performance
overhead of the parsing process to zero.
You can modify the Abstract syntax tree by hooking into DQL parsing
process by adding a Custom Tree Walker. A walker is an interface
that walks each node of the Abstract syntax tree, thereby
generating the SQL statement.
There are two types of custom tree walkers that you can hook into
the DQL parser:
- An output walker. This one actually generates the SQL, and there
is only ever one of them. We implemented the default SqlWalker
implementation for it.
- A tree walker. There can be many tree walkers, they cannot
generate the sql, however they can modify the AST before its
rendered to sql.
Now this is all awfully technical, so let me come to some use-cases
fast to keep you motivated. Using walker implementation you can for
example:
- Modify the AST to generate a Count Query to be used with a
paginator for any given DQL query.
- Modify the Output Walker to generate vendor-specific SQL
(instead of ANSI).
- Modify the AST to add additional where clauses for specific
entities (example ACL, country-specific content...)
- Modify the Output walker to pretty print the SQL for debugging
purposes.
In this cookbook-entry I will show examples on the first two
points. There are probably much more use-cases.
Generic count query for pagination
----------------------------------
Say you have a blog and posts all with one category and one author.
A query for the front-page or any archive page might look something
like:
.. code-block:: sql
SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now in this query the blog post is the root entity, meaning its the
one that is hydrated directly from the query and returned as an
array of blog posts. In contrast the comment and author are loaded
for deeper use in the object tree.
A pagination for this query would want to approximate the number of
posts that match the WHERE clause of this query to be able to
predict the number of pages to show to the user. A draft of the DQL
query for pagination would look like:
.. code-block:: sql
SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now you could go and write each of these queries by hand, or you
can use a tree walker to modify the AST for you. Lets see how the
API would look for this use-case:
.. code-block:: php
<?php
$pageNum = 1;
$query = $em->createQuery($dql);
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
$totalResults = Paginate::count($query);
$results = $query->getResult();
The ``Paginate::count(Query $query)`` looks like:
.. code-block:: php
<?php
class Paginate
{
static public function count(Query $query)
{
/* @var $countQuery Query */
$countQuery = clone $query;
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
return $countQuery->getSingleScalarResult();
}
}
It clones the query, resets the limit clause first and max results
and registers the ``CountSqlWalker`` customer tree walker which
will modify the AST to execute a count query. The walkers
implementation is:
.. code-block:: php
<?php
class CountSqlWalker extends TreeWalkerAdapter
{
/**
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkSelectStatement(SelectStatement $AST)
{
$parent = null;
$parentName = null;
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
break;
}
}
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, array(
$parent['metadata']->getSingleIdentifierFieldName())
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, true), null
)
);
}
}
This will delete any given select expressions and replace them with
a distinct count query for the root entities primary key. This will
only work if your entity has only one identifier field (composite
keys won't work).
Modify the Output Walker to generate Vendor specific SQL
--------------------------------------------------------
Most RMDBS have vendor-specific features for optimizing select
query execution plans. You can write your own output walker to
introduce certain keywords using the Query Hint API. A query hint
can be set via ``Query::setHint($name, $value)`` as shown in the
previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint.
We will implement a custom Output Walker that allows to specify the
SQL\_NO\_CACHE query hint.
.. code-block:: php
<?php
$dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...";
$query = $m->createQuery($dql);
$query->setQueryHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker');
$query->setQueryHint("mysqlWalker.sqlNoCache", true);
$results = $query->getResult();
Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will
modify the generation of the SELECT clause, adding the
SQL\_NO\_CACHE on those queries that need it:
.. code-block:: php
<?php
class MysqlWalker extends SqlWalker
{
/**
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
*
* @param $selectClause
* @return string The SQL.
*/
public function walkSelectClause($selectClause)
{
$sql = parent::walkSelectClause($selectClause);
if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
if ($selectClause->isDistinct) {
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
} else {
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
}
}
return $sql;
}
}
Writing extensions to the Output Walker requires a very deep
understanding of the DQL Parser and Walkers, but may offer your
huge benefits with using vendor specific features. This would still
allow you write DQL queries instead of NativeQueries to make use of
vendor specific features.

View File

@ -0,0 +1,240 @@
DQL User Defined Functions
==========================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
By default DQL supports a limited subset of all the vendor-specific
SQL functions common between all the vendors. However in many cases
once you have decided on a specific database vendor, you will never
change it during the life of your project. This decision for a
specific vendor potentially allows you to make use of powerful SQL
features that are unique to the vendor.
It is worth to mention that Doctrine 2 also allows you to handwrite
your SQL instead of extending the DQL parser. Extending DQL is sort of an
advanced extension point. You can map arbitrary SQL to your objects
and gain access to vendor specific functionalities using the
``EntityManager#createNativeQuery()`` API as described in
the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the Used-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
There are three types of functions in DQL, those that return a
numerical value, those that return a string and those that return a
Date. Your custom method has to be registered as either one of
those. The return type information is used by the DQL parser to
check possible syntax errors during the parsing process, for
example using a string function return value in a math expression.
Registering your own DQL functions
----------------------------------
You can register your functions adding them to the ORM
configuration:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomStringFunction($name, $class);
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
The ``$name`` is the name the function will be referred to in the
DQL query. ``$class`` is a string of a class-name which has to
extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class
that offers all the necessary API and methods to implement a UDF.
In this post we will implement some MySql specific Date calculation
methods, which are quite handy in my opinion:
Date Diff
---------
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
takes two dates as argument and calculates the difference in days
with ``date1-date2``.
The DQL parser is a top-down recursive descent parser to generate
the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to
generate the appropriate SQL from the AST. This makes reading the
Parser/TreeWalker code manageable in a finite amount of time.
The ``FunctionNode`` class I referred to earlier requires you to
implement two methods, one for the parsing process (obviously)
called ``parse`` and one for the TreeWalker process called
``getSql()``. I show you the code for the DateDiff method and
discuss it step by step:
.. code-block:: php
<?php
/**
* DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
*/
class DateDiff extends FunctionNode
{
// (1)
public $firstDateExpression = null;
public $secondDateExpression = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // (2)
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(Lexer::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', ' .
$this->secondDateExpression->dispatch($sqlWalker) .
')'; // (7)
}
}
The Parsing process of the DATEDIFF function is going to find two
expressions the date1 and the date2 values, whose AST Node
representations will be saved in the variables of the DateDiff
FunctionNode instance at (1).
The parse() method has to cut the function call "DATEDIFF" and its
argument into pieces. Since the parser detects the function using a
lookahead the T\_IDENTIFIER of the function name has to be taken
from the stack (2), followed by a detection of the arguments in
(4)-(6). The opening and closing parenthesis have to be detected
also. This happens during the Parsing process and leads to the
generation of a DateDiff FunctionNode somewhere in the AST of the
dql statement.
The ``ArithmeticPrimary`` method call is the most common
denominator of valid EBNF tokens taken from the
`DQL EBNF grammar <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
that matches our requirements for valid input into the DateDiff Dql
function. Picking the right tokens for your methods is a tricky
business, but the EBNF grammar is pretty helpful finding it, as is
looking at the Parser source code.
Now in the TreeWalker process we have to pick up this node and
generate SQL from it, which apparently is quite easy looking at the
code in (7). Since we don't know which type of AST Node the first
and second Date expression are we are just dispatching them back to
the SQL Walker to generate SQL from and then wrap our DATEDIFF
function call around this output.
Now registering this DateDiff FunctionNode with the ORM using:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff');
We can do fancy stuff like:
.. code-block:: sql
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7
Date Add
--------
Often useful it the ability to do some simple date calculations in
your DQL query using
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
I'll skip the blah and show the code for this function:
.. code-block:: php
<?php
/**
* DateAddFunction ::=
* "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")"
*/
class DateAdd extends FunctionNode
{
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_IDENTIFIER);
/* @var $lexer Lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATE_ADD(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' .
$this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit .
')';
}
}
The only difference compared to the DATEDIFF here is, we
additionally need the ``Lexer`` to access the value of the
``T_IDENTIFIER`` token for the Date Interval unit, for example the
MONTH in:
.. code-block:: sql
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created
The above method now only supports the specification using
``INTERVAL``, to also allow a real date in DATE\_ADD we need to add
some decision logic to the parsing process (makes up for a nice
exercise).
Now as you see, the Parsing process doesn't catch all the possible
SQL errors, here we don't match for all the valid inputs for the
interval unit. However where necessary we rely on the database
vendors SQL parser to show us further errors in the parsing
process, for example if the Unit would not be one of the supported
values by MySql.
Conclusion
----------
Now that you all know how you can implement vendor specific SQL
functionalities in DQL, we would be excited to see user extensions
that add vendor specific function packages, for example more math
functions, XML + GIS Support, Hashing functions and so on.
For 2.0 we will come with the current set of functions, however for
a future version we will re-evaluate if we can abstract even more
vendor sql functions and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be
found
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.

View File

@ -1,47 +1,63 @@
Implementing ArrayAccess for Domain Objects
===========================================
This recipe will show you how to implement ArrayAccess for your domain objects in order to allow more uniform access, for example in templates. In these examples we will implement ArrayAccess on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects.
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
++ Option 1
This recipe will show you how to implement ArrayAccess for your
domain objects in order to allow more uniform access, for example
in templates. In these examples we will implement ArrayAccess on a
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats:
Option 1
--------
* It will not work with private fields
* It will not go through any getters/setters
In this implementation we will make use of PHPs highly dynamic
nature to dynamically access properties of a subtype in a supertype
at runtime. Note that this implementation has 2 main caveats:
-
[php]
- It will not work with private fields
- It will not go through any getters/setters
.. code-block:: php
<?php
abstract class DomainObject implements ArrayAccess
{
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetSet($offset, $value) {
$this->$offset = $value;
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetUnset($offset) {
$this->$offset = null;
}
}
Option 2
--------
++ Option 2
In this implementation we will dynamically invoke getters/setters.
Again we use PHPs dynamic nature to invoke methods on a subtype
from a supertype at runtime. This implementation has the following
caveats:
In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats:
* It relies on a naming convention
* The semantics of offsetExists can differ
* offsetUnset will not work with typehinted setters
- It relies on a naming convention
- The semantics of offsetExists can differ
- offsetUnset will not work with typehinted setters
-
.. code-block:: php
[php]
<?php
abstract class DomainObject implements ArrayAccess
{
public function offsetExists($offset) {
@ -49,41 +65,48 @@ In this implementation we will dynamically invoke getters/setters. Again we use
$value = $this->{"get$offset"}();
return $value !== null;
}
public function offsetSet($offset, $value) {
$this->{"set$offset"}($value);
}
public function offsetGet($offset) {
return $this->{"get$offset"}();
}
public function offsetUnset($offset) {
$this->{"set$offset"}(null);
}
}
++ Read-only
You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException).
Read-only
---------
[php]
You can slightly tweak option 1 or option 2 in order to make array
access read-only. This will also circumvent some of the caveats of
each option. Simply make offsetSet and offsetUnset throw an
exception (i.e. BadMethodCallException).
.. code-block:: php
<?php
abstract class DomainObject implements ArrayAccess
{
public function offsetExists($offset) {
// option 1 or option 2
}
public function offsetSet($offset, $value) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
public function offsetGet($offset) {
// option 1 or option 2
}
public function offsetUnset($offset) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
}

View File

@ -1,22 +1,38 @@
Implementing the Notify ChangeTracking Policy
=============================================
The NOTIFY change-tracking policy is the most effective change-tracking policy provided by Doctrine but it requires some boilerplate code. This recipe will show you how this boilerplate code should look like. We will implement it on a [Layer Supertype](http://martinfowler.com/eaaCatalog/layerSupertype.html) for all our domain objects.
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
++ Implementing NotifyPropertyChanged
The NOTIFY change-tracking policy is the most effective
change-tracking policy provided by Doctrine but it requires some
boilerplate code. This recipe will show you how this boilerplate
code should look like. We will implement it on a
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
The NOTIFY policy is based on the assumption that the entities notify interested listeners of changes to their properties. For that purpose, a class that wants to use this policy needs to implement the `NotifyPropertyChanged` interface from the `Doctrine\Common` namespace.
Implementing NotifyPropertyChanged
----------------------------------
[php]
The NOTIFY policy is based on the assumption that the entities
notify interested listeners of changes to their properties. For
that purpose, a class that wants to use this policy needs to
implement the ``NotifyPropertyChanged`` interface from the
``Doctrine\Common`` namespace.
.. code-block:: php
<?php
use Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->_listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function _onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->_listeners) {
@ -27,15 +43,19 @@ The NOTIFY policy is based on the assumption that the entities notify interested
}
}
Then, in each property setter of concrete, derived domain classes, you need to invoke _onPropertyChanged as follows to notify listeners:
Then, in each property setter of concrete, derived domain classes,
you need to invoke \_onPropertyChanged as follows to notify
listeners:
[php]
.. code-block:: php
<?php
// Mapping not shown, either in annotations, xml or yaml as usual
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->_onPropertyChanged('data', $this->data, $data);
@ -44,5 +64,9 @@ Then, in each property setter of concrete, derived domain classes, you need to i
}
}
The check whether the new value is different from the old one is not mandatory but recommended. That way you can avoid unnecessary updates and also have full control over when you consider a property changed.
The check whether the new value is different from the old one is
not mandatory but recommended. That way you can avoid unnecessary
updates and also have full control over when you consider a
property changed.

View File

@ -0,0 +1,78 @@
Implementing Wakeup or Clone
============================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing \_\_wakeup
------------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __wakeup()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Safely implementing \_\_clone
-----------------------------
Safely implementing ``__clone`` is pretty much the same:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __clone()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Summary
-------
As you have seen, it is quite easy to safely make use of
``__wakeup`` and ``__clone`` in your entities without adding any
really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine
invokes these methods, the entities never have an identity (yet).
Furthermore, it is possibly a good idea to check for the identity
in your code anyway, since it's rarely the case that you want to
unserialize or clone an entity with no identity.

View File

@ -1,81 +1,96 @@
This is recipe for using Doctrine 2 in your [CodeIgniter](http://www.codeigniter.com) framework.
Integrating with CodeIgniter
============================
This is recipe for using Doctrine 2 in your
`CodeIgniter <http://www.codeigniter.com>`_ framework.
.. note::
This might not work for all CodeIgniter versions and may require
slight adjustments.
> **NOTE**
>
> This might not work for all CodeIgniter versions and may require slight adjustments.
Here is how to set it up:
Make a CodeIgniter library that is both a wrapper and a bootstrap for Doctrine 2.
Make a CodeIgniter library that is both a wrapper and a bootstrap
for Doctrine 2.
++ Setting up the file structure
Setting up the file structure
-----------------------------
Here are the steps:
* Add a php file to your system/application/libraries folder called Doctrine.php. This is going to be your wrapper/bootstrap for the D2 entity manager.
* Put the Doctrine folder (the one that contains Common, DBAL, and ORM) inside that same libraries folder.
* Your system/application/libraries folder now looks like this:
system/applications/libraries
-Doctrine
-Doctrine.php
-index.html
- Add a php file to your system/application/libraries folder
called Doctrine.php. This is going to be your wrapper/bootstrap for
the D2 entity manager.
- Put the Doctrine folder (the one that contains Common, DBAL, and
ORM) inside that same libraries folder.
- Your system/application/libraries folder now looks like this:
* If you want, open your config/autoload.php file and autoload your Doctrine library.
system/applications/libraries -Doctrine -Doctrine.php -index.html
[php]
$autoload['libraries'] = array('doctrine');
- If you want, open your config/autoload.php file and autoload
your Doctrine library.
++ Creating your Doctrine CodeIgniter library
<?php $autoload['libraries'] = array('doctrine');
Now, here is what your Doctrine.php file should look like. Customize it to your needs.
[php]
Creating your Doctrine CodeIgniter library
------------------------------------------
Now, here is what your Doctrine.php file should look like.
Customize it to your needs.
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader,
Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager,
Doctrine\Common\Cache\ArrayCache,
Doctrine\DBAL\Logging\EchoSqlLogger;
class Doctrine {
public $em = null;
public function __construct()
{
// load database configuration from CodeIgniter
require_once APPPATH.'config/database.php';
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
// if you want to.
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
$doctrineClassLoader->register();
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
$entitiesClassLoader->register();
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
$proxiesClassLoader->register();
// Set up caches
$config = new Configuration;
$cache = new ArrayCache;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Proxy configuration
$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');
// Set up logger
$logger = new EchoSqlLogger;
$config->setSqlLogger($logger);
$config->setAutoGenerateProxyClasses( TRUE );
// Database connection information
$connectionOptions = array(
'driver' => 'pdo_mysql',
@ -84,28 +99,42 @@ Now, here is what your Doctrine.php file should look like. Customize it to your
'host' => $db['default']['hostname'],
'dbname' => $db['default']['database']
);
// Create EntityManager
$this->em = EntityManager::create($connectionOptions, $config);
}
}
Please note that this is a development configuration; for a production system you'll want to use a real caching system like APC, get rid of EchoSqlLogger, and turn off autoGenerateProxyClasses.
Please note that this is a development configuration; for a
production system you'll want to use a real caching system like
APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the [Doctrine 2 Configuration documentation](http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options).
For more details, consult the
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
++ Now to use it
Now to use it
-------------
Whenever you need a reference to the entity manager inside one of your controllers, views, or models you can do this:
Whenever you need a reference to the entity manager inside one of
your controllers, views, or models you can do this:
[php]
.. code-block:: php
<?php
$em = $this->doctrine->em;
That's all there is to it. Once you get the reference to your EntityManager do your Doctrine 2.0 voodoo as normal.
That's all there is to it. Once you get the reference to your
EntityManager do your Doctrine 2.0 voodoo as normal.
Note: If you do not choose to autoload the Doctrine library, you will need to put this line before you get a reference to it:
Note: If you do not choose to autoload the Doctrine library, you
will need to put this line before you get a reference to it:
[php]
.. code-block:: php
<?php
$this->load->library('doctrine');
Good luck!

View File

@ -0,0 +1,73 @@
SQL-Table Prefixes
==================
This recipe is intended as an example of implementing a
loadClassMetadata listener to provide a Table Prefix option for
your application. The method used below is not a hack, but fully
integrates into the Doctrine system, all SQL generated will include
the appropriate table prefix.
In most circumstances it is desirable to separate different
applications into individual databases, but in certain cases, it
may be beneficial to have a table prefix for your Entities to
separate them from other vendor products in the same database.
Implementing the listener
-------------------------
The listener in this example has been set up with the
DoctrineExtensions namespace. You create this file in your
library/DoctrineExtensions directory, but will need to set up
appropriate autoloaders.
.. code-block:: php
<?php
namespace DoctrineExtensions;
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefix
{
protected $_prefix = '';
public function __construct($prefix)
{
$this->_prefix = (string) $prefix;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$classMetadata->setTableName($this->_prefix . $classMetadata->getTableName());
}
}
Telling the EntityManager about our listener
--------------------------------------------
A listener of this type must be set up before the EntityManager has
been initialised, otherwise an Entity might be created or cached
before the prefix has been set.
.. note::
If you set this listener up, be aware that you will need
to clear your caches and drop then recreate your database schema.
.. code-block:: php
<?php
// $connectionOptions and $config set earlier
$evm = new \Doctrine\Common\EventManager;
// Table Prefix
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);

View File

@ -1,39 +1,75 @@
Strategy-Pattern
================
This recipe will give you a short introduction on how to design similar entities without using expensive (i.e. slow) inheritance but with not more than
* the well-known strategy pattern
* event listeners
This recipe will give you a short introduction on how to design
similar entities without using expensive (i.e. slow) inheritance
but with not more than \* the well-known strategy pattern \* event
listeners
++ Scenario / Problem
Scenario / Problem
------------------
Given a Content-Management-System, we probably want to add / edit some so-called "blocks" and "panels". What are they for?
Given a Content-Management-System, we probably want to add / edit
some so-called "blocks" and "panels". What are they for?
* A block might be a registration form, some text content, a table with information. A good example might also be a small calendar.
* A panel is by definition a block that can itself contain blocks. A good example for a panel might be a sidebar box: You could easily add a small calendar into it.
So, in this scenario, when building your CMS, you will surely add lots of blocks and panels to your pages and you will find yourself highly uncomfortable because of the following:
- A block might be a registration form, some text content, a table
with information. A good example might also be a small calendar.
- A panel is by definition a block that can itself contain blocks.
A good example for a panel might be a sidebar box: You could easily
add a small calendar into it.
* Every existing page needs to know about the panels it contains - therefore, you'll have an association to your panels. But if you've got several types of panels - what do you do? Add an association to
every panel-type? This wouldn't be flexible. You might be tempted to add an AbstractPanelEntity and an AbstractBlockEntity that use class inheritance. Your page could then only confer to the AbstractPanelType and Doctrine 2 would do the rest for you, i.e. load the right entities. But - you'll for sure have lots of panels and blocks, and even worse, you'd have to edit the discriminator map *manually* every time you or another developer implements a new block / entity. This would tear down any effort of modular programming.
So, in this scenario, when building your CMS, you will surely add
lots of blocks and panels to your pages and you will find yourself
highly uncomfortable because of the following:
- Every existing page needs to know about the panels it contains -
therefore, you'll have an association to your panels. But if you've
got several types of panels - what do you do? Add an association to
every panel-type? This wouldn't be flexible. You might be tempted
to add an AbstractPanelEntity and an AbstractBlockEntity that use
class inheritance. Your page could then only confer to the
AbstractPanelType and Doctrine 2 would do the rest for you, i.e.
load the right entities. But - you'll for sure have lots of panels
and blocks, and even worse, you'd have to edit the discriminator
map *manually* every time you or another developer implements a new
block / entity. This would tear down any effort of modular
programming.
Therefore, we need something thats far more flexible.
++ Solution
Solution
--------
The solution itself is pretty easy. We will have one base class that will be loaded via the page and that has specific behaviour - a Block class might render the front-end and even the backend, for example. Now, every block that you'll write might look different or need different data - therefore, we'll offer an API to these methods but internally, we use a strategy that exactly knows what to do.
The solution itself is pretty easy. We will have one base class
that will be loaded via the page and that has specific behaviour -
a Block class might render the front-end and even the backend, for
example. Now, every block that you'll write might look different or
need different data - therefore, we'll offer an API to these
methods but internally, we use a strategy that exactly knows what
to do.
First of all, we need to make sure that we have an interface that contains every needed action. Such actions would be rendering the front-end or the backend, solving dependencies (blocks that are supposed to be placed in the sidebar could refuse to be placed in the middle of your page, for example).
First of all, we need to make sure that we have an interface that
contains every needed action. Such actions would be rendering the
front-end or the backend, solving dependencies (blocks that are
supposed to be placed in the sidebar could refuse to be placed in
the middle of your page, for example).
Such an interface could look like this:
[php]
/**
* This interface defines the basic actions that a block / panel needs to support.
*
* Every blockstrategy is *only* responsible for rendering a block and declaring some basic
* support, but *not* for updating its configuration etc. For this purpose, use controllers
* and models.
*/
interface BlockStrategyInterface {
.. code-block:: php
<?php
/**
* This interface defines the basic actions that a block / panel needs to support.
*
* Every blockstrategy is *only* responsible for rendering a block and declaring some basic
* support, but *not* for updating its configuration etc. For this purpose, use controllers
* and models.
*/
interface BlockStrategyInterface {
/**
* This could configure your entity
*/
@ -47,17 +83,16 @@ Such an interface could look like this:
/**
* Set the view object.
*
* @param \Zend_View_Interface $view
* @return \Zend_View_Helper_Interface
*/
public function setView(\Zend_View_Interface $view);
/**
* @return \Zend_View_Interface
*/
public function getView();
/**
* Renders this strategy. This method will be called when the user
* displays the site.
@ -65,7 +100,7 @@ Such an interface could look like this:
* @return string
*/
public function renderFrontend();
/**
* Renders the backend of this block. This method will be called when
* a user tries to reconfigure this block instance.
@ -83,27 +118,29 @@ Such an interface could look like this:
* @return array
*/
public function getRequiredPanelTypes();
/**
* Determines whether a Block is able to use a given type or not
* @param string $typeName The typename
* @return boolean
*/
public function canUsePanelType($typeName);
public function setBlockEntity(AbstractBlock $block);
public function getBlockEntity();
}
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
.. code-block:: php
[php]
/**
* This is the base class for both Panels and Blocks.
* It shouldn't be extended by your own blocks - simply write a strategy!
*/
abstract class AbstractBlock {
<?php
/**
* This is the base class for both Panels and Blocks.
* It shouldn't be extended by your own blocks - simply write a strategy!
*/
abstract class AbstractBlock {
/**
* The id of the block item instance
* This is a doctrine field, so you need to setup generation for it
@ -124,7 +161,8 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
protected $strategyClassName;
/**
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2.
*
* @var BlockStrategyInterface
*/
protected $strategyInstance;
@ -139,7 +177,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
@ -148,7 +186,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyInstance() {
return $this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
@ -161,35 +199,42 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
$strategy->setBlockEntity($this);
}
Now, the important point is that $strategyClassName is a Doctrine 2 field, i.e. Doctrine will persist this value. This is only the class name of your strategy and not an instance!
Now, the important point is that $strategyClassName is a Doctrine 2
field, i.e. Doctrine will persist this value. This is only the
class name of your strategy and not an instance!
Finishing your strategy pattern, we hook into the Doctrine postLoad event and check whether a block has been loaded. If so, you will initialize it - i.e. get the strategies classname, create an instance of it and set it via setStrategyBlock().
Finishing your strategy pattern, we hook into the Doctrine postLoad
event and check whether a block has been loaded. If so, you will
initialize it - i.e. get the strategies classname, create an
instance of it and set it via setStrategyBlock().
This might look like this:
[php]
.. code-block:: php
<?php
use \Doctrine\ORM,
\Doctrine\Common;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*/
class BlockStrategyEventListener implements Common\EventSubscriber {
protected $view;
public function __construct(\Zend_View_Interface $view) {
$this->view = $view;
}
public function getSubscribedEvents() {
return array(ORM\Events::postLoad);
}
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
$blockItem = $args->getEntity();
// Both blocks and panels are instances of Block\AbstractBlock
if ($blockItem instanceof Block\AbstractBlock) {
$strategy = $blockItem->getStrategyClassName();
@ -203,4 +248,7 @@ This might look like this:
}
}
In this example, even some variables are set - like a view object or a specific configuration object.
In this example, even some variables are set - like a view object
or a specific configuration object.

View File

@ -0,0 +1,137 @@
Validation of Entities
======================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
Doctrine 2 does not ship with any internal validators, the reason
being that we think all the frameworks out there already ship with
quite decent ones that can be integrated into your Domain easily.
What we offer are hooks to execute any kind of validation.
.. note::
You don't need to validate your entities in the lifecycle
events. Its only one of many options. Of course you can also
perform validations in value setters or any other method of your
entities that are used in your code.
Entities can register lifecycle event methods with Doctrine that
are called on different occasions. For validation we would need to
hook into the events called before persisting and updating. Even
though we don't support validation out of the box, the
implementation is even simpler than in Doctrine 1 and you will get
the additional benefit of being able to re-use your validation in
any other part of your domain.
Say we have an ``Order`` with several ``OrderLine`` instances. We
never want to allow any customer to order for a larger sum than he
is allowed to:
.. code-block:: php
<?php
class Order
{
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines AS $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
}
}
Now this is some pretty important piece of business logic in your
code, enforcing it at any time is important so that customers with
a unknown reputation don't owe your business too much money.
We can enforce this constraint in any of the metadata drivers.
First Annotations:
.. code-block:: php
<?php
/**
* @Entity
* @HasLifecycleCallbacks
*/
class Order
{
/**
* @PrePersist @PreUpdate
*/
public function assertCustomerAllowedBuying() {}
}
In XML Mappings:
.. code-block:: xml
<doctrine-mapping>
<entity name="Order">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
</lifecycle-callbacks>
</entity>
</doctirne-mapping>
YAML needs some little change yet, to allow multiple lifecycle
events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
Exception that happens in the lifecycle callbacks will be cached by
the EntityManager and the current transaction is rolled back.
Of course you can do any type of primitive checks, not null,
email-validation, string size, integer and date ranges in your
validation callbacks.
.. code-block:: php
<?php
class Order
{
/**
* @PrePersist @PreUpdate
*/
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}
}
}
What is nice about lifecycle events is, you can also re-use the
methods at other places in your domain, for example in combination
with your form library. Additionally there is no limitation in the
number of methods you register on one particular event, i.e. you
can register multiple methods for validation in "PrePersist" or
"PreUpdate" or mix and share them in any combinations between those
two events.
There is no limit to what you can and can't validate in
"PrePersist" and "PreUpdate" as long as you don't create new entity
instances. This was already discussed in the previous blog post on
the Versionable extension, which requires another type of event
called "onFlush".
Further readings: :doc:`Lifecycle Events <../reference/events>`

View File

@ -0,0 +1,168 @@
Working with DateTime Instances
===============================
There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
DateTime changes are detected by Reference
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities
and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object"))
these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database:
.. code-block:: php
<?php
/** @Entity */
class Article
{
/** @Column(type="datetime") */
private $updated;
public function setUpdated()
{
// will NOT be saved in the database
$this->updated->modify("now");
}
}
The way to go would be:
.. code-block:: php
<?php
class Article
{
public function setUpdated()
{
// WILL be saved in the database
$this->updated = new \DateTime("now");
}
}
Default Timezone Gotcha
~~~~~~~~~~~~~~~~~~~~~~~
By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that
is created by Doctrine will be assigned the timezone that is currently the default, either through
the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
This is very important to handle correctly if your application runs on different serves or is moved from one to another server
(with different timezone settings). You have to make sure that the timezone is the correct one
on all this systems.
Handling different Timezones with the DateTime Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you first come across the requirement to save different you are still optimistic to manage this mess,
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
in Databases <http://derickrethans.nl/storing-date-time-in-database.html>`_.
The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
in different offset directions depending on the real location.
The solution for this dialemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround
that even allows correct date-time handling with timezones:
1. Always convert any DateTime instance to UTC.
2. Only set Timezones for displaying purposes
3. Save the Timezone in the Entity for persistence.
Say we have an application for an international postal company and employees insert events regarding postal-package
around the world, in their current timezones. To determine the exact time an event occoured means to save both
the UTC time at the time of the booking and the timezone the event happend in.
.. code-block:: php
<?php
namespace DoctrineExtensions\DBAL\Types;
use Doctrine\DBAL\Platforms\AbtractPlatform;
use Doctrine\DBAL\Types\ConversionException;
class UTCDateTimeType extends DateTimeType
{
static private $utc = null;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}
return $value->format($platform->getDateTimeFormatString(),
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone(\DateTimeZone::UTC))
);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}
$val = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone(\DateTimeZone::UTC))
);
if (!$val) {
throw ConversionException::conversionFailed($value, $this->getName());
}
return $val;
}
}
This database type makes sure that every DateTime instance is always saved in UTC, relative
to the current timezone that the passed DateTime instance has. To be able to transform these values
back into their real timezone you have to save the timezone in a seperate field of the entity
requiring timezoned datetimes:
.. code-block:: php
<?php
namespace Shipping;
/**
* @Entity
*/
class Event
{
/** @Column(type="datetime") */
private $created;
/** @Column(type="string") */
private $timezone;
/**
* @var bool
*/
private $localized = false;
public function __construct(\DateTime $createDate)
{
$this->localized = true;
$this->created = $createDate;
$this->timezone = $createDate->getTimeZone()->getName();
}
public function getCreated()
{
if (!$this->localized) {
$this->created->setTimeZone(new \DateTimeZone($this->timezone));
}
return $this->created;
}
}
This snippet makes use of the previously discussed "changeset by reference only" property of
objects. That means a new DateTime will only be used during updating if the reference
changes between retrieval and flush operation. This means we can easily go and modify
the instance by setting the previous local timezone.

71
en/index.rst Normal file
View File

@ -0,0 +1,71 @@
Welcome to Doctrine 2 ORM's documentation!
==========================================
Reference Guide
---------------
.. toctree::
:maxdepth: 1
:numbered:
reference/introduction
reference/architecture
reference/configuration
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/transactions-and-concurrency
reference/events
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-objects
reference/xml-mapping
reference/yaml-mapping
reference/annotations-reference
reference/php-mapping
reference/caching
reference/improving-performance
reference/best-practices
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
Tutorials
---------
.. toctree::
:maxdepth: 1
tutorials/getting-started-xml-edition
Cookbook
--------
.. toctree::
:maxdepth: 1
cookbook/aggregate-fields
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/integrating-with-codeigniter
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

113
en/make.bat Normal file
View File

@ -0,0 +1,113 @@
@ECHO OFF
REM Command file for Sphinx documentation
set SPHINXBUILD=sphinx-build
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

View File

@ -0,0 +1,861 @@
Annotations Reference
=====================
In this chapter a reference of every Doctrine 2 Annotation is given
with short explanations on their context and usage.
Index
-----
- :ref:`@Column <annref_column>`
- :ref:`@ChangeTrackingPolicy <annref_changetrackingpolicy>`
- :ref:`@DiscriminatorColumn <annref_discriminatorcolumn>`
- :ref:`@DiscriminatorMap <annref_discriminatormap>`
- :ref:`@Entity <annref_entity>`
- :ref:`@GeneratedValue <annref_generatedvalue>`
- :ref:`@HasLifecycleCallbacks <annref_haslifecyclecallbacks>`
- :ref:`@Index <annref_index>`
- :ref:`@Id <annref_id>`
- :ref:`@InheritanceType <annref_inheritancetype>`
- :ref:`@JoinColumn <annref_joincolumn>`
- :ref:`@JoinTable <annref_jointable>`
- :ref:`@ManyToOne <annref_manytoone>`
- :ref:`@ManyToMany <annref_manytomany>`
- :ref:`@MappedSuperclass <annref_mappedsuperclass>`
- :ref:`@OneToOne <annref_onetoone>`
- :ref:`@OneToMany <annref_onetomany>`
- :ref:`@OrderBy <annref_orderby>`
- :ref:`@PostLoad <annref_postload>`
- :ref:`@PostPersist <annref_postpersist>`
- :ref:`@PostRemove <annref_postremove>`
- :ref:`@PostUpdate <annref_postupdate>`
- :ref:`@PrePersist <annref_prepersist>`
- :ref:`@PreRemove <annref_preremove>`
- :ref:`@PreUpdate <annref_preupdate>`
- :ref:`@SequenceGenerator <annref_sequencegenerator>`
- :ref:`@Table <annref_table>`
- :ref:`@UniqueConstraint <annref_uniqueconstraint>`
- :ref:`@Version <annref_version>`
Reference
---------
.. _annref_column:
@Column
~~~~~~~
Marks an annotated instance variable as "persistent". It has to be
inside the instance variables PHP DocBlock comment. Any value hold
inside this variable will be saved to and loaded from the database
as part of the lifecycle of the instance variables entity-class.
Required attributes:
- type - Name of the Doctrine Type which is converted between PHP
and Database representation.
Optional attributes:
- name - By default the property name is used for the database
column name also, however the 'name' attribute allows you to
determine the column name.
- length - Used by the "string" type to determine its maximum
length in the database. Doctrine does not validate the length of a
string values for you.
- precision - The precision for a decimal (exact numeric) column
(Applies only for decimal column)
- scale - The scale for a decimal (exact numeric) column (Applies
only for decimal column)
- unique - Boolean value to determine if the value of the column
should be unique across all rows of the underlying entities table.
- nullable - Determines if NULL values allowed for this column.
- columnDefinition - DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features.
However you should make careful use of this feature and the
consequences. Additionally you should remember that the "type"
attribute still handles the conversion between PHP and Database
values. If you use this attribute on a column that is used for
joins between tables you should also take a look at
:ref:`@JoinColumn <annref_joincolumn>`.
Examples:
.. code-block:: php
<?php
/**
* @Column(type="string", length=32, unique=true, nullable=false)
*/
protected $username;
/**
* @Column(type="string", columnDefinition="CHAR(2) NOT NULL")
*/
protected $country;
/**
* @Column(type="decimal", precision=2, scale=1)
*/
protected $height;
.. _annref_changetrackingpolicy:
@ChangeTrackingPolicy
~~~~~~~~~~~~~~~~~~~~~
The Change Tracking Policy annotation allows to specify how the
Doctrine 2 UnitOfWork should detect changes in properties of
entities during flush. By default each entity is checked according
to a deferred implicit strategy, which means upon flush UnitOfWork
compares all the properties of an entity to a previously stored
snapshot. This works out of the box, however you might want to
tweak the flush performance where using another change tracking
policy is an interesting option.
The :doc:`details on all the available change tracking policies <change-tracking-policies>`
can be found in the configuration section.
Example:
.. code-block:: php
<?php
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_IMPLICIT")
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
* @ChangeTrackingPolicy("NOTIFY")
*/
class User {}
.. _annref_discriminatorcolumn:
@DiscrimnatorColumn
~~~~~~~~~~~~~~~~~~~~~
This annotation is a required annotation for the topmost/super
class of an inheritance hierarchy. It specifies the details of the
column which saves the name of the class, which the entity is
actually instantiated as.
Required attributes:
- name - The column name of the discriminator. This name is also
used during Array hydration as key to specify the class-name.
Optional attributes:
- type - By default this is string.
- length - By default this is 255.
.. _annref_discriminatormap:
@DiscriminatorMap
~~~~~~~~~~~~~~~~~~~~~
The discriminator map is a required annotation on the
top-most/super class in an inheritance hierarchy. It takes an array
as only argument which defines which class should be saved under
which name in the database. Keys are the database value and values
are the classes, either as fully- or as unqualified class names
depending if the classes are in the namespace or not.
.. code-block:: php
<?php
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
.. _annref_entity:
@Entity
~~~~~~~
Required annotation to mark a PHP class as Entity. Doctrine manages
the persistence of all classes marked as entity.
Optional attributes:
- repositoryClass - Specifies the FQCN of a subclass of the
Doctrine. Use of repositories for entities is encouraged to keep
specialized DQL and SQL operations separated from the Model/Domain
Layer.
Example:
.. code-block:: php
<?php
/**
* @Entity(repositoryClass="MyProject\UserRepository")
*/
class User
{
//...
}
.. _annref_generatedvalue:
@GeneratedValue
~~~~~~~~~~~~~~~~~~~~~
Specifies which strategy is used for identifier generation for an
instance variable which is annotated by :ref:`@Id <annref_id>`. This
annotation is optional and only has meaning when used in
conjunction with @Id.
If this annotation is not specified with @Id the NONE strategy is
used as default.
Required attributes:
- strategy - Set the name of the identifier generation strategy.
Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE.
Example:
.. code-block:: php
<?php
/**
* @Id
* @Column(type="integer")
* @generatedValue(strategy="IDENTITY")
*/
protected $id = null;
.. _annref_haslifecyclecallbacks:
@HasLifecycleCallbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Annotation which has to be set on the entity-class PHP DocBlock to
notify Doctrine that this entity has entity life-cycle callback
annotations set on at least one of its methods. Using @PostLoad,
@PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate or
@PostUpdate without this marker annotation will make Doctrine
ignore the callbacks.
Example:
.. code-block:: php
<?php
/**
* @Entity
* @HasLifecycleCallbacks
*/
class User
{
/**
* @PostPersist
*/
public function sendOptinMail() {}
}
.. _annref_index:
@Index
~~~~~~~
Annotation is used inside the :ref:`@Table <annref_table>` annotation on
the entity-class level. It allows to hint the SchemaTool to
generate a database index on the specified table columns. It only
has meaning in the SchemaTool schema generation context.
Required attributes:
- name - Name of the Index
- columns - Array of columns.
Example:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",indexes={@index(name="search_idx", columns={"name", "email"})})
*/
class ECommerceProduct
{
}
.. _annref_id:
@Id
~~~~~~~
The annotated instance variable will be marked as entity
identifier, the primary key in the database. This annotation is a
marker only and has no required or optional attributes. For
entities that have multiple identifier columns each column has to
be marked with @Id.
Example:
.. code-block:: php
<?php
/**
* @Id
* @Column(type="integer")
*/
protected $id = null;
.. _annref_inheritancetype:
@InheritanceType
~~~~~~~~~~~~~~~~~~~~~
In an inheritance hierarchy you have to use this annotation on the
topmost/super class to define which strategy should be used for
inheritance. Currently Single Table and Class Table Inheritance are
supported.
This annotation has always been used in conjunction with the
:ref:`@DiscriminatorMap <annref_discriminatormap>` and
:ref:`@DiscriminatorColumn <annref_discriminatorcolumn>` annotations.
Examples:
.. code-block:: php
<?php
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
.. _annref_joincolumn:
@JoinColumn
~~~~~~~~~~~~~~
This annotation is used in the context of relations in
:ref:`@ManyToOne <annref_manytoone>`, :ref:`@OneToOne <annref_onetoone>` fields
and in the Context of :ref:`@JoinTable <annref_jointable>` nested inside
a @ManyToMany. This annotation is not required. If its not
specified the attributes *name* and *referencedColumnName* are
inferred from the table and primary key names.
Required attributes:
- name - Column name that holds the foreign key identifier for
this relation. In the context of @JoinTable it specifies the column
name in the join table.
- referencedColumnName - Name of the primary key identifier that
is used for joining of this relation.
Optional attributes:
- unique - Determines if this relation exclusive between the
affected entities and should be enforced so on the database
constraint level. Defaults to false.
- nullable - Determine if the related entity is required, or if
null is an allowed state for the relation. Defaults to true.
- onDelete - Cascade Action (Database-level)
- onUpdate - Cascade Action (Database-level)
- columnDefinition - DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features. Using
this attribute on @JoinColumn is necessary if you need slightly
different column definitions for joining columns, for example
regarding NULL/NOT NULL defaults. However by default a
"columnDefinition" attribute on :ref:`@Column <annref_column>` also sets
the related @JoinColumn's columnDefinition. This is necessary to
make foreign keys work.
Example:
.. code-block:: php
<?php
/**
* @OneToOne(targetEntity="Customer")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
.. _annref_joincolumns:
@JoinColumns
~~~~~~~~~~~~~~
An array of @JoinColumn annotations for a
:ref:`@ManyToOne <annref_manytoone>` or :ref:`@OneToOne <annref_onetoone>`
relation with an entity that has multiple identifiers.
.. _annref_jointable:
@JoinTable
~~~~~~~~~~~~~~
Using :ref:`@OneToMany <annref_onetomany>` or
:ref:`@ManyToMany <annref_manytomany>` on the owning side of the relation
requires to specify the @JoinTable annotation which describes the
details of the database join table. If you do not specify
@JoinTable on these relations reasonable mapping defaults apply
using the affected table and the column names.
Required attributes:
- name - Database name of the join-table
- joinColumns - An array of @JoinColumn annotations describing the
join-relation between the owning entities table and the join table.
- inverseJoinColumns - An array of @JoinColumn annotations
describing the join-relation between the inverse entities table and
the join table.
Optional attributes:
- schema - Database schema name of this table.
Example:
.. code-block:: php
<?php
/**
* @ManyToMany(targetEntity="Phonenumber")
* @JoinTable(name="users_phonenumbers",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
*/
public $phonenumbers;
.. _annref_manytoone:
@ManyToOne
~~~~~~~~~~~~~~
Defines that the annotated instance variable holds a reference that
describes a many-to-one relationship between two entities.
Required attributes:
- targetEntity - FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- cascade - Cascade Option
- fetch - One of LAZY or EAGER
- inversedBy - The inversedBy attribute designates the field in
the entity that is the inverse side of the relationship.
Example:
.. code-block:: php
<?php
/**
* @ManyToOne(targetEntity="Cart", cascade={"ALL"}, fetch="EAGER")
*/
private $cart;
.. _annref_manytomany:
@ManyToMany
~~~~~~~~~~~~~~
Defines an instance variable holds a many-to-many relationship
between two entities. :ref:`@JoinTable <annref_jointable>` is an
additional, optional annotation that has reasonable default
configuration values using the table and names of the two related
entities.
Required attributes:
- targetEntity - FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- mappedBy - This option specifies the property name on the
targetEntity that is the owning side of this relation. Its a
required attribute for the inverse side of a relationship.
- inversedBy - The inversedBy attribute designates the field in the
entity that is the inverse side of the relationship.
- cascade - Cascade Option
- fetch - One of LAZY or EAGER
**NOTE** For ManyToMany bidirectional relationships either side may
be the owning side (the side that defines the @JoinTable and/or
does not make use of the mappedBy attribute, thus using a default
join table).
Example:
.. code-block:: php
<?php
/**
* Owning Side
*
* @ManyToMany(targetEntity="Group", inversedBy="features")
* @JoinTable(name="user_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
private $groups;
/**
* Inverse Side
*
* @ManyToMany(targetEntity="User", mappedBy="groups")
*/
private $features;
.. _annref_mappedsuperclass:
@MappedSuperclass
~~~~~~~~~~~~~~~~~~~~~
An mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. This annotation is specified on
the Class docblock and has no additional attributes.
The @MappedSuperclass annotation cannot be used in conjunction with
@Entity. See the Inheritance Mapping section for
:doc:`more details on the restrictions of mapped superclasses <inheritance-mapping>`.
.. _annref_onetoone:
@OneToOne
~~~~~~~~~~~~~~
The @OneToOne annotation works almost exactly as the
:ref:`@ManyToOne <annref_manytoone>` with one additional option that can
be specified. The configuration defaults for
:ref:`@JoinColumn <annref_joincolumn>` using the target entity table and
primary key column names apply here too.
Required attributes:
- targetEntity - FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- cascade - Cascade Option
- fetch - One of LAZY or EAGER
- orphanRemoval - Boolean that specifies if orphans, inverse
OneToOne entities that are not connected to any owning instance,
should be removed by Doctrine. Defaults to false.
- inversedBy - The inversedBy attribute designates the field in the
entity that is the inverse side of the relationship.
Example:
.. code-block:: php
<?php
/**
* @OneToOne(targetEntity="Customer")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
.. _annref_onetomany:
@OneToMany
~~~~~~~~~~~~~~
Required attributes:
- targetEntity - FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- cascade - Cascade Option
- orphanRemoval - Boolean that specifies if orphans, inverse
OneToOne entities that are not connected to any owning instance,
should be removed by Doctrine. Defaults to false.
- mappedBy - This option specifies the property name on the
targetEntity that is the owning side of this relation. Its a
required attribute for the inverse side of a relationship.
Example:
.. code-block:: php
<?php
/**
* @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true)
*/
public $phonenumbers;
.. _annref_orderby:
@OrderBy
~~~~~~~~~~~~~~
Optional annotation that can be specified with a
:ref:`@ManyToMany <annref_manytomany>` or :ref:`@OneToMany <annref_onetomany>`
annotation to specify by which criteria the collection should be
retrieved from the database by using an ORDER BY clause.
This annotation requires a single non-attributed value with an DQL
snippet:
Example:
.. code-block:: php
<?php
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
*/
private $groups;
The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
.. _annref_postload:
@PostLoad
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a @PostLoad event.
Only works with @HasLifecycleCallbacks in the entity class PHP
DocBlock.
.. _annref_postpersist:
@PostPersist
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a @PostPersist event.
Only works with @HasLifecycleCallbacks in the entity class PHP
DocBlock.
.. _annref_postremove:
@PostRemove
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a @PostRemove event.
Only works with @HasLifecycleCallbacks in the entity class PHP
DocBlock.
.. _annref_postupdate:
@PostUpdate
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a @PostUpdate event.
Only works with @HasLifecycleCallbacks in the entity class PHP
DocBlock.
.. _annref_prepersist:
@PrePersist
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a @PrePersist event.
Only works with @HasLifecycleCallbacks in the entity class PHP
DocBlock.
.. _annref_preremove:
@PreRemove
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a @PreRemove event.
Only works with @HasLifecycleCallbacks in the entity class PHP
DocBlock.
.. _annref_preupdate:
@PreUpdate
~~~~~~~~~~~~~~
Marks a method on the entity to be called as a @PreUpdate event.
Only works with @HasLifecycleCallbacks in the entity class PHP
DocBlock.
.. _annref_sequencegenerator:
@SequenceGenerator
~~~~~~~~~~~~~~~~~~~~~
For the use with @generatedValue(strategy="SEQUENCE") this
annotation allows to specify details about the sequence, such as
the increment size and initial values of the sequence.
Required attributes:
- sequenceName - Name of the sequence
Optional attributes:
- allocationSize - Increment the sequence by the allocation size
when its fetched. A value larger than 1 allows to optimize for
scenarios where you create more than one new entity per request.
Defaults to 10
- initialValue - Where does the sequence start, defaults to 1.
Example:
.. code-block:: php
<?php
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @Column(type="integer")
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
.. _annref_table:
@Table
~~~~~~~
Annotation describes the table an entity is persisted in. It is
placed on the entity-class PHP DocBlock and is optional. If it is
not specified the table name will default to the entities
unqualified classname.
Required attributes:
- name - Name of the table
Optional attributes:
- schema - Database schema name of this table.
- indexes - Array of @Index annotations
- uniqueConstraints - Array of @UniqueConstraint annotations.
Example:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="user",
* uniqueConstraints={@UniqueConstraint(name="user_unique",columns={"username"})},
* indexes={@Index(name="user_idx", columns={"email"})}
* )
*/
class User { }
.. _annref_uniqueconstraint:
@UniqueConstraint
~~~~~~~~~~~~~~~~~~~~~
Annotation is used inside the :ref:`@Table <annref_table>` annotation on
the entity-class level. It allows to hint the SchemaTool to
generate a database unique constraint on the specified table
columns. It only has meaning in the SchemaTool schema generation
context.
Required attributes:
- name - Name of the Index
- columns - Array of columns.
Example:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"})})
*/
class ECommerceProduct
{
}
.. _annref_version:
@Version
~~~~~~~~~~~~~~
Marker annotation that defines a specified column as version
attribute used in an optimistic locking scenario. It only works on
:ref:`@Column <annref_column>` annotations that have the type integer or
datetime. Combining @Version with :ref:`@Id <annref_id>` is not supported.
Example:
.. code-block:: php
<?php
/**
* @column(type="integer")
* @version
*/
protected $version;

View File

@ -0,0 +1,130 @@
Architecture
============
This chapter gives an overview of the overall architecture,
terminology and constraints of Doctrine 2. It is recommended to
read this chapter carefully.
Entities
--------
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class must not be final or contain final methods.
- All persistent properties/field of any entity class should
always be private or protected, otherwise lazy-loading might not
work as expected.
- An entity class must not implement ``__clone`` or
`do so safely <http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone>`_.
- An entity class must not implement ``__wakeup`` or
`do so safely <http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone>`_.
Also consider implementing
`Serializable <http://de3.php.net/manual/en/class.serializable.php]>`_
instead.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
must not have a mapped field with the same name as an already
mapped field that is inherited from A.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
entities. Entities may extend non-entity classes as well as entity
classes, and non-entity classes may extend entity classes.
**TIP** The constructor of an entity is only ever invoked when
*you* construct a new instance with the *new* keyword. Doctrine
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
Entity states
~~~~~~~~~~~~~
An entity instance can be characterized as being NEW, MANAGED,
DETACHED or REMOVED.
- A NEW entity instance has no persistent identity, and is not yet
associated with an EntityManager and a UnitOfWork (i.e. those just
created with the "new" operator).
- A MANAGED entity instance is an instance with a persistent
identity that is associated with an EntityManager and whose
persistence is thus managed.
- A DETACHED entity instance is an instance with a persistent
identity that is not (or no longer) associated with an
EntityManager and a UnitOfWork.
- A REMOVED entity instance is an instance with a persistent
identity, associated with an EntityManager, that will be removed
from the database upon transaction commit.
.. _architecture_persistent_fields:
Persistent fields
~~~~~~~~~~~~~~~~~
The persistent state of an entity is represented by instance
variables. An instance variable must be directly accessed only from
within the methods of the entity by the entity instance itself.
Instance variables must not be accessed by clients of the entity.
The state of the entity is available to clients only through the
entitys methods, i.e. accessor methods (getter/setter methods) or
other business methods.
Collection-valued persistent fields and properties must be defined
in terms of the ``Doctrine\Common\Collections\Collection``
interface. The collection implementation type may be used by the
application to initialize fields or properties before the entity is
made persistent. Once the entity becomes managed (or detached),
subsequent access must be through the interface type.
Serializing entities
~~~~~~~~~~~~~~~~~~~~
Serializing entities can be problematic and is not really
recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an
EntityManager. If you intend to serialize (and unserialize) entity
instances that still hold references to proxy objects you may run
into problems with private properties because of technical
limitations. Proxy objects implement ``__sleep`` and it is not
possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us).
The EntityManager
-----------------
The ``EntityManager`` class is a central access point to the ORM
functionality provided by Doctrine 2. The ``EntityManager`` API is
used to manage the persistence of your objects and to query for
persistent objects.
Transactional write-behind
~~~~~~~~~~~~~~~~~~~~~~~~~~
An ``EntityManager`` and the underlying ``UnitOfWork`` employ a
strategy called "transactional write-behind" that delays the
execution of SQL statements in order to execute them in the most
efficient way and to execute them at the end of a transaction so
that all write locks are quickly released. You should see Doctrine
as a tool to synchronize your in-memory objects with the database
in well defined units of work. Work with your objects and modify
them as usual and when you're done call ``EntityManager#flush()``
to make your changes persistent.
The Unit of Work
~~~~~~~~~~~~~~~~
Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a
typical implementation of the
`Unit of Work pattern <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.

View File

@ -0,0 +1,497 @@
Basic Mapping
=============
This chapter explains the basic mapping of objects and properties.
Mapping of associations will be covered in the next chapter
"Association Mapping".
Mapping Drivers
---------------
Doctrine provides several different ways for specifying
object-relational mapping metadata:
- Docblock Annotations
- XML
- YAML
This manual usually uses docblock annotations in all the examples
that are spread throughout all chapters. There are dedicated
chapters for XML and YAML mapping, respectively.
.. note::
If you're wondering which mapping driver gives the best
performance, the answer is: They all give exactly the same performance.
Once the metadata of a class has
been read from the source (annotations, xml or yaml) it is stored
in an instance of the ``Doctrine\ORM\Mapping\ClassMetadata`` class
and these instances are stored in the metadata cache. Therefore at
the end of the day all drivers perform equally well. If you're not
using a metadata cache (not recommended!) then the XML driver might
have a slight edge in performance due to the powerful native XML
support in PHP.
Introduction to Docblock Annotations
------------------------------------
You've probably used docblock annotations in some form already,
most likely to provide documentation metadata for a tool like
``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a
tool to embed metadata inside the documentation section which can
then be processed by some tool. Doctrine 2 generalizes the concept
of docblock annotations so that they can be used for any kind of
metadata and so that it is easy to define new docblock annotations.
In order to allow more involved annotation values and to reduce the
chances of clashes with other docblock annotations, the Doctrine 2
docblock annotations feature an alternative syntax that is heavily
inspired by the Annotation syntax introduced in Java 5.
The implementation of these enhanced docblock annotations is
located in the ``Doctrine\Common\Annotations`` namespace and
therefore part of the Common package. Doctrine 2 docblock
annotations support namespaces and nested annotations among other
things. The Doctrine 2 ORM defines its own set of docblock
annotations for supplying object-relational mapping metadata.
**NOTE** If you're not comfortable with the concept of docblock
annotations, don't worry, as mentioned earlier Doctrine 2 provides
XML and YAML alternatives and you could easily implement your own
favourite mechanism for defining ORM metadata.
Persistent classes
------------------
In order to mark a class for object-relational persistence it needs
to be designated as an entity. This can be done through the
``@Entity`` marker annotation.
.. code-block:: php
<?php
/** @Entity */
class MyPersistentClass
{
//...
}
By default, the entity will be persisted to a table with the same
name as the class name. In order to change that, you can use the
``@Table`` annotation as follows:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="my_persistent_class")
*/
class MyPersistentClass
{
//...
}
Now instances of MyPersistentClass will be persisted into a table
named ``my_persistent_class``.
Doctrine Mapping Types
----------------------
A Doctrine Mapping Type defines the mapping between a PHP type and
an SQL type. All Doctrine Mapping Types that ship with Doctrine are
fully portable between different RDBMS. You can even write your own
custom mapping types that might or might not be portable, which is
explained later in this chapter.
For example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to an SQL VARCHAR (or VARCHAR2 etc.
depending on the RDBMS brand). Here is a quick overview of the
built-in mapping 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 a SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``array``: Type that maps a SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``float``: Type that maps a SQL Float (Double Precision) to a
PHP double. *IMPORTANT*: Works only with locale settings that use
decimal points as separator.
.. note::
Doctrine Mapping Types are NOT SQL types and NOT PHP
types! They are mapping types between 2 types.
.. warning::
Mapping types are *case-sensitive*. For example, using
a DateTime column will NOT match the datetime type that ships with
Doctrine 2!
Property Mapping
----------------
After a class has been marked as an entity it can specify mappings
for its instance fields. Here we will only look at simple fields
that hold scalar values like strings, numbers, etc. Associations to
other objects are covered in the chapter "Association Mapping".
To mark a property for relational persistence the ``@Column``
docblock annotation is used. This annotation usually requires at
least 1 attribute to be set, the ``type``. The ``type`` attribute
specifies the Doctrine Mapping Type to use for the field. If the
type is not specified, 'string' is used as the default mapping type
since it is the most flexible.
Example:
.. code-block:: php
<?php
/** @Entity */
class MyPersistentClass
{
/** @Column(type="integer") */
private $id;
/** @Column(length=50) */
private $name; // type defaults to string
//...
}
In that example we mapped the field ``id`` to the column ``id``
using the mapping type ``integer`` and the field ``name`` is mapped
to the column ``name`` with the default mapping type ``string``. As
you can see, by default the column names are assumed to be the same
as the field names. To specify a different name for the column, you
can use the ``name`` attribute of the Column annotation as
follows:
.. code-block:: php
<?php
/** @Column(name="db_name") */
private $name;
The Column annotation has some more attributes. Here is a complete
list:
- ``type``: (optional, defaults to 'string') The mapping type to
use for the column.
- ``name``: (optional, defaults to field name) The name of the
column in the database.
- ``length``: (optional, default 255) The length of the column in
the database. (Applies only if a string-valued column is used).
- ``unique``: (optional, default FALSE) Whether the column is a
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``precision``: (optional, default 0) The precision for a decimal
(exact numeric) column. (Applies only if a decimal column is used.)
- ``scale``: (optional, default 0) The scale for a decimal (exact
numeric) column. (Applies only if a decimal column is used.)
Custom Mapping Types
--------------------
Doctrine allows you to create new mapping types. This can come in
handy when you're missing a specific mapping type or when you want
to replace the existing implementation of a mapping type.
In order to create a new mapping type you need to subclass
``Doctrine\DBAL\Types\Type`` and implement/override the methods as
you wish. Here is an example skeleton of such a custom type class:
.. code-block:: php
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
class MyType extends Type
{
const MYTYPE = 'mytype'; // modify to match your type name
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
// return the SQL used to create your column type. To create a portable column type, use the $platform.
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
Restrictions to keep in mind:
- If the value of the field is *NULL* the method
``convertToDatabaseValue()`` is not called.
- The ``UnitOfWork`` never passes values to the database convert
method that did not change in the request.
When you have implemented the type you still need to let Doctrine
know about it. This can be achieved through the
``Doctrine\DBAL\Configuration#setCustomTypes(array $types)``
method. ``Doctrine\ORM\Configuration`` is a subclass of
``Doctrine\DBAL\Configuration``, so the methods are available on
your ORM Configuration instance as well.
Here is an example:
.. code-block:: php
<?php
// in bootstrapping code
// ...
use Doctrine\DBAL\Types\Type;
// ...
// Register my type
Type::addType('mytype', 'My\Project\Types\MyType');
As can be seen above, when registering the custom types in the
configuration you specify a unique name for the mapping type and
map that to the corresponding fully qualified class name. Now you
can use your new type in your mapping like this:
.. code-block:: php
<?php
class MyPersistentClass
{
/** @Column(type="mytype") */
private $field;
}
To have Schema-Tool convert the underlying database type of your
new "mytype" directly into an instance of ``MyType`` you have to
additionally register this mapping with your database platform:
.. code-block:: php
<?php
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
Now using Schema-Tool, whenever it detects a column having the
``db_mytype`` it will convert it into a ``mytype`` Doctrine Type
instance for Schema representation. Keep in mind that you can
easily produce clashes this way, each database type can only map to
exactly one Doctrine mapping type.
Identifiers / Primary Keys
--------------------------
Every entity class needs an identifier/primary key. You designate
the field that serves as the identifier with the ``@Id`` marker
annotation. Here is an example:
.. code-block:: php
<?php
class MyPersistentClass
{
/** @Id @Column(type="integer") */
private $id;
//...
}
Without doing anything else, the identifier is assumed to be
manually assigned. That means your code would need to properly set
the identifier property before passing a new entity to
``EntityManager#persist($entity)``.
A common alternative strategy is to use a generated value as the
identifier. To do this, you use the ``@GeneratedValue`` annotation
like this:
.. code-block:: php
<?php
class MyPersistentClass
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
}
This tells Doctrine to automatically generate a value for the
identifier. How this value is generated is specified by the
``strategy`` attribute, which is optional and defaults to 'AUTO'. A
value of ``AUTO`` tells Doctrine to use the generation strategy
that is preferred by the currently used database platform. See
below for details.
Identifier Generation Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The previous example showed how to use the default identifier
generation strategy without knowing the underlying database with
the AUTO-detection strategy. It is also possible to specify the
identifier generation strategy more explicitly, which allows to
make use of some additional features.
Here is the list of possible generation strategies:
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
preferred by the used database platform. The preferred strategies
are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle
and PostgreSQL. This strategy provides full portability.
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
generation. This strategy does currently not provide full
portability. Sequences are supported by Oracle and PostgreSql.
- ``IDENTITY``: Tells Doctrine to use special identity columns in
the database that generate a value on insertion of a row. This
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
***This strategy is not yet implemented!***
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
thus generated) by your code. The assignment must take place before
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the @GeneratedValue entirely.
Sequence Generator
^^^^^^^^^^^^^^^^^^
The Sequence Generator can currently be used in conjunction with
Oracle or Postgres and allows some additional configuration options
besides specifying the sequence's name:
.. code-block:: php
<?php
class User {
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(name="tablename_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
}
The initial value specifies at which value the sequence should
start.
The allocationSize is a powerful feature to optimize INSERT
performance of Doctrine. The allocationSize specifies by how much
values the sequence is incremented whenever the next value is
retrieved. If this is larger than 1 (one) Doctrine can generate
identifier values for the allocationSizes amount of entities. In
the above example with ``allocationSize=100`` Doctrine 2 would only
need to access the sequence once to generate the identifiers for
100 new entities.
*The default allocationSize for a @SequenceGenerator is currently 10.*
.. caution::
The allocationSize is detected by SchemaTool and
transformed into an "INCREMENT BY " clause in the CREATE SEQUENCE
statement. For a database schema created manually (and not
SchemaTool) you have to make sure that the allocationSize
configuration option is never larger than the actual sequences
INCREMENT BY value, otherwise you may get duplicate keys.
.. note::
It is possible to use strategy="AUTO" and at the same time
specifying a @SequenceGenerator. In such a case, your custom
sequence settings are used in the case where the preferred strategy
of the underlying platform is SEQUENCE, such as for Oracle and
PostgreSQL.
Composite Keys
~~~~~~~~~~~~~~
Doctrine 2 allows to use composite primary keys. There are however
some restrictions opposed to using a single identifier. The use of
the ``@GeneratedValue`` annotation is only supported for simple
(not composite) primary keys, which means you can only use
composite keys if you generate the primary key values yourself
before calling ``EntityManager#persist()`` on the entity.
To designate a composite primary key / identifier, simply put the
@Id marker annotation on all fields that make up the primary key.
Quoting Reserved Words
----------------------
It may sometimes be necessary to quote a column or table name
because it conflicts with a reserved word of the particular RDBMS
in use. This is often referred to as "Identifier Quoting". To let
Doctrine know that you would like a table or column name to be
quoted in all SQL statements, enclose the table or column name in
backticks. Here is an example:
.. code-block:: php
<?php
/** @Column(name="`number`", type="integer") */
private $number;
Doctrine will then quote this column name in all SQL statements
according to the used database platform.
.. warning::
Identifier Quoting is not supported for join column
names or discriminator column names.
.. warning::
Identifier Quoting is a feature that is mainly intended
to support legacy database schemas. The use of reserved words and
identifier quoting is generally discouraged. Identifier quoting
should not be used to enable the use non-standard-characters such
as a dash in a hypothetical column ``test-name``. Also Schema-Tool
will likely have troubles when quoting is used for case-sensitivity
reasons (in Oracle for example).

View File

@ -0,0 +1,177 @@
Batch Processing
================
This chapter shows you how to accomplish bulk inserts, updates and
deletes with Doctrine in an efficient way. The main problem with
bulk operations is usually not to run out of memory and this is
especially what the strategies presented here provide help with.
.. warning::
An ORM tool is not primarily well-suited for mass
inserts, updates or deletions. Every RDBMS has its own, most
effective way of dealing with such operations and if the options
outlined below are not sufficient for your purposes we recommend
you use the tools for your particular RDBMS for these bulk
operations.
Bulk Inserts
------------
Bulk inserts in Doctrine are best performed in batches, taking
advantage of the transactional write-behind behavior of an
``EntityManager``. The following code shows an example for
inserting 10000 objects with a batch size of 20. You may need to
experiment with the batch size to find the size that works best for
you. Larger batch sizes mean more prepared statement reuse
internally but also mean more work during ``flush``.
.. code-block:: php
<?php
$batchSize = 20;
for ($i = 1; $i <= 10000; ++$i) {
$user = new CmsUser;
$user->setStatus('user');
$user->setUsername('user' . $i);
$user->setName('Mr.Smith-' . $i);
$em->persist($user);
if (($i % $batchSize) == 0) {
$em->flush();
$em->clear(); // Detaches all objects from Doctrine!
}
}
Bulk Updates
------------
There are 2 possibilities for bulk updates with Doctrine.
DQL UPDATE
~~~~~~~~~~
The by far most efficient way for bulk updates is to use a DQL
UPDATE query. Example:
.. code-block:: php
<?php
$q = $em->createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9');
$numUpdated = $q->execute();
Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk updates is to use the
``Query#iterate()`` facility to iterate over the query results step
by step instead of loading the whole result into memory at once.
The following example shows how to do this, combining the iteration
with the batching strategy that was already used for bulk inserts:
.. code-block:: php
<?php
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach($iterableResult AS $row) {
$user = $row[0];
$user->increaseCredit();
$user->calculateNewBonuses();
if (($i % $batchSize) == 0) {
$em->flush(); // Executes all updates.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
.. note::
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Bulk Deletes
------------
There are two possibilities for bulk deletes with Doctrine. You can
either issue a single DQL DELETE query or you can iterate over
results removing them one at a time.
DQL DELETE
~~~~~~~~~~
The by far most efficient way for bulk deletes is to use a DQL
DELETE query.
Example:
.. code-block:: php
<?php
$q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000');
$numDeleted = $q->execute();
Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk deletes is to use the
``Query#iterate()`` facility to iterate over the query results step
by step instead of loading the whole result into memory at once.
The following example shows how to do this:
.. code-block:: php
<?php
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
while (($row = $iterableResult->next()) !== false) {
$em->remove($row[0]);
if (($i % $batchSize) == 0) {
$em->flush(); // Executes all deletions.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
.. note::
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Iterating Large Results for Data-Processing
-------------------------------------------
You can use the ``iterate()`` method just to iterate over a large
result and no UPDATE or DELETE intention. The ``IterableResult``
instance returned from ``$query->iterate()`` implements the
Iterator interface so you can process a large result without memory
problems using the following approach:
.. code-block:: php
<?php
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult AS $row) {
// do stuff with the data in the row, $row[0] is always the object
// detach from Doctrine, so that it can be Garbage-Collected immediately
$this->_em->detach($row[0]);
}
.. note::
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.

View File

@ -0,0 +1,127 @@
Best Practices
==============
The best practices mentioned here that affect database
design generally refer to best practices when working with Doctrine
and do not necessarily reflect best practices for database design
in general.
Don't use public properties on entities
---------------------------------------
It is very important that you don't map public properties on
entities, but only protected or private ones. The reason for this
is simple, whenever you access a public property of a proxy object
that hasn't been initialized yet the return value will be null.
Doctrine cannot hook into this process and magically make the
entity lazy load.
This can create situations where it is very hard to debug the
current failure. We therefore urge you to map only private and
protected properties on entities and use getter methods or magic
\_\_get() to access them.
Constrain relationships as much as possible
-------------------------------------------
It is important to constrain relationships as much as possible.
This means:
- Impose a traversal direction (avoid bidirectional associations
if possible)
- Eliminate nonessential associations
This has several benefits:
- Reduced coupling in your domain model
- Simpler code in your domain model (no need to maintain
bidirectionality properly)
- Less work for Doctrine
Avoid composite keys
--------------------
Even though Doctrine fully supports composite keys it is best not
to use them if possible. Composite keys require additional work by
Doctrine and thus have a higher probability of errors.
Use events judiciously
----------------------
The event system of Doctrine is great and fast. Even though making
heavy use of events, especially lifecycle events, can have a
negative impact on the performance of your application. Thus you
should use events judiciously.
Use cascades judiciously
------------------------
Automatic cascades of the persist/remove/merge/etc. operations are
very handy but should be used wisely. Do NOT simply add all
cascades to all associations. Think about which cascades actually
do make sense for you for a particular association, given the
scenarios it is most likely used in.
Don't use special characters
----------------------------
Avoid using any non-ASCII characters in class, field, table or
column names. Doctrine itself is not unicode-safe in many places
and will not be until PHP itself is fully unicode-aware (PHP6).
Don't use identifier quoting
----------------------------
Identifier quoting is a workaround for using reserved words that
often causes problems in edge cases. Do not use identifier quoting
and avoid using reserved words as table or column names.
Initialize collections in the constructor
-----------------------------------------
It is recommended best practice to initialize any business
collections in entities in the constructor. Example:
.. code-block:: php
<?php
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
private $addresses;
private $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;
}
}
Don't map foreign keys to fields in an entity
---------------------------------------------
Foreign keys have no meaning whatsoever in an object model. Foreign
keys are how a relational database establishes relationships. Your
object model establishes relationships through object references.
Thus mapping foreign keys to object fields heavily leaks details of
the relational model into the object model, something you really
should not do.
Use explicit transaction demarcation
------------------------------------
While Doctrine will automatically wrap all DML operations in a
transaction on flush(), it is considered best practice to
explicitly set the transaction boundaries yourself. Otherwise every
single query is wrapped in a small transaction (Yes, SELECT
queries, too) since you can not talk to your database outside of a
transaction. While such short transactions for read-only (SELECT)
queries generally don't have any noticeable performance impact, it
is still preferable to use fewer, well-defined transactions that
are established through explicit transaction boundaries.

488
en/reference/caching.rst Normal file
View File

@ -0,0 +1,488 @@
Caching
=======
Doctrine provides cache drivers in the ``Common`` package for some
of the most popular caching implementations such as APC, Memcache
and Xcache. We also provide an ``ArrayCache`` driver which stores
the data in a PHP array. Obviously, the cache does not live between
requests but this is useful for testing in a development
environment.
Cache Drivers
-------------
The cache drivers follow a simple interface that is defined in
``Doctrine\Common\Cache\Cache``. All the cache drivers extend a
base class ``Doctrine\Common\Cache\AbstractCache`` which implements
the before mentioned interface.
The interface defines the following methods for you to publicly
use.
- fetch($id) - Fetches an entry from the cache.
- contains($id) - Test if an entry exists in the cache.
- save($id, $data, $lifeTime = false) - Puts data into the cache.
- delete($id) - Deletes a cache entry.
Each driver extends the ``AbstractCache`` class which defines a few
abstract protected methods that each of the drivers must
implement.
- \_doFetch($id)
- \_doContains($id)
- \_doSave($id, $data, $lifeTime = false)
- \_doDelete($id)
The public methods ``fetch()``, ``contains()``, etc. utilize the
above protected methods that are implemented by the drivers. The
code is organized this way so that the protected methods in the
drivers do the raw interaction with the cache implementation and
the ``AbstractCache`` can build custom functionality on top of
these methods.
APC
~~~
In order to use the APC cache driver you must have it compiled and
enabled in your php.ini. You can read about APC
`in the PHP Documentation <http://us2.php.net/apc>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APC cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$cacheDriver->save('cache_id', 'my_data');
Memcache
~~~~~~~~
In order to use the Memcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcache
` on the PHP website <http://us2.php.net/memcache>`_. It will
give you a little background information about what it is and how
you can use it as well as how to install it.
Below is a simple example of how you could use the Memcache cache
driver by itself.
.. code-block:: php
<?php
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache()
$cacheDriver->save('cache_id', 'my_data');
Xcache
~~~~~~
In order to use the Xcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Xcache
`here <http://xcache.lighttpd.net/>`_. It will give you a little
background information about what it is and how you can use it as
well as how to install it.
Below is a simple example of how you could use the Xcache cache
driver by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
$cacheDriver->save('cache_id', 'my_data');
Using Cache Drivers
-------------------
In this section we'll describe how you can fully utilize the API of
the cache drivers to save cache, check if some cache exists, fetch
the cached data and delete the cached data. We'll use the
``ArrayCache`` implementation as our example here.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ArrayCache();
Saving
~~~~~~
To save some data to the cache driver it is as simple as using the
``save()`` method.
.. code-block:: php
<?php
$cacheDriver->save('cache_id', 'my_data');
The ``save()`` method accepts three arguments which are described
below.
- ``$id`` - The cache id
- ``$data`` - The cache entry/data.
- ``$lifeTime`` - The lifetime. If != false, sets a specific
lifetime for this cache entry (null => infinite lifeTime).
You can save any type of data whether it be a string, array,
object, etc.
.. code-block:: php
<?php
$array = array(
'key1' => 'value1',
'key2' => 'value2'
);
$cacheDriver->save('my_array', $array);
Checking
~~~~~~~~
Checking whether some cache exists is very simple, just use the
``contains()`` method. It accepts a single argument which is the ID
of the cache entry.
.. code-block:: php
<?php
if ($cacheDriver->contains('cache_id')) {
echo 'cache exists';
} else {
echo 'cache does not exist';
}
Fetching
~~~~~~~~
Now if you want to retrieve some cache entry you can use the
``fetch()`` method. It also accepts a single argument just like
``contains()`` which is the ID of the cache entry.
.. code-block:: php
<?php
$array = $cacheDriver->fetch('my_array');
Deleting
~~~~~~~~
As you might guess, deleting is just as easy as saving, checking
and fetching. We have a few ways to delete cache entries. You can
delete by an individual ID, regular expression, prefix, suffix or
you can delete all entries.
By Cache ID
^^^^^^^^^^^
.. code-block:: php
<?php
$cacheDriver->delete('my_array');
You can also pass wild cards to the ``delete()`` method and it will
return an array of IDs that were matched and deleted.
.. code-block:: php
<?php
$deleted = $cacheDriver->delete('users_*');
By Regular Expression
^^^^^^^^^^^^^^^^^^^^^
If you need a little more control than wild cards you can use a PHP
regular expression to delete cache entries.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteByRegex('/users_.*/');
By Prefix
^^^^^^^^^
Because regular expressions are kind of slow, if simply deleting by
a prefix or suffix is sufficient, it is recommended that you do
that instead of using a regular expression because it will be much
faster if you have many cache entries.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteByPrefix('users_');
By Suffix
^^^^^^^^^
Just like we did above with the prefix you can do the same with a
suffix.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteBySuffix('_my_account');
All
^^^
If you simply want to delete all cache entries you can do so with
the ``deleteAll()`` method.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteAll();
Counting
~~~~~~~~
If you want to count how many entries are stored in the cache
driver instance you can use the ``count()`` method.
.. code-block:: php
<?php
echo $cacheDriver->count();
.. note::
In order to use ``deleteByRegex()``, ``deleteByPrefix()``,
``deleteBySuffix()``, ``deleteAll()``, ``count()`` or ``getIds()``
you must enable an option for the cache driver to manage your cache
IDs internally. This is necessary because APC, Memcache, etc. don't
have any advanced functionality for fetching and deleting. We add
some functionality on top of the cache drivers to maintain an index
of all the IDs stored in the cache driver so that we can allow more
granular deleting operations.
::
<?php
$cacheDriver->setManageCacheIds(true);
Namespaces
~~~~~~~~~~
If you heavily use caching in your application and utilize it in
multiple parts of your application, or use it in different
applications on the same server you may have issues with cache
naming collisions. This can be worked around by using namespaces.
You can set the namespace a cache driver should use by using the
``setNamespace()`` method.
.. code-block:: php
<?php
$cacheDriver->setNamespace('my_namespace_');
Integrating with the ORM
------------------------
The Doctrine ORM package is tightly integrated with the cache
drivers to allow you to improve performance of various aspects of
Doctrine by just simply making some additional configurations and
method calls.
Query Cache
~~~~~~~~~~~
It is highly recommended that in a production environment you cache
the transformation of a DQL query to its SQL counterpart. It
doesn't make sense to do this parsing multiple times as it doesn't
change unless you alter the DQL query.
This can be done by configuring the query cache implementation to
use on your ORM configuration.
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Result Cache
~~~~~~~~~~~~
The result cache can be used to cache the results of your queries
so that we don't have to query the database or hydrate the data
again after the first time. You just need to configure the result
cache implementation.
.. code-block:: php
<?php
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now when you're executing DQL queries you can configure them to use
the result cache.
.. code-block:: php
<?php
$query = $em->createQuery('select u from \Entities\User u');
$query->useResultCache(true);
You can also configure an individual query to use a different
result cache driver.
.. code-block:: php
<?php
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
.. note::
Setting the result cache driver on the query will
automatically enable the result cache for the query. If you want to
disable it pass false to ``useResultCache()``.
::
<?php
$query->useResultCache(false);
If you want to set the time the cache has to live you can use the
``setResultCacheLifetime()`` method.
.. code-block:: php
<?php
$query->setResultCacheLifetime(3600);
The ID used to store the result set cache is a hash which is
automatically generated for you if you don't set a custom ID
yourself with the ``setResultCacheId()`` method.
.. code-block:: php
<?php
$query->setResultCacheId('my_custom_id');
You can also set the lifetime and cache ID by passing the values as
the second and third argument to ``useResultCache()``.
.. code-block:: php
<?php
$query->useResultCache(true, 3600, 'my_custom_id');
Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
YAML, XML, Annotations, etc. Instead of parsing this information on
each request we should cache it using one of the cache drivers.
Just like the query and result cache we need to configure it
first.
.. code-block:: php
<?php
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now the metadata information will only be parsed once and stored in
the cache driver.
Clearing the Cache
------------------
We've already shown you previously how you can use the API of the
cache drivers to manually delete cache entries. For your
convenience we offer a command line task for you to help you with
clearing the query, result and metadata cache.
From the Doctrine command line you can run the following command.
.. code-block:: php
$ ./doctrine clear-cache
Running this task with no arguments will clear all the cache for
all the configured drivers. If you want to be more specific about
what you clear you can use the following options.
To clear the query cache use the ``--query`` option.
.. code-block:: php
$ ./doctrine clear-cache --query
To clear the metadata cache use the ``--metadata`` option.
.. code-block:: php
$ ./doctrine clear-cache --metadata
To clear the result cache use the ``--result`` option.
.. code-block:: php
$ ./doctrine clear-cache --result
When you use the ``--result`` option you can use some other options
to be more specific about what queries result sets you want to
clear.
Just like the API of the cache drivers you can clear based on an
ID, regular expression, prefix or suffix.
.. code-block:: php
$ ./doctrine clear-cache --result --id=cache_id
Or if you want to clear based on a regular expressions.
.. code-block:: php
$ ./doctrine clear-cache --result --regex=users_.*
Or with a prefix.
.. code-block:: php
$ ./doctrine clear-cache --result --prefix=users_
And finally with a suffix.
.. code-block:: php
$ ./doctrine clear-cache --result --suffix=_my_account
.. note::
Using the ``--id``, ``--regex``, etc. options with the
``--query`` and ``--metadata`` are not allowed as it is not
necessary to be specific about what you clear. You only ever need
to completely clear the cache to remove stale entries.
Cache Slams
-----------
Something to be careful of when utilizing the cache drivers is
cache slams. If you have a heavily trafficked website with some
code that checks for the existence of a cache record and if it does
not exist it generates the information and saves it to the cache.
Now if 100 requests were issued all at the same time and each one
sees the cache does not exist and they all try and insert the same
cache entry it could lock up APC, Xcache, etc. and cause problems.
Ways exist to work around this, like pre-populating your cache and
not letting your users requests populate the cache.
You can read more about cache slams
`in this blog post <http://t3.dotgnu.info/blog/php/user-cache-timebomb>`_.

View File

@ -0,0 +1,151 @@
Change Tracking Policies
========================
Change tracking is the process of determining what has changed in
managed entities since the last time they were synchronized with
the database.
Doctrine provides 3 different change tracking policies, each having
its particular advantages and disadvantages. The change tracking
policy can be defined on a per-class basis (or more precisely,
per-hierarchy).
Deferred Implicit
~~~~~~~~~~~~~~~~~
The deferred implicit policy is the default change tracking policy
and the most convenient one. With this policy, Doctrine detects the
changes by a property-by-property comparison at commit time and
also detects changes to entities or new entities that are
referenced by other managed entities ("persistence by
reachability"). Although the most convenient policy, it can have
negative effects on performance if you are dealing with large units
of work (see "Understanding the Unit of Work"). Since Doctrine
can't know what has changed, it needs to check all managed entities
for changes every time you invoke EntityManager#flush(), making
this operation rather costly.
Deferred Explicit
~~~~~~~~~~~~~~~~~
The deferred explicit policy is similar to the deferred implicit
policy in that it detects changes through a property-by-property
comparison at commit time. The difference is that only entities are
considered that have been explicitly marked for change detection
through a call to EntityManager#persist(entity) or through a save
cascade. All other entities are skipped. This policy therefore
gives improved performance for larger units of work while
sacrificing the behavior of "automatic dirty checking".
Therefore, flush() operations are potentially cheaper with this
policy. The negative aspect this has is that if you have a rather
large application and you pass your objects through several layers
for processing purposes and business tasks you may need to track
yourself which entities have changed on the way so you can pass
them to EntityManager#persist().
This policy can be configured as follows:
.. code-block:: php
<?php
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class User
{
// ...
}
Notify
~~~~~~
This policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that
purpose, a class that wants to use this policy needs to implement
the ``NotifyPropertyChanged`` interface from the Doctrine
namespace. As a guideline, such an implementation can look as
follows:
.. code-block:: php
<?php
use Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->_listeners[] = $listener;
}
}
Then, in each property setter of this class or derived classes, you
need to notify all the ``PropertyChangedListener`` instances. As an
example we add a convenience method on ``MyEntity`` that shows this
behaviour:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data)
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
You have to invoke ``_onPropertyChanged`` inside every method that
changes the persistent state of ``MyEntity``.
The check whether the new value is different from the old one is
not mandatory but recommended. That way you also have full control
over when you consider a property changed.
The negative point of this policy is obvious: You need implement an
interface and write some plumbing code. But also note that we tried
hard to keep this notification functionality abstract. Strictly
speaking, it has nothing to do with the persistence layer and the
Doctrine ORM or DBAL. You may find that property notification
events come in handy in many other scenarios as well. As mentioned
earlier, the ``Doctrine\Common`` namespace is not that evil and
consists solely of very small classes and interfaces that have
almost no external dependencies (none to the DBAL and none to the
ORM) and that you can easily take with you should you want to swap
out the persistence layer. This change tracking policy does not
introduce a dependency on the Doctrine DBAL/ORM or the persistence
layer.
The positive point and main advantage of this policy is its
effectiveness. It has the best performance characteristics of the 3
policies with larger units of work and a flush() operation is very
cheap when nothing has changed.

View File

@ -0,0 +1,478 @@
Configuration
=============
Bootstrapping
-------------
Bootstrapping Doctrine is a relatively simple procedure that
roughly exists of just 2 steps:
- Making sure Doctrine class files can be loaded on demand.
- Obtaining an EntityManager instance.
Class loading
~~~~~~~~~~~~~
Lets start with the class loading setup. We need to set up some
class loaders (often called "autoloader") so that Doctrine class
files are loaded on demand. The Doctrine namespace contains a very
fast and minimalistic class loader that can be used for Doctrine
and any other libraries where the coding standards ensure that a
class's location in the directory tree is reflected by its name and
namespace and where there is a common root namespace.
.. note::
You are not forced to use the Doctrine class loader to
load Doctrine classes. Doctrine does not care how the classes are
loaded, if you want to use a different class loader or your own to
load Doctrine classes, just do that. Along the same lines, the
class loader in the Doctrine namespace is not meant to be only used
for Doctrine classes, too. It is a generic class loader that can be
used for any classes that follow some basic naming standards as
described above.
The following example shows the setup of a ``ClassLoader`` for the
different types of Doctrine Installations:
.. note::
This assumes you've created some kind of script to test
the following code in. Something like a ``test.php`` file.
PEAR or Tarball Download
^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
<?php
// test.php
require '/path/to/libraries/Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/libraries');
$classLoader->register(); // register on SPL autoload stack
Git
^^^
The Git bootstrap assumes that you have fetched the related
packages through ``git submodule update --init``
.. code-block:: php
<?php
// test.php
$lib = '/path/to/doctrine2-orm/lib/';
require $lib . 'vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine\Common', $lib . 'vendor/doctrine-common/lib');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', $lib . 'vendor/doctrine-dbal/lib');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine\ORM', $lib);
$classLoader->register();
Additional Symfony Components
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you don't use Doctrine2 in combination with Symfony2 you have to
register an additional namespace to be able to use the Doctrine-CLI
Tool or the YAML Mapping driver:
.. code-block:: php
<?php
// PEAR or Tarball setup
$classloader = new \Doctrine\Common\ClassLoader('Symfony', '/path/to/libraries/Doctrine');
$classloader->register();
// Git Setup
$classloader = new \Doctrine\Common\ClassLoader('Symfony', $lib . 'vendor/');
$classloader->register();
For best class loading performance it is recommended that you keep
your include\_path short, ideally it should only contain the path
to the PEAR libraries, and any other class libraries should be
registered with their full base path.
Obtaining an EntityManager
~~~~~~~~~~~~~~~~~~~~~~~~~~
Once you have prepared the class loading, you acquire an
*EntityManager* instance. The EntityManager class is the primary
access point to ORM functionality provided by Doctrine.
A simple configuration of the EntityManager requires a
``Doctrine\ORM\Configuration`` instance as well as some database
connection parameters:
.. code-block:: php
<?php
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration;
// ...
if ($applicationMode == "development") {
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$cache = new \Doctrine\Common\Cache\ApcCache;
}
$config = new Configuration;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
if ($applicationMode == "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
}
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
$em = EntityManager::create($connectionOptions, $config);
.. note::
Do not use Doctrine without a metadata and query cache!
Doctrine is highly optimized for working with caches. The main
parts in Doctrine that are optimized for caching are the metadata
mapping information with the metadata cache and the DQL to SQL
conversions with the query cache. These 2 caches require only an
absolute minimum of memory yet they heavily improve the runtime
performance of Doctrine. The recommended cache driver to use with
Doctrine is `APC <http://www.php.net/apc>`_. APC provides you with
an opcode-cache (which is highly recommended anyway) and a very
fast in-memory cache storage that you can use for the metadata and
query caches as seen in the previous code snippet.
Configuration Options
---------------------
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyDir($dir);
$config->getProxyDir();
Gets or sets the directory where Doctrine generates any proxy
classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyNamespace($namespace);
$config->getProxyNamespace();
Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setMetadataDriverImpl($driver);
$config->getMetadataDriverImpl();
Gets or sets the metadata driver implementation that is used by
Doctrine to acquire the object-relational metadata for your
classes.
There are currently 4 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
Throughout the most part of this manual the AnnotationDriver is
used in the examples. For information on the usage of the XmlDriver
or YamlDriver please refer to the dedicated chapters
``XML Mapping`` and ``YAML Mapping``.
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the annotation
driver, because otherwise mass-operations on all entities through
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
Metadata Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setMetadataCacheImpl($cache);
$config->getMetadataCacheImpl();
Gets or sets the cache implementation to use for caching metadata
information, that is, all the information you supply via
annotations, xml or yaml, so that they do not need to be parsed and
loaded from scratch on every single request which is a waste of
resources. The cache implementation must implement the
``Doctrine\Common\Cache\Cache`` interface.
Usage of a metadata cache is highly recommended.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
For development you should use the
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setQueryCacheImpl($cache);
$config->getQueryCacheImpl();
Gets or sets the cache implementation to use for caching DQL
queries, that is, the result of a DQL parsing process that includes
the final SQL as well as meta information about how to process the
SQL result set of a query. Note that the query cache does not
affect query results. You do not get stale data. This is a pure
optimization cache without any negative side-effects (except some
minimal memory usage in your cache).
Usage of a query cache is highly recommended.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
For development you should use the
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setSQLLogger($logger);
$config->getSQLLogger();
Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
``Doctrine\DBAL\Logging\SqlLogger`` interface. A simple default
implementation that logs to the standard output using ``echo`` and
``var_dump`` can be found at
``Doctrine\DBAL\Logging\EchoSqlLogger``.
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
Gets or sets whether proxy classes should be generated
automatically at runtime by Doctrine. If set to ``FALSE``, proxy
classes must be generated manually through the doctrine command
line task ``generate-proxies``. The strongly recommended value for
a production environment is ``FALSE``.
Development vs Production Configuration
---------------------------------------
You should code your Doctrine2 bootstrapping with two different
runtime models in mind. There are some serious benefits of using
APC or Memcache in production. In development however this will
frequently give you fatal errors, when you change your entities and
the cache still keeps the outdated metadata. That is why we
recommend the ``ArrayCache`` for development.
Furthermore you should have the Auto-generating Proxy Classes
option to true in development and to false in production. If this
option is set to ``TRUE`` it can seriously hurt your script
performance if several proxy classes are re-generated during script
execution. Filesystem calls of that magnitude can even slower than
all the database queries Doctrine issues. Additionally writing a
proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Connection Options
------------------
The ``$connectionOptions`` passed as the first argument to
``EntityManager::create()`` has to be either an array or an
instance of ``Doctrine\DBAL\Connection``. If an array is passed it
is directly passed along to the DBAL Factory
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
configuration is explained in the
`DBAL section <./../../../../../dbal/2.0/docs/reference/configuration/en>`_.
Proxy Objects
-------------
A proxy object is an object that is put in place or used instead of
the "real" object. A proxy object can add behavior to the object
being proxied without that object being aware of it. In Doctrine 2,
proxy objects are used to realize several features but mainly for
transparent lazy-loading.
Proxy objects with their lazy-loading facilities help to keep the
subset of objects that are already in memory connected to the rest
of the objects. This is an essential property as without it there
would always be fragile partial objects at the outer edges of your
object graph.
Doctrine 2 implements a variant of the proxy pattern where it
generates classes that extend your entity classes and adds
lazy-loading capabilities to them. Doctrine can then give you an
instance of such a proxy class whenever you request an object of
the class being proxied. This happens in two situations:
Reference Proxies
~~~~~~~~~~~~~~~~~
The method ``EntityManager#getReference($entityName, $identifier)``
lets you obtain a reference to an entity for which the identifier
is known, without loading that entity from the database. This is
useful, for example, as a performance enhancement, when you want to
establish an association to an entity for which you have the
identifier. You could simply do this:
.. code-block:: php
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Here, we added an Item to a Cart without loading the Item from the
database. If you invoke any method on the Item instance, it would
fully initialize its state transparently from the database. Here
$item is actually an instance of the proxy class that was generated
for the Item class but your code does not need to care. In fact it
**should not care**. Proxy objects should be transparent to your
code.
Association proxies
~~~~~~~~~~~~~~~~~~~
The second most important situation where Doctrine uses proxy
objects is when querying for objects. Whenever you query for an
object that has a single-valued association to another object that
is configured LAZY, without joining that association in the same
query, Doctrine puts proxy objects in place where normally the
associated object would be. Just like other proxies it will
transparently initialize itself on first access.
.. note::
Joining an association in a DQL or native query
essentially means eager loading of that association in that query.
This will override the 'fetch' option specified in the mapping for
that association, but only for that query.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically by Doctrine. The configuration option that
controls this behavior is:
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
The default value is ``TRUE`` for convenient development. However,
this setting is not optimal for performance and therefore not
recommended for a production environment. To eliminate the overhead
of proxy class generation during runtime, set this configuration
option to ``FALSE``. When you do this in a development environment,
note that you may get class/file not found errors if certain proxy
classes are not available or failing lazy-loads if new methods were
added to the entity class that are not yet in the proxy class. In
such a case, simply use the Doctrine Console to (re)generate the
proxy classes like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
Multiple Metadata Sources
-------------------------
When using different components using Doctrine 2 you may end up
with them using two different metadata drivers, for example XML and
YAML. You can use the DriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
<?php
$chain = new DriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is
delegated to the appropriate driver. The chain semantics come from
the fact that the driver loops through all namespaces and matches
the entity class name against the namespace using a
``strpos() === 0`` call. This means you need to order the drivers
correctly if sub-namespaces use different metadata driver
implementations.

608
en/reference/events.rst Normal file
View File

@ -0,0 +1,608 @@
Events
======
Doctrine 2 features a lightweight event system that is part of the
Common package.
The Event System
----------------
The event system is controlled by the ``EventManager``. It is the
central point of Doctrine's event listener system. Listeners are
registered on the manager and events are dispatched through the
manager.
.. code-block:: php
<?php
$evm = new EventManager();
Now we can add some event listeners to the ``$evm``. Let's create a
``EventTest`` class to play around with.
.. code-block:: php
<?php
class EventTest
{
const preFoo = 'preFoo';
const postFoo = 'postFoo';
private $_evm;
public $preFooInvoked = false;
public $postFooInvoked = false;
public function __construct($evm)
{
$evm->addEventListener(array(self::preFoo, self::postFoo), $this);
}
public function preFoo(EventArgs $e)
{
$this->preFooInvoked = true;
}
public function postFoo(EventArgs $e)
{
$this->postFooInvoked = true;
}
}
// Create a new instance
$test = new EventTest($evm);
Events can be dispatched by using the ``dispatchEvent()`` method.
.. code-block:: php
<?php
$evm->dispatchEvent(EventTest::preFoo);
$evm->dispatchEvent(EventTest::postFoo);
You can easily remove a listener with the ``removeEventListener()``
method.
.. code-block:: php
<?php
$evm->removeEventListener(array(self::preFoo, self::postFoo), $this);
The Doctrine 2 event system also has a simple concept of event
subscribers. We can define a simple ``TestEventSubscriber`` class
which implements the ``\Doctrine\Common\EventSubscriber`` interface
and implements a ``getSubscribedEvents()`` method which returns an
array of events it should be subscribed to.
.. code-block:: php
<?php
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
{
public $preFooInvoked = false;
public function preFoo()
{
$this->preFooInvoked = true;
}
public function getSubscribedEvents()
{
return array(TestEvent::preFoo);
}
}
$eventSubscriber = new TestEventSubscriber();
$evm->addEventSubscriber($eventSubscriber);
Now when you dispatch an event any event subscribers will be
notified for that event.
.. code-block:: php
<?php
$evm->dispatchEvent(TestEvent::preFoo);
Now you can test the ``$eventSubscriber`` instance to see if the
``preFoo()`` method was invoked.
.. code-block:: php
<?php
if ($eventSubscriber->preFooInvoked) {
echo 'pre foo invoked!';
}
Naming convention
~~~~~~~~~~~~~~~~~
Events being used with the Doctrine 2 EventManager are best named
with camelcase and the value of the corresponding constant should
be the name of the constant itself, even with spelling. This has
several reasons:
- It is easy to read.
- Simplicity.
- Each method within an EventSubscriber is named after the
corresponding constant. If constant name and constant value differ,
you MUST use the new value and thus, your code might be subject to
codechanges when the value changes. This contradicts the intention
of a constant.
An example for a correct notation can be found in the example
``EventTest`` above.
Lifecycle Events
----------------
The EntityManager and UnitOfWork trigger a bunch of events during
the life-time of their registered entities.
- preRemove - The preRemove event occurs for a given entity before
the respective EntityManager remove operation for that entity is
executed. It is not called for a DQL DELETE statement.
- postRemove - The postRemove event occurs for an entity after the
entity has been deleted. It will be invoked after the database
delete operations. It is not called for a DQL DELETE statement.
- prePersist - The prePersist event occurs for a given entity
before the respective EntityManager persist operation for that
entity is executed.
- postPersist - The postPersist event occurs for an entity after
the entity has been made persistent. It will be invoked after the
database insert operations. Generated primary key values are
available in the postPersist event.
- preUpdate - The preUpdate event occurs before the database
update operations to entity data. It is not called for a DQL UPDATE statement.
- postUpdate - The postUpdate event occurs after the database
update operations to entity data. It is not called for a DQL UPDATE statement.
- postLoad - The postLoad event occurs for an entity after the
entity has been loaded into the current EntityManager from the
database or after the refresh operation has been applied to it.
- loadClassMetadata - The loadClassMetadata event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml/yaml).
- onFlush - The onFlush event occurs after the change-sets of all
managed entities are computed. This event is not a lifecycle
callback.
.. warning::
Note that the postLoad event occurs for an entity
before any associations have been initialized. Therefore it is not
safe to access associations in a postLoad callback or event
handler.
You can access the Event constants from the ``Events`` class in the
ORM package.
.. code-block:: php
<?php
use Doctrine\ORM\Events;
echo Events::preUpdate;
These can be hooked into by two different types of event
listeners:
- Lifecycle Callbacks are methods on the entity classes that are
called when the event is triggered. They receive absolutely no
arguments and are specifically designed to allow changes inside the
entity classes state.
- Lifecycle Event Listeners are classes with specific callback
methods that receives some kind of ``EventArgs`` instance which
give access to the entity, EntityManager or other relevant data.
.. note::
All Lifecycle events that happen during the ``flush()`` of
an EntityManager have very specific constraints on the allowed
operations that can be executed. Please read the
*Implementing Event Listeners* section very carefully to understand
which operations are allowed in which lifecycle event.
Lifecycle Callbacks
-------------------
A lifecycle event is a regular event with the additional feature of
providing a mechanism to register direct callbacks inside the
corresponding entity classes that are executed when the lifecycle
event occurs.
.. code-block:: php
<?php
/** @Entity @HasLifecycleCallbacks */
class User
{
// ...
/**
* @Column(type="string", length=255)
*/
public $value;
/** @Column(name="created_at", type="string", length=255) */
private $createdAt;
/** @PrePersist */
public function doStuffOnPrePersist()
{
$this->createdAt = date('Y-m-d H:m:s');
}
/** @PrePersist */
public function doOtherStuffOnPrePersist()
{
$this->value = 'changed from prePersist callback!';
}
/** @PostPersist */
public function doStuffOnPostPersist()
{
$this->value = 'changed from postPersist callback!';
}
/** @PostLoad */
public function doStuffOnPostLoad()
{
$this->value = 'changed from postLoad callback!';
}
/** @PreUpdate */
public function doStuffOnPreUpdate()
{
$this->value = 'changed from preUpdate callback!';
}
}
Note that when using annotations you have to apply the
@HasLifecycleCallbacks marker annotation on the entity class.
If you want to register lifecycle callbacks from YAML or XML you
can do it with the following.
.. code-block:: yaml
User:
type: entity
fields:
# ...
name:
type: string(50)
lifecycleCallbacks:
doStuffOnPrePersist: prePersist
doStuffOnPostPersist: postPersist
XML would look something like this:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
<entity name="User">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
You just need to make sure a public ``doStuffOnPrePersist()`` and
``doStuffOnPostPersist()`` method is defined on your ``User``
model.
.. code-block:: php
<?php
// ...
class User
{
// ...
public function doStuffOnPrePersist()
{
// ...
}
public function doStuffOnPostPersist()
{
// ...
}
}
The ``key`` of the lifecycleCallbacks is the name of the method and
the value is the event type. The allowed event types are the ones
listed in the previous Lifecycle Events section.
Listening to Lifecycle Events
-----------------------------
Lifecycle event listeners are much more powerful than the simple
lifecycle callbacks that are defined on the entity classes. They
allow to implement re-usable behaviors between different entity
classes, yet require much more detailed knowledge about the inner
workings of the EntityManager and UnitOfWork. Please read the
*Implementing Event Listeners* section carefully if you are trying
to write your own listener.
To register an event listener you have to hook it into the
EventManager that is passed to the EntityManager factory:
.. code-block:: php
<?php
$eventManager = new EventManager();
$eventManager->addEventListener(array(Events::preUpdate), MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
You can also retrieve the event manager instance after the
EntityManager was created:
.. code-block:: php
<?php
$entityManager->getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener());
$entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());
Implementing Event Listeners
----------------------------
This section explains what is and what is not allowed during
specific lifecycle events of the UnitOfWork. Although you get
passed the EntityManager in all of these events, you have to follow
this restrictions very carefully since operations in the wrong
event may produce lots of different errors, such as inconsistent
data and lost updates/persists/removes.
For the described events that are also lifecycle callback events
the restrictions apply as well, with the additional restriction
that you do not have access to the EntityManager or UnitOfWork APIs
inside these events.
prePersist
~~~~~~~~~~
There are two ways for the ``prePersist`` event to be triggered.
One is obviously when you call ``EntityManager#persist()``. The
event is also called for all cascaded associations.
There is another way for ``prePersist`` to be called, inside the
``flush()`` method when changes to associations are computed and
this association is marked as cascade persist. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called "persistence by reachability".
In both cases you get passed a ``LifecycleEventArgs`` instance
which has access to the entity and the entity manager.
The following restrictions apply to ``prePersist``:
- If you are using a PrePersist Identity Generator such as
sequences the ID value will *NOT* be available within any
PrePersist events.
- Doctrine will not recognize changes made to relations in a pre
persist event called by "reachability" through a cascade persist
unless you use the internal ``UnitOfWork`` API. We do not recommend
such operations in the persistence by reachability context, so do
this at your own risk and possibly supported by unit-tests.
preRemove
~~~~~~~~~
The ``preRemove`` event is called on every entity when its passed
to the ``EntityManager#remove()`` method. It is cascaded for all
associations that are marked as cascade delete.
There are no restrictions to what methods can be called inside the
``preRemove`` event, except when the remove method itself was
called during a flush operation.
onFlush
~~~~~~~
OnFlush is a very powerful event. It is called inside
``EntityManager#flush()`` after the changes to all the managed
entities and their associations have been computed. This means, the
``onFlush`` event has access to the sets of:
- Entities scheduled for insert
- Entities scheduled for update
- Entities scheduled for removal
- Collections scheduled for update
- Collections scheduled for removal
To make use of the onFlush event you have to be familiar with the
internal UnitOfWork API, which grants you access to the previously
mentioned sets. See this example:
.. code-block:: php
<?php
class FlushExampleListener
{
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
}
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
}
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
}
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
}
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
}
}
}
The following restrictions apply to the onFlush event:
- Calling ``EntityManager#persist()`` does not suffice to trigger
a persist on an entity. You have to execute an additional call to
``$unitOfWork->computeChangeSet($classMetadata, $entity)``.
- Changing primitive fields or associations requires you to
explicitly trigger a re-computation of the changeset of the
affected entity. This can be done by either calling
``$unitOfWork->computeChangeSet($classMetadata, $entity)`` or
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
The second method has lower overhead, but only re-computes
primitive fields, never associations.
preUpdate
~~~~~~~~~
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
``EntityManager#flush()`` method.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
referential integrity at this point of the flush operation. This
event has a powerful feature however, it is executed with a
``PreUpdateEventArgs`` instance, which contains a reference to the
computed change-set of this entity.
This means you have access to all the fields that have changed for
this entity with their old and new value. The following methods are
available on the ``PreUpdateEventArgs``:
- ``getEntity()`` to get access to the actual entity.
- ``getEntityChangeSet()`` to get a copy of the changeset array.
Changes to this returned array do not affect updating.
- ``hasChangedField($fieldName)`` to check if the given field name
of the current entity changed.
- ``getOldValue($fieldName)`` and ``getNewValue($fieldName)`` to
access the values of a field.
- ``setNewValue($fieldName, $value)`` to change the value of a
field to be updated.
A simple example for this event looks like:
.. code-block:: php
<?php
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}
You could also use this listener to implement validation of all the
fields that have changed. This is more efficient than using a
lifecycle callback when there are expensive validations to call:
.. code-block:: php
<?php
class ValidCreditCardListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof Account) {
if ($eventArgs->hasChangedField('creditCard')) {
$this->validateCreditCard($eventArgs->getNewValue('creditCard'));
}
}
}
private function validateCreditCard($no)
{
// throw an exception to interrupt flush event. Transaction will be rolled back.
}
}
Restrictions for this event:
- Changes to associations of the passed entities are not
recognized by the flush operation anymore.
- Changes to fields of the passed entities are not recognized by
the flush operation anymore, use the computed change-set passed to
the event to modify primitive field values.
- Any calls to ``EntityManager#persist()`` or
``EntityManager#remove()``, even in combination with the UnitOfWork
API are strongly discouraged and don't work as expected outside the
flush operation.
postUpdate, postRemove, postPersist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The three post events are called inside ``EntityManager#flush()``.
Changes in here are not relevant to the persistence in the
database, but you can use this events to
postLoad
~~~~~~~~
This event is called after an entity is constructed by the
EntityManager.
Load ClassMetadata Event
------------------------
When the mapping information for an entity is read, it is populated
in to a ``ClassMetadataInfo`` instance. You can hook in to this
process and manipulate the instance.
.. code-block:: php
<?php
$test = new EventTest();
$metadataFactory = $em->getMetadataFactory();
$evm = $em->getEventManager();
$evm->addEventListener(Events::loadClassMetadata, $test);
class EventTest
{
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$fieldMapping = array(
'fieldName' => 'about',
'type' => 'string',
'length' => 255
);
$classMetadata->mapField($fieldMapping);
}
}

View File

@ -0,0 +1,42 @@
Improving Performance
=====================
Bytecode Cache
--------------
It is highly recommended to make use of a bytecode cache like APC.
A bytecode cache removes the need for parsing PHP code on every
request and can greatly improve performance.
"If you care about performance and don't use a bytecode
cache then you don't really care about performance. Please get one
and start using it."
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
Metadata and Query caches
-------------------------
As already mentioned earlier in the chapter about configuring
Doctrine, it is strongly discouraged to use Doctrine without a
Metadata and Query cache (preferably with APC or Memcache as the
cache driver). Operating Doctrine without these caches means
Doctrine will need to load your mapping information on every single
request and has to parse each DQL query on every single request.
This is a waste of resources.
Alternative Query Result Formats
--------------------------------
Make effective use of the available alternative query result
formats like nested array graphs or pure scalar results, especially
in scenarios where data is loaded for read-only purposes.
Apply Best Practices
--------------------
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.

View File

@ -0,0 +1,259 @@
Inheritance Mapping
===================
Mapped Superclasses
-------------------
An mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. Typically, the purpose of such a
mapped superclass is to define state and mapping information that
is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can
appear in the middle of an otherwise mapped inheritance hierarchy
(through Single Table Inheritance or Class Table Inheritance).
.. note::
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional. For further support of inheritance, the single or
joined table inheritance features have to be used.
Example:
.. code-block:: php
<?php
/** @MappedSuperclass */
class MappedSuperclassBase
{
/** @Column(type="integer") */
private $mapped1;
/** @Column(type="string") */
private $mapped2;
/**
* @OneToOne(targetEntity="MappedSuperclassRelated1")
* @JoinColumn(name="related1_id", referencedColumnName="id")
*/
private $mappedRelated1;
// ... more fields and methods
}
/** @Entity */
class EntitySubClass extends MappedSuperclassBase
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
// ... more fields and methods
}
The DDL for the corresponding database schema would look something
like this (this is for SQLite):
.. code-block:: sql
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
As you can see from this DDL snippet, there is only a single table
for the entity subclass. All the mappings from the mapped
superclass were inherited to the subclass as if they had been
defined on that class directly.
Single Table Inheritance
------------------------
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
is an inheritance mapping strategy where all classes of a hierarchy
are mapped to a single database table. In order to distinguish
which row represents which type in the hierarchy a so-called
discriminator column is used.
Example:
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
Things to note:
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
type ``Person`` and "employee" identifies a row as being of type
``Employee``.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
This mapping approach works well when the type hierarchy is fairly
simple and stable. Adding a new type to the hierarchy and adding
fields to existing supertypes simply involves adding new columns to
the table, though in large deployments this may have an adverse
impact on the index and column layout inside the database.
Performance impact
~~~~~~~~~~~~~~~~~~
This strategy is very efficient for querying across all types in
the hierarchy or for specific types. No table joins are required,
only a WHERE clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performant.
There is a general performance consideration with Single Table
Inheritance: If you use a STI entity as a many-to-one or one-to-one
entity you should never use one of the classes at the upper levels
of the inheritance hierachy as "targetEntity", only those that have
no subclasses. Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allows
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.
Class Table Inheritance
-----------------------
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
of a parent class through a foreign key constraint. Doctrine 2
implements this strategy through the use of a discriminator column
in the topmost table of the hierarchy because this is the easiest
way to achieve polymorphic queries with Class Table Inheritance.
Example:
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/** @Entity */
class Employee extends Person
{
// ...
}
Things to note:
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
discriminator column identify a row as being of which type. In the
case above a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type
``Employee``.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
.. note::
When you do not use the SchemaTool to generate the
required SQL you should know that deleting a class table
inheritance makes use of the foreign key property
``ON DELETE CASCADE`` in all database implementations. A failure to
implement this yourself will lead to dead rows in the database.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
Introducing a new type to the hierarchy, at any level, simply
involves interjecting a new table into the schema. Subtypes of that
type will automatically join with that new type at runtime.
Similarly, modifying any entity type in the hierarchy by adding,
modifying or removing fields affects only the immediate table
mapped to that type. This mapping strategy provides the greatest
flexibility at design time, since changes to any type are always
limited to that type's dedicated table.
Performance impact
~~~~~~~~~~~~~~~~~~
This strategy inherently requires multiple JOIN operations to
perform just about any query which can have a negative impact on
performance, especially with large tables and/or large hierarchies.
When partial objects are allowed, either globally or on the
specific query, then querying for any type will not cause the
tables of subtypes to be OUTER JOINed which can increase
performance but the resulting partial objects will not fully load
themselves on access of any subtype fields, so accessing fields of
subtypes after such a query is not safe.
There is a general performance consideration with Class Table
Inheritance: If you use a CTI entity as a many-to-one or one-to-one
entity you should never use one of the classes at the upper levels
of the inheritance hierachy as "targetEntity", only those that have
no subclasses. Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
For each entity in the Class-Table Inheritance hierarchy all the
mapped fields have to be columns on the table of this entity.
Additionally each child table has to have an id column that matches
the id column definition on the root table (except for any sequence
or auto-increment details). Furthermore each child table has to
have a foreign key pointing from the id column to the root table id
column and cascading on delete.

View File

@ -0,0 +1,401 @@
Introduction
============
Welcome
-------
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that
provides transparent persistence for PHP objects. It sits on top of
a powerful database abstraction layer (DBAL). Object-Relational
Mappers primary task is the transparent translation between (PHP)
objects and relational database rows.
One of Doctrines key features is the option to write database
queries in a proprietary object oriented SQL dialect called
Doctrine Query Language (DQL), inspired by Hibernates HQL. Besides
DQLs slight differences to SQL it abstracts the mapping between
database rows and objects considerably, allowing developers to
write powerful queries in a simple and flexible fashion.
Disclaimer
----------
This is the Doctrine 2 reference documentation. Introductory guides
and tutorials that you can follow along from start to finish, like
the "Guide to Doctrine" book known from the Doctrine 1.x series,
will be available at a later date.
Using an Object-Relational Mapper
---------------------------------
As the term ORM already hints at, Doctrine 2 aims to simplify the
translation between database rows and the PHP object model. The
primary use case for Doctrine are therefore applications that
utilize the Object-Oriented Programming Paradigm. For applications
that not primarily work with objects Doctrine 2 is not suited very
well.
Requirements
------------
Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine 2 Packages
-------------------
Doctrine 2 is divided into three main packages.
- Common
- DBAL (includes Common)
- ORM (includes DBAL+Common)
This manual mainly covers the ORM package, sometimes touching parts
of the underlying DBAL and Common packages. The Doctrine code base
is split in to these packages for a few reasons and they are to...
- ...make things more maintainable and decoupled
- ...allow you to use the code in Doctrine Common without the ORM
or DBAL
- ...allow you to use the DBAL without the ORM
The Common Package
~~~~~~~~~~~~~~~~~~
The Common package contains highly reusable components that have no
dependencies beyond the package itself (and PHP, of course). The
root namespace of the Common package is ``Doctrine\Common``.
The DBAL Package
~~~~~~~~~~~~~~~~
The DBAL package contains an enhanced database abstraction layer on
top of PDO but is not strongly bound to PDO. The purpose of this
layer is to provide a single API that bridges most of the
differences between the different RDBMS vendors. The root namespace
of the DBAL package is ``Doctrine\DBAL``.
The ORM Package
~~~~~~~~~~~~~~~
The ORM package contains the object-relational mapping toolkit that
provides transparent relational persistence for plain PHP objects.
The root namespace of the ORM package is ``Doctrine\ORM``.
Installing
----------
Doctrine can be installed many different ways. We will describe all
the different ways and you can choose which one suits you best.
PEAR
~~~~
You can easily install any of the three Doctrine packages from the
PEAR command line installation utility.
To install just the ``Common`` package you can run the following
command:
.. code-block:: bash
$ sudo pear install pear.doctrine-project.org/DoctrineCommon-<version>
If you want to use the Doctrine Database Abstraction Layer you can
install it with the following command.
.. code-block:: bash
$ sudo pear install pear.doctrine-project.org/DoctrineDBAL-<version>
Or, if you want to get the works and go for the ORM you can install
it with the following command.
.. code-block:: bash
$ sudo pear install pear.doctrine-project.org/DoctrineORM-<version>
**NOTE** The ``<version>`` tag above represents the version you
want to install. For example the current version at the time of
writing this is ``2.0.0BETA3`` for the ORM, so you could install it
like the following:
.. code-block:: bash
$ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0BETA3
When you have a package installed via PEAR you can require and load
the ``ClassLoader`` with the following code.
.. code-block:: php
<?php
require 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader();
The packages are installed in to your shared PEAR PHP code folder
in a folder named ``Doctrine``. You also get a nice command line
utility installed and made available on your system. Now when you
run the ``doctrine`` command you will see what you can do with it.
.. code-block:: php
$ doctrine
Doctrine Command Line Interface version 2.0.0BETA3-DEV
Usage:
[options] command [arguments]
Options:
--help -h Display this help message.
--quiet -q Do not output any message.
--verbose -v Increase verbosity of messages.
--version -V Display this program version.
--color -c Force ANSI color output.
--no-interaction -n Do not ask any interactive question.
Available commands:
help Displays help for a command (?)
list Lists commands
dbal
:import Import SQL file(s) directly to Database.
:run-sql Executes arbitrary SQL directly from the command line.
orm
:convert-d1-schema Converts Doctrine 1.X schema into a Doctrine 2.X schema.
:convert-mapping Convert mapping information between supported formats.
:ensure-production-settings Verify that Doctrine is properly configured for a production environment.
:generate-entities Generate entity classes and method stubs from your mapping information.
:generate-proxies Generates proxy classes for entity classes.
:generate-repositories Generate repository classes from your mapping information.
:run-dql Executes arbitrary DQL directly from the command line.
:validate-schema Validate that the mapping files.
orm:clear-cache
:metadata Clear all metadata cache of the various cache drivers.
:query Clear all query cache of the various cache drivers.
:result Clear result cache of the various cache drivers.
orm:schema-tool
:create Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.
:drop Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output.
:update Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
Package Download
~~~~~~~~~~~~~~~~
You can also use Doctrine 2 by downloading the latest release
package from
`the download page <http://www.doctrine-project.org/download>`_.
See the configuration section on how to configure and bootstrap a
downloaded version of Doctrine.
GitHub
~~~~~~
Alternatively you can clone the latest version of Doctrine 2 via
GitHub.com:
.. code-block:: php
$ git clone git://github.com/doctrine/doctrine2.git doctrine
This downloads all the sources of the ORM package. You need to
initialize the Github submodules for the Common and DBAL package
dependencies:
.. code-block:: php
$ git submodule init
$ git submodule update
This updates your Git checkout to use the Doctrine and Doctrine
package versions that are recommended for the cloned Master version
of Doctrine 2.
See the configuration chapter on how to configure a Github
installation of Doctrine with regards to autoloading.
**NOTE**
You should not combine the Doctrine-Common, Doctrine-DBAL and
Doctrine-ORM master commits with each other in combination. The ORM
may not work with the current Common or DBAL master versions.
Instead the ORM ships with the Git Submodules that are required.
Subversion
~~~~~~~~~~
**NOTE**
Using the SVN Mirror is not recommended. It only allows access to
the latest master commit and does not automatically fetch the
submodules.
If you prefer subversion you can also checkout the code from
GitHub.com through the subversion protocol:
.. code-block:: php
$ svn co http://svn.github.com/doctrine/doctrine2.git doctrine2
However this only allows you to check out the current master of
Doctrine 2, without the Common and DBAL dependencies. You have to
grab them yourself, but might run into version incompatibilities
between the different master branches of Common, DBAL and ORM.
Sandbox Quickstart
------------------
**NOTE** The sandbox is only available via the Doctrine2 Github
Repository or soon as a separate download on the downloads page.
You will find it in the $root/tools/sandbox folder.
The sandbox is a pre-configured environment for evaluating and
playing with Doctrine 2.
Overview
~~~~~~~~
After navigating to the sandbox directory, you should see the
following structure:
.. code-block:: php
sandbox/
Entities/
Address.php
User.php
xml/
Entities.Address.dcm.xml
Entities.User.dcm.xml
yaml/
Entities.Address.dcm.yml
Entities.User.dcm.yml
cli-config.php
doctrine
doctrine.php
index.php
Here is a short overview of the purpose of these folders and
files:
- The ``Entities`` folder is where any model classes are created.
Two example entities are already there.
- The ``xml`` folder is where any XML mapping files are created
(if you want to use XML mapping). Two example mapping documents for
the 2 example entities are already there.
- The ``yaml`` folder is where any YAML mapping files are created
(if you want to use YAML mapping). Two example mapping documents
for the 2 example entities are already there.
- The ``cli-config.php`` contains bootstrap code for a
configuration that is used by the Console tool ``doctrine``
whenever you execute a task.
- ``doctrine``/``doctrine.php`` is a command-line tool.
- ``index.php`` is a basic classical bootstrap file of a php
application that uses Doctrine 2.
Mini-tutorial
~~~~~~~~~~~~~
1) From within the tools/sandbox folder, run the following command
and you should see the same output.
$ php doctrine orm:schema-tool:create Creating database schema...
Database schema created successfully!
2) Take another look into the tools/sandbox folder. A SQLite
database should have been created with the name
``database.sqlite``.
3) Open ``index.php`` and at the bottom edit it so it looks like
the following:
.. raw:: html
<?php
//... bootstrap stuff
## PUT YOUR TEST CODE BELOW
$user = new \Entities\User;
$user->
setName('Garfield'); :math:`$em->persist($`user); $em->flush();
echo "User saved!";
Open index.php in your browser or execute it on the command line.
You should see the output "User saved!".
4) Inspect the SQLite database. Again from within the tools/sandbox
folder, execute the following command:
$ php doctrine dbal:run-sql "select \* from users"
You should get the following output:
.. code-block:: php
array(1) {
[0]=>
array(2) {
["id"]=>
string(1) "1"
["name"]=>
string(8) "Garfield"
}
}
You just saved your first entity with a generated ID in an SQLite
database.
5) Replace the contents of index.php with the following:
.. raw:: html
<?php
//... bootstrap stuff
## PUT YOUR TEST CODE BELOW
$q = $em->
createQuery('select u from Entities u where u.name = ?1');
$q->setParameter(1, 'Garfield'); $garfield =
$q->getSingleResult();
echo "Hello " . $garfield->getName() . "!";
You just created your first DQL query to retrieve the user with the
name 'Garfield' from an SQLite database (Yes, there is an easier
way to do it, but we wanted to introduce you to DQL at this point.
Can you **find** the easier way?).
**TIP** When you create new model classes or alter existing ones
you can recreate the database schema with the command
``doctrine orm:schema-tool --drop`` followed by
``doctrine orm:schema-tool --create``.
6) Explore Doctrine 2!
Instead of reading through the reference manual we also recommend to look at the tutorials:
:doc:`Getting Started XML Edition <../tutorials/getting-started-xml-edition>`

View File

@ -0,0 +1,267 @@
Limitations and Known Issues
============================
We try to make using Doctrine2 a very pleasant experience.
Therefore we think it is very important to be honest about the
current limitations to our users. Much like every other piece of
software Doctrine2 is not perfect and far from feature complete.
This section should give you an overview of current limitations of
Doctrine 2 as well as critical known issues that you should know
about.
Current Limitations
-------------------
There is a set of limitations that exist currently which might be
solved in the future. Any of this limitations now stated has at
least one ticket in the Tracker and is discussed for future
releases.
Foreign Keys as Identifiers
~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are many use-cases where you would want to use an
Entity-Attribute-Value approach to modelling and define a
table-schema like the following:
.. code-block:: sql
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This is currently *NOT* possible with Doctrine2. You have to define
a surrogate key on the ``product_attributes`` table and use a
unique-constraint for the ``product_id`` and ``attribute_name``.
.. code-block:: sql
CREATE TABLE product_attributes (
attribute_id, INTEGER,
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (attribute_id),
UNIQUE (product_id, attribute_name)
);
Although we state that we support composite primary keys that does
not currently include foreign keys as primary key columns. To see
the fundamental difference between the two different
``product_attributes`` tables you should see how they translate
into a Doctrine Mapping (Using Annotations):
.. code-block:: php
<?php
/**
* Scenario 1: THIS IS NOT POSSIBLE CURRENTLY
* @Entity @Table(name="product_attributes")
*/
class ProductAttribute
{
/** @Id @ManyToOne(targetEntity="Product") */
private $product;
/** @Id @Column(type="string", name="attribute_name") */
private $name;
/** @Column(type="string", name="attribute_value") */
private $value;
}
/**
* Scenario 2: Using the surrogate key workaround
* @Entity
* @Table(name="product_attributes", uniqueConstraints={@UniqueConstraint(columns={"product_id", "attribute_name"})}))
*/
class ProductAttribute
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @ManyToOne(targetEntity="Product") */
private $product;
/** @Column(type="string", name="attribute_name") */
private $name;
/** @Column(type="string", name="attribute_value") */
private $value;
}
The following Jira Issue
`contains the feature request to allow @ManyToOne and @OneToOne annotations along the @Id annotation <http://www.doctrine-project.org/jira/browse/DDC-117>`_.
Mapping Arrays to a Join Table
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Related to the previous limitation with "Foreign Keys as
Identifier" you might be interested in mapping the same table
structure as given above to an array. However this is not yet
possible either. See the following example:
.. code-block:: sql
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This schema should be mapped to a Product Entity as follows:
.. code-block:: php
class Product
{
private $id;
private $name;
private $attributes = array();
}
Where the ``attribute_name`` column contains the key and
``attribute_value`` contains the value of each array element in
``$attributes``.
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
Value Objects
~~~~~~~~~~~~~
There is currently no native support value objects in Doctrine
other than for ``DateTime`` instances or if you serialize the
objects using ``serialize()/deserialize()`` which the DBAL Type
"object" supports.
The feature request for full value-object support
`is described in the DDC-93 ticket <http://www.doctrine-project.org/jira/browse/DDC-93>`_.
Applying Filter Rules to any Query
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are scenarios in many applications where you want to apply
additional filter rules to each query implicitly. Examples
include:
- In I18N Applications restrict results to a entities annotated
with a specific locale
- For a large collection always only return objects in a specific
date range/where condition applied.
- Soft-Delete
There is currently no way to achieve this consistently across both
DQL and Repository/Persister generated queries, but as this is a
pretty important feature we plan to add support for it in the
future.
Custom Persisters
~~~~~~~~~~~~~~~~~
A Persister in Doctrine is an object that is responsible for the
hydration and write operations of an entity against the database.
Currently there is no way to overwrite the persister implementation
for a given entity, however there are several use-cases that can
benefit from custom persister implementations:
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
- The previous Filter Rules Feature Request
Persist Keys of Collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~
PHP Arrays are ordered hash-maps and so should be the
``Doctrine\Common\Collections\Collection`` interface. We plan to
evaluate a feature that optionally persists and hydrates the keys
of a Collection instance.
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
Mapping many tables to one entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is not possible to map several equally looking tables onto one
entity. For example if you have a production and an archive table
of a certain business concept then you cannot have both tables map
to the same entity.
Behaviors
~~~~~~~~~
Doctrine 2 *will never* include a behavior system like Doctrine 1
in the core library. We don't think behaviors add more value than
they cost pain and debugging hell. Please see the many different
blog posts we have written on this topics:
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
Doctrine 2 has enough hooks and extension points so that *you* can
add whatever you want on top of it. None of this will ever become
core functionality of Doctrine2 however, you will have to rely on
third party extensions for magical behaviors.
Nested Set
~~~~~~~~~~
NestedSet was offered as a behavior in Doctrine 1 and will not be
included in the core of Doctrine 2. However there are already two
extensions out there that offer support for Nested Set with
Doctrine 2:
- `Doctrine2 Hierachical-Structural Behavior <http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <http://github.com/blt04/doctrine2-nestedset>`_
Known Issues
------------
The Known Issues section describes critical/blocker bugs and other
issues that are either complicated to fix, not fixable due to
backwards compatibility issues or where no simple fix exists (yet).
We don't plan to add every bug in the tracker there, just those
issues that can potentially cause nightmares or pain of any sort.
Identifier Quoting and Legacy Databases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For compatibility reasons between all the supported vendors and
edge case problems Doctrine 2 does *NOT* do automatic identifier
quoting. This can lead to problems when trying to get
legacy-databases to work with Doctrine 2.
- You can quote column-names as described in the
`Basic-Mapping <basic-mapping>`_ section.
- You cannot quote join column names.
- You cannot use non [a-zA-Z0-9\_]+ characters, they will break
several SQL statements.
Having problems with these kind of column names? Many databases
support all CRUD operations on views that semantically map to
certain tables. You can create views for all your problematic
tables and column names to avoid the legacy quoting nightmare.

View File

@ -0,0 +1,194 @@
Metadata Drivers
================
The heart of an object relational mapper is the mapping information
that glues everything together. It instructs the EntityManager how
it should behave when dealing with the different entities.
Core Metadata Drivers
---------------------
Doctrine provides a few different ways for you to specify your
metadata:
- **XML files** (XmlDriver)
- **Class DocBlock Annotations** (AnnotationDriver)
- **YAML files** (YamlDriver)
- **PHP Code in files or static functions** (PhpDriver)
Something important to note about the above drivers is they are all
an intermediate step to the same end result. The mapping
information is populated to ``Doctrine\ORM\Mapping\ClassMetadata``
instances. So in the end, Doctrine only ever has to work with the
API of the ``ClassMetadata`` class to get mapping information for
an entity.
.. note::
The populated ``ClassMetadata`` instances are also cached
so in a production environment the parsing and populating only ever
happens once. You can configure the metadata cache implementation
using the ``setMetadataCacheImpl()`` method on the
``Doctrine\ORM\Configuration`` class:
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
If you want to use one of the included core metadata drivers you
just need to configure it. All the drivers are in the
``Doctrine\ORM\Mapping\Driver`` namespace:
.. code-block:: php
<?php
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver('/path/to/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Implementing Metadata Drivers
-----------------------------
In addition to the included metadata drivers you can very easily
implement your own. All you need to do is define a class which
implements the ``Driver`` interface:
.. code-block:: php
<?php
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
interface Driver
{
/**
* Loads the metadata for the specified class into the provided container.
*
* @param string $className
* @param ClassMetadataInfo $metadata
*/
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array The names of all mapped classes known to this driver.
*/
function getAllClassNames();
/**
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a
* MappedSuperclass.
*
* @param string $className
* @return boolean
*/
function isTransient($className);
}
If you want to write a metadata driver to parse information from
some file format we've made your life a little easier by providing
the ``AbstractFileDriver`` implementation for you to extend from:
.. code-block:: php
<?php
class MyMetadataDriver extends AbstractFileDriver
{
/**
* {@inheritdoc}
*/
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$data = $this->_loadMappingFile($file);
// populate ClassMetadataInfo instance from $data
}
/**
* {@inheritdoc}
*/
protected function _loadMappingFile($file)
{
// parse contents of $file and return php data structure
}
}
.. note::
When using the ``AbstractFileDriver`` it requires that you
only have one entity defined per file and the file named after the
class described inside where namespace separators are replaced by
periods. So if you have an entity named ``Entities\User`` and you
wanted to write a mapping file for your driver above you would need
to name the file ``Entities.User.dcm.ext`` for it to be
recognized.
Now you can use your ``MyMetadataDriver`` implementation by setting
it with the ``setMetadataDriverImpl()`` method:
.. code-block:: php
<?php
$driver = new MyMetadataDriver('/path/to/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
ClassMetadata
-------------
The last piece you need to know and understand about metadata in
Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to
be familiar with them in order to implement your own drivers but
more importantly to retrieve mapping information for a certain
entity when needed.
You have all the methods you need to manually specify the mapping
information instead of using some mapping file to populate it from.
The base ``ClassMetadataInfo`` class is responsible for only data
storage and is not meant for runtime use. It does not require that
the class actually exists yet so it is useful for describing some
entity before it exists and using that information to generate for
example the entities themselves. The class ``ClassMetadata``
extends ``ClassMetadataInfo`` and adds some functionality required
for runtime usage and requires that the PHP class is present and
can be autoloaded.
You can read more about the API of the ``ClassMetadata`` classes in
the PHP Mapping chapter.
Getting ClassMetadata Instances
-------------------------------
If you want to get the ``ClassMetadata`` instance for an entity in
your project to programatically use some mapping information to
generate some HTML or something similar you can retrieve it through
the ``ClassMetadataFactory``:
.. code-block:: php
<?php
$cmf = $em->getMetadataFactory();
$class = $cmf->getMetadataFor('MyEntityName');
Now you can learn about the entity and use the data stored in the
``ClassMetadata`` instance to get all mapped fields for example and
iterate over them:
.. code-block:: php
<?php
foreach ($class->fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}

358
en/reference/native-sql.rst Normal file
View File

@ -0,0 +1,358 @@
Native SQL
==========
A ``NativeQuery`` lets you execute native SQL, mapping the results
according to your specifications. Such a specification that
describes how an SQL result set is mapped to a Doctrine result is
represented by a ``ResultSetMapping``. It describes how each column
of the database result should be mapped by Doctrine in terms of the
object graph. This allows you to map arbitrary SQL code to objects,
such as highly vendor-optimized SQL or stored-procedures.
The NativeQuery class
---------------------
To create a ``NativeQuery`` you use the method
``EntityManager#createNativeQuery($sql, $resultSetMapping)``. As
you can see in the signature of this method, it expects 2
ingredients: The SQL you want to execute and the
``ResultSetMapping`` that describes how the results will be
mapped.
Once you obtained an instance of a ``NativeQuery``, you can bind
parameters to it and finally execute it.
The ResultSetMapping
--------------------
Understanding the ``ResultSetMapping`` is the key to using a
``NativeQuery``. A Doctrine result can contain the following
components:
- Entity results. These represent root result elements.
- Joined entity results. These represent joined entities in
associations of root entity results.
- Field results. These represent a column in the result set that
maps to a field of an entity. A field result always belongs to an
entity result or joined entity result.
- Scalar results. These represent scalar values in the result set
that will appear in each result row. Adding scalar results to a
ResultSetMapping can also cause the overall result to become
**mixed** (see DQL - Doctrine Query Language) if the same
ResultSetMapping also contains entity results.
- Meta results. These represent columns that contain
meta-information, such as foreign keys and discriminator columns.
When querying for objects (``getResult()``), all meta columns of
root entities or joined entities must be present in the SQL query
and mapped accordingly using ``ResultSetMapping#addMetaResult``.
.. note::
It might not surprise you that Doctrine uses
``ResultSetMapping``s internally when you create DQL queries. As
the query gets parsed and transformed to SQL, Doctrine fills a
``ResultSetMapping`` that describes how the results should be
processed by the hydration routines.
We will now look at each of the result types that can appear in a
ResultSetMapping in detail.
Entity results
~~~~~~~~~~~~~~
An entity result describes an entity type that appears as a root
element in the transformed result. You add an entity result through
``ResultSetMapping#addEntityResult()``. Let's take a look at the
method signature in detail:
.. code-block:: php
<?php
/**
* Adds an entity result to this ResultSetMapping.
*
* @param string $class The class name of the entity.
* @param string $alias The alias for the class. The alias must be unique among all entity
* results or joined entity results within this ResultSetMapping.
*/
public function addEntityResult($class, $alias)
The first parameter is the fully qualified name of the entity
class. The second parameter is some arbitrary alias for this entity
result that must be unique within a ``ResultSetMapping``. You use
this alias to attach field results to the entity result. It is very
similar to an identification variable that you use in DQL to alias
classes or relationships.
An entity result alone is not enough to form a valid
``ResultSetMapping``. An entity result or joined entity result
always needs a set of field results, which we will look at soon.
Joined entity results
~~~~~~~~~~~~~~~~~~~~~
A joined entity result describes an entity type that appears as a
joined relationship element in the transformed result, attached to
a (root) entity result. You add a joined entity result through
``ResultSetMapping#addJoinedEntityResult()``. Let's take a look at
the method signature in detail:
.. code-block:: php
<?php
/**
* Adds a joined entity result.
*
* @param string $class The class name of the joined entity.
* @param string $alias The unique alias to use for the joined entity.
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
* @param object $relation The association field that connects the parent entity result with the joined entity result.
*/
public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
The first parameter is the class name of the joined entity. The
second parameter is an arbitrary alias for the joined entity that
must be unique within the ``ResultSetMapping``. You use this alias
to attach field results to the entity result. The third parameter
is the alias of the entity result that is the parent type of the
joined relationship. The fourth and last parameter is the name of
the field on the parent entity result that should contain the
joined entity result.
Field results
~~~~~~~~~~~~~
A field result describes the mapping of a single column in an SQL
result set to a field in an entity. As such, field results are
inherently bound to entity results. You add a field result through
``ResultSetMapping#addFieldResult()``. Again, let's examine the
method signature in detail:
.. code-block:: php
<?php
/**
* Adds a field result that is part of an entity result or joined entity result.
*
* @param string $alias The alias of the entity result or joined entity result.
* @param string $columnName The name of the column in the SQL result set.
* @param string $fieldName The name of the field on the (joined) entity.
*/
public function addFieldResult($alias, $columnName, $fieldName)
The first parameter is the alias of the entity result to which the
field result will belong. The second parameter is the name of the
column in the SQL result set. Note that this name is case
sensitive, i.e. if you use a native query against Oracle it must be
all uppercase. The third parameter is the name of the field on the
entity result identified by ``$alias`` into which the value of the
column should be set.
Scalar results
~~~~~~~~~~~~~~
A scalar result describes the mapping of a single column in an SQL
result set to a scalar value in the Doctrine result. Scalar results
are typically used for aggregate values but any column in the SQL
result set can be mapped as a scalar value. To add a scalar result
use ``ResultSetMapping#addScalarResult()``. The method signature in
detail:
.. code-block:: php
<?php
/**
* Adds a scalar result mapping.
*
* @param string $columnName The name of the column in the SQL result set.
* @param string $alias The result alias with which the scalar result should be placed in the result structure.
*/
public function addScalarResult($columnName, $alias)
The first parameter is the name of the column in the SQL result set
and the second parameter is the result alias under which the value
of the column will be placed in the transformed Doctrine result.
Meta results
~~~~~~~~~~~~
A meta result describes a single column in an SQL result set that
is either a foreign key or a discriminator column. These columns
are essential for Doctrine to properly construct objects out of SQL
result sets. To add a column as a meta result use
``ResultSetMapping#addMetaResult()``. The method signature in
detail:
.. code-block:: php
<?php
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
* @param string $alias
* @param string $columnAlias
* @param string $columnName
*/
public function addMetaResult($alias, $columnAlias, $columnName)
The first parameter is the alias of the entity result to which the
meta column belongs. A meta result column (foreign key or
discriminator column) always belongs to to an entity result. The
second parameter is the column alias/name of the column in the SQL
result set and the third parameter is the column name used in the
mapping.
Discriminator Column
~~~~~~~~~~~~~~~~~~~~
When joining an inheritance tree you have to give Doctrine a hint
which meta-column is the discriminator column of this tree.
.. code-block:: php
<?php
/**
* Sets a discriminator column for an entity result or joined entity result.
* The discriminator column will be used to determine the concrete class name to
* instantiate.
*
* @param string $alias The alias of the entity result or joined entity result the discriminator
* column should be used for.
* @param string $discrColumn The name of the discriminator column in the SQL result set.
* @todo Rename: addDiscriminatorColumn
*/
public function setDiscriminatorColumn($alias, $discrColumn)
Examples
~~~~~~~~
Understanding a ResultSetMapping is probably easiest through
looking at some examples.
First a basic example that describes the mapping of a single
entity.
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u where u.name=?1"
// User owns no associations.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
The result would look like this:
.. code-block:: php
array(
[0] => User (Object)
)
Note that this would be a partial object if the entity has more
fields than just id and name. In the example above the column and
field names are identical but that is not necessary, of course.
Also note that the query string passed to createNativeQuery is
**real native SQL**. Doctrine does not touch this SQL in any way.
In the previous basic example, a User had no relations and the
table the class is mapped to owns no foreign keys. The next example
assumes User has a unidirectional or bidirectional one-to-one
association to a CmsAddress, where the User is the owning side and
thus owns the foreign key.
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u where u.name=?1"
// User owns an association to an Address but the Address is not loaded in the query.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Foreign keys are used by Doctrine for lazy-loading purposes when
querying for objects. In the previous example, each user object in
the result will have a proxy (a "ghost") in place of the address
that contains the address\_id. When the ghost proxy is accessed, it
loads itself based on this key.
Consequently, associations that are *fetch-joined* do not require
the foreign keys to be present in the SQL result set, only
associations that are lazy.
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u join u.address a WHERE u.name = ?1"
// User owns association to an Address and the Address is loaded in the query.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address');
$rsm->addFieldResult('a', 'address_id', 'id');
$rsm->addFieldResult('a', 'street', 'street');
$rsm->addFieldResult('a', 'city', 'city');
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
In this case the nested entity ``Address`` is registered with the
``ResultSetMapping#addJoinedEntityResult`` method, which notifies
Doctrine that this entity is not hydrated at the root level, but as
a joined entity somewhere inside the object graph. In this case we
specify the alias 'u' as third parameter and ``address`` as fourth
parameter, which means the ``Address`` is hydrated into the
``User::$address`` property.
If a fetched entity is part of a mapped hierarchy that requires a
discriminator column, this column must be present in the result set
as a meta column so that Doctrine can create the appropriate
concrete type. This is shown in the following example where we
assume that there are one or more subclasses that extend User and
either Class Table Inheritance or Single Table Inheritance is used
to map the hierarchy (both use a discriminator column).
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u where u.name=?1"
// User is a mapped base class for other classes. User owns no associations.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Note that in the case of Class Table Inheritance, an example as
above would result in partial objects if any objects in the result
are actually a subtype of User. When using DQL, Doctrine
automatically includes the necessary joins for this mapping
strategy but with native SQL it is your responsibility.

View File

@ -0,0 +1,70 @@
Partial Objects
===============
A partial object is an object whose state is not fully initialized
after being reconstituted from the database and that is
disconnected from the rest of its data. The following section will
describe why partial objects are problematic and what the approach
of Doctrine2 to this problem is.
.. note::
The partial object problem in general does not apply to
methods or queries where you do not retrieve the query result as
objects. Examples are: ``Query#getArrayResult()``,
``Query#getScalarResult()``, ``Query#getSingleScalarResult()``,
etc.
What is the problem?
--------------------
In short, partial objects are problematic because they are usually
objects with broken invariants. As such, code that uses these
partial objects tends to be very fragile and either needs to "know"
which fields or methods can be safely accessed or add checks around
every field access or method invocation. The same holds true for
the internals, i.e. the method implementations, of such objects.
You usually simply assume the state you need in the method is
available, after all you properly constructed this object before
you pushed it into the database, right? These blind assumptions can
quickly lead to null reference errors when working with such
partial objects.
It gets worse with the scenario of an optional association (0..1 to
1). When the associated field is NULL, you don't know whether this
object does not have an associated object or whether it was simply
not loaded when the owning object was loaded from the database.
These are reasons why many ORMs do not allow partial objects at all
and instead you always have to load an object with all its fields
(associations being proxied). One secure way to allow partial
objects is if the programming language/platform allows the ORM tool
to hook deeply into the object and instrument it in such a way that
individual fields (not only associations) can be loaded lazily on
first access. This is possible in Java, for example, through
bytecode instrumentation. In PHP though this is not possible, so
there is no way to have "secure" partial objects in an ORM with
transparent persistence.
Doctrine, by default, does not allow partial objects. That means,
any query that only selects partial object data and wants to
retrieve the result as objects (i.e. ``Query#getResult()``) will
raise an exception telling you that partial objects are dangerous.
If you want to force a query to return you partial objects,
possibly as a performance tweak, you can use the ``partial``
keyword as follows:
.. code-block:: php
<?php
$q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u");
When should I force partial objects?
------------------------------------
Mainly for optimization purposes, but be careful of premature
optimization as partial objects lead to potentially more fragile
code.

View File

@ -0,0 +1,253 @@
PHP Mapping
===========
Doctrine 2 also allows you to provide the ORM metadata in the form
of plain PHP code using the ``ClassMetadata`` API. You can write
the code in PHP files or inside of a static function named
``loadMetadata($class)`` on the entity class itself.
PHP Files
---------
If you wish to write your mapping information inside PHP files that
are named after the entity and included to populate the metadata
for an entity you can do so by using the ``PHPDriver``:
.. code-block:: php
<?php
$driver = new PHPDriver('/path/to/php/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now imagine we had an entity named ``Entities\User`` and we wanted
to write a mapping file for it using the above configured
``PHPDriver`` instance:
.. code-block:: php
<?php
namespace Entities;
class User
{
private $id;
private $username;
}
To write the mapping information you just need to create a file
named ``Entities.User.php`` inside of the
``/path/to/php/mapping/files`` folder:
.. code-block:: php
<?php
// /path/to/php/mapping/files/Entities.User.php
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string'
));
Now we can easily retrieve the populated ``ClassMetadata`` instance
where the ``PHPDriver`` includes the file and the
``ClassMetadataFactory`` caches it for later retrieval:
.. code-block:: php
<?php
$class = $em->getClassMetadata('Entities\User');
// or
$class = $em->getMetadataFactory()->getMetadataFor('Entities\User');
Static Function
---------------
In addition to the PHP files you can also specify your mapping
information inside of a static function defined on the entity class
itself. This is useful for cases where you want to keep your entity
and mapping information together but don't want to use annotations.
For this you just need to use the ``StaticPHPDriver``:
.. code-block:: php
<?php
$driver = new StaticPHPDriver('/path/to/entities');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now you just need to define a static function named
``loadMetadata($metadata)`` on your entity:
.. code-block:: php
<?php
namespace Entities;
use Doctrine\ORM\Mapping\ClassMetadata;
class User
{
// ...
public static function loadMetadata(ClassMetadata $metadata)
{
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string'
));
}
}
ClassMetadataInfo API
---------------------
The ``ClassMetadataInfo`` class is the base data object for storing
the mapping metadata for a single entity. It contains all the
getters and setters you need populate and retrieve information for
an entity.
General Setters
~~~~~~~~~~~~~~~
- ``setTableName($tableName)``
- ``setPrimaryTable(array $primaryTableDefinition)``
- ``setCustomRepositoryClass($repositoryClassName)``
- ``setIdGeneratorType($generatorType)``
- ``setIdGenerator($generator)``
- ``setSequenceGeneratorDefinition(array $definition)``
- ``setChangeTrackingPolicy($policy)``
- ``setIdentifier(array $identifier)``
Inheritance Setters
~~~~~~~~~~~~~~~~~~~
- ``setInheritanceType($type)``
- ``setSubclasses(array $subclasses)``
- ``setParentClasses(array $classNames)``
- ``setDiscriminatorColumn($columnDef)``
- ``setDiscriminatorMap(array $map)``
Field Mapping Setters
~~~~~~~~~~~~~~~~~~~~~
- ``mapField(array $mapping)``
- ``mapOneToOne(array $mapping)``
- ``mapOneToMany(array $mapping)``
- ``mapManyToOne(array $mapping)``
- ``mapManyToMany(array $mapping)``
Lifecycle Callback Setters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``addLifecycleCallback($callback, $event)``
- ``setLifecycleCallbacks(array $callbacks)``
Versioning Setters
~~~~~~~~~~~~~~~~~~
- ``setVersionMapping(array &$mapping)``
- ``setVersioned($bool)``
- ``setVersionField()``
General Getters
~~~~~~~~~~~~~~~
- ``getTableName()``
- ``getTemporaryIdTableName()``
Identifier Getters
~~~~~~~~~~~~~~~~~~
- ``getIdentifierColumnNames()``
- ``usesIdGenerator()``
- ``isIdentifier($fieldName)``
- ``isIdGeneratorIdentity()``
- ``isIdGeneratorSequence()``
- ``isIdGeneratorTable()``
- ``isIdentifierNatural()``
- ``getIdentifierFieldNames()``
- ``getSingleIdentifierFieldName()``
- ``getSingleIdentifierColumnName()``
Inheritance Getters
~~~~~~~~~~~~~~~~~~~
- ``isInheritanceTypeNone()``
- ``isInheritanceTypeJoined()``
- ``isInheritanceTypeSingleTable()``
- ``isInheritanceTypeTablePerClass()``
- ``isInheritedField($fieldName)``
- ``isInheritedAssociation($fieldName)``
Change Tracking Getters
~~~~~~~~~~~~~~~~~~~~~~~
- ``isChangeTrackingDeferredExplicit()``
- ``isChangeTrackingDeferredImplicit()``
- ``isChangeTrackingNotify()``
Field & Association Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``isUniqueField($fieldName)``
- ``isNullable($fieldName)``
- ``getColumnName($fieldName)``
- ``getFieldMapping($fieldName)``
- ``getAssociationMapping($fieldName)``
- ``getAssociationMappings()``
- ``getFieldName($columnName)``
- ``hasField($fieldName)``
- ``getColumnNames(array $fieldNames = null)``
- ``getTypeOfField($fieldName)``
- ``getTypeOfColumn($columnName)``
- ``hasAssociation($fieldName)``
- ``isSingleValuedAssociation($fieldName)``
- ``isCollectionValuedAssociation($fieldName)``
Lifecycle Callback Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``hasLifecycleCallbacks($lifecycleEvent)``
- ``getLifecycleCallbacks($event)``
ClassMetadata API
-----------------
The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds
the runtime functionality required by Doctrine. It adds a few extra
methods related to runtime reflection for working with the entities
themselves.
- ``getReflectionClass()``
- ``getReflectionProperties()``
- ``getReflectionProperty($name)``
- ``getSingleIdReflectionProperty()``
- ``getIdentifierValues($entity)``
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``

View File

@ -1,40 +1,55 @@
The QueryBuilder
================
++ The QueryBuilder
A ``QueryBuilder`` provides an API that is designed for
conditionally constructing a DQL query in several steps.
A `QueryBuilder` provides an API that is designed for conditionally constructing a DQL query in several steps.
It provides a set of classes and methods that is able to
programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, and also pick one if you prefer.
It provides a set of classes and methods that is able to programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other as you want, and also pick one if you prefer.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+++ Constructing a new QueryBuilder object
The same way you build a normal Query, you build a ``QueryBuilder``
object, just providing the correct method name. Here is an example
how to build a ``QueryBuilder`` object:
The same way you build a normal Query, you build a `QueryBuilder` object, just providing the correct method name.
Here is an example how to build a `QueryBuilder` object:
.. code-block:: php
[php]
<?php
// $em instanceof EntityManager
// example1: creating a QueryBuilder instance
$qb = $em->createQueryBuilder();
Once you have created an instance of QueryBuilder, it provides a set of useful informative functions that you can use.
One good example is to inspect what type of object the `QueryBuilder` is.
[php]
Once you have created an instance of QueryBuilder, it provides a
set of useful informative functions that you can use. One good
example is to inspect what type of object the ``QueryBuilder`` is.
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example2: retrieving type of QueryBuilder
echo $qb->getType(); // Prints: 0
There're currently 3 possible return values for `getType()`:
* `QueryBuilder::SELECT`, which returns value 0
* `QueryBuilder::DELETE`, returning value 1
* `QueryBuilder::UPDATE`, which returns value 2
There're currently 3 possible return values for ``getType()``:
It is possible to retrieve the associated `EntityManager` of the current `QueryBuilder`, its DQL and also a `Query` object when you finish building your DQL.
[php]
- ``QueryBuilder::SELECT``, which returns value 0
- ``QueryBuilder::DELETE``, returning value 1
- ``QueryBuilder::UPDATE``, which returns value 2
It is possible to retrieve the associated ``EntityManager`` of the
current ``QueryBuilder``, its DQL and also a ``Query`` object when
you finish building your DQL.
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example3: retrieve the associated EntityManager
@ -45,25 +60,41 @@ It is possible to retrieve the associated `EntityManager` of the current `QueryB
// example5: retrieve the associated Query object with the processed DQL
$q = $qb->getQuery();
Internally, `QueryBuilder` works with a DQL cache to increase performance. Any changes that may affect the generated DQL actually modifies the state of `QueryBuilder` to a stage we call STATE_DIRTY.
One `QueryBuilder` can be in two different states:
* `QueryBuilder::STATE_CLEAN`, which means DQL haven't been altered since last retrieval or nothing were added since its instantiation
* `QueryBuilder::STATE_DIRTY`, means DQL query must (and will) be processed on next retrieval
+++ Working with QueryBuilder
Internally, ``QueryBuilder`` works with a DQL cache to increase
performance. Any changes that may affect the generated DQL actually
modifies the state of ``QueryBuilder`` to a stage we call
STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
All helper methods in `QueryBuilder` actually rely on a single one: `add()`.
This method is responsible of building every piece of DQL. It takes 3 parameters: `$dqlPartName`, `$dqlPart` and `$append` (default=false)
* `$dqlPartName`: Where the `$dqlPart` should be placed. Possible values: select, from, where, groupBy, having, orderBy
* `$dqlPart`: What should be placed in `$dqlPartName`. Accepts a string or any instance of `Doctrine\ORM\Query\Expr\*`
* `$append`: Optional flag (default=false) if the `$dqlPart` should override all previously defined items in `$dqlPartName` or not
- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been
altered since last retrieval or nothing were added since its
instantiation
- ``QueryBuilder::STATE_DIRTY``, means DQL query must (and will)
be processed on next retrieval
Working with QueryBuilder
~~~~~~~~~~~~~~~~~~~~~~~~~
All helper methods in ``QueryBuilder`` actually rely on a single
one: ``add()``. This method is responsible of building every piece
of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
``$append`` (default=false)
- ``$dqlPartName``: Where the ``$dqlPart`` should be placed.
Possible values: select, from, where, groupBy, having, orderBy
- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts
a string or any instance of ``Doctrine\ORM\Query\Expr\*``
- ``$append``: Optional flag (default=false) if the ``$dqlPart``
should override all previously defined items in ``$dqlPartName`` or
not
-
[php]
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
@ -72,11 +103,18 @@ This method is responsible of building every piece of DQL. It takes 3 parameters
->add('where', 'u.id = ?1')
->add('orderBy', 'u.name ASC');
++++ Binding parameters to your query
Binding parameters to your query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Doctrine supports dynamic binding of parameters to your query, similar to preparing queries. You can use both strings and numbers as placeholders, although both have a slightly different syntax. Additionally, you must make your choice: Mixing both styles is not allowed. Binding parameters can simply be achieved as follows:
Doctrine supports dynamic binding of parameters to your query,
similar to preparing queries. You can use both strings and numbers
as placeholders, although both have a slightly different syntax.
Additionally, you must make your choice: Mixing both styles is not
allowed. Binding parameters can simply be achieved as follows:
[php]
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
@ -86,10 +124,12 @@ Doctrine supports dynamic binding of parameters to your query, similar to prepar
->add('orderBy', 'u.name ASC');
->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100
You are not forced to enumerate your placeholders as the alternative syntax is available:
You are not forced to enumerate your placeholders as the
alternative syntax is available:
.. code-block:: php
[php]
<?php
// $qb instanceof QueryBuilder
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
@ -99,20 +139,27 @@ You are not forced to enumerate your placeholders as the alternative syntax is a
->add('orderBy', 'u.name ASC');
->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100
Note that numeric placeholders start with a ? followed by a number while the named placeholders start with a : followed by a string.
Note that numeric placeholders start with a ? followed by a number
while the named placeholders start with a : followed by a string.
If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax:
If you've got several parameters to bind to your query, you can
also use setParameters() instead of setParameter() with the
following syntax:
[php]
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// Query here...
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
Getting already bound parameters is easy - simply use the above
mentioned syntax with "getParameter()" or "getParameters()":
Getting already bound parameters is easy - simply use the above mentioned syntax with "getParameter()" or "getParameters()":
.. code-block:: php
[php]
<?php
// $qb instanceof QueryBuilder
// See example above
@ -120,14 +167,20 @@ Getting already bound parameters is easy - simply use the above mentioned syntax
// Equivalent to
$param = array($qb->getParameter(1), $qb->getParameter(2));
Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL.
Note: If you try to get a parameter that was not bound yet,
getParameter() simply returns NULL.
++++ Expr\* classes
Expr\* classes
^^^^^^^^^^^^^^
When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class.
Here is the same query of example 6 written using `Doctrine\ORM\Query\Expr\Expr\*` classes:
When you call ``add()`` with string, it internally evaluates to an
instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the
same query of example 6 written using
``Doctrine\ORM\Query\Expr\Expr\*`` classes:
[php]
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances
@ -136,14 +189,21 @@ Here is the same query of example 6 written using `Doctrine\ORM\Query\Expr\Expr\
->add('where', new Expr\Comparison('u.id', '=', '?1'))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
Of course this is the hardest way to build a DQL query in Doctrine. To simplify some of these efforts, we introduce what we call as `Expr` helper class.
++++ The Expr class
Of course this is the hardest way to build a DQL query in Doctrine.
To simplify some of these efforts, we introduce what we call as
``Expr`` helper class.
To workaround most of the issues that `add()` method may cause, Doctrine created a class that can be considered as a helper for building queries.
This class is called `Expr`, which provides a set of useful static methods to help building queries:
The Expr class
^^^^^^^^^^^^^^
[php]
To workaround most of the issues that ``add()`` method may cause,
Doctrine created a class that can be considered as a helper for
building queries. This class is called ``Expr``, which provides a
set of useful static methods to help building queries:
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class
@ -154,21 +214,24 @@ This class is called `Expr`, which provides a set of useful static methods to he
$qb->expr()->like('u.nickname', '?2')
))
->add('orderBy', $qb->expr()->orderBy('u.surname', 'ASC'));
Although it still sounds complex, the ability to programmatically create conditions are the main feature of `Expr`.
Here it is a complete list of supported helper methods available:
[php]
Although it still sounds complex, the ability to programmatically
create conditions are the main feature of ``Expr``. Here it is a
complete list of supported helper methods available:
.. code-block:: php
<?php
class Expr
{
/** Base objects **/
// Example usage - $qb->expr()->select('u')
public function select($select = null); // Returns Expr\Select instance
// Example - $qb->expr()->from('User', 'u')
public function from($from, $alias); // Returns Expr\From instance
// Example - $qb->expr()->leftJoin('u.Phonenumbers', 'p', Expr\Join::ON, 'p.user_id = u.id AND p.country_code = 55');
// Example - $qb->expr()->leftJoin('u. Phonenumbers', 'p', 'ON', $qb->expr()->andx($qb->expr()->eq('p.user_id', 'u.id'), $qb->expr()->eq('p.country_code', '55'));
public function leftJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance
@ -176,46 +239,46 @@ Here it is a complete list of supported helper methods available:
// Example - $qb->expr()->innerJoin('u.Group', 'g', Expr\Join::WITH, 'g.manager_level = 100');
// Example - $qb->expr()->innerJoin('u.Group', 'g', 'WITH', $qb->expr()->eq('g.manager_level', '100'));
public function innerJoin($join, $alias, $conditionType = null, $condition = null); // Returns Expr\Join instance
// Example - $qb->expr()->orderBy('u.surname', 'ASC')->add('u.firstname', 'ASC')->...
public function orderBy($sort = null, $order = null); // Returns Expr\OrderBy instance
// Example - $qb->expr()->groupBy()->add('u.id')->...
public function groupBy($groupBy = null); // Returns Expr\GroupBy instance
/** Conditional objects **/
// Example - $qb->expr()->andx($cond1 [, $condN])->add(...)->...
public function andx($x = null); // Returns Expr\Andx instance
// Example - $qb->expr()->orx($cond1 [, $condN])->add(...)->...
public function orx($x = null); // Returns Expr\Orx instance
/** Comparison objects **/
// Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
public function eq($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1
public function neq($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1
public function lt($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1
public function lte($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1
public function gt($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1
public function gte($x, $y); // Returns Expr\Comparison instance
/** Arithmetic objects **/
// Example - $qb->expr()->prod('u.id', '2') => u.id * 2
public function prod($x, $y); // Returns Expr\Math instance
@ -227,25 +290,25 @@ Here it is a complete list of supported helper methods available:
// Example - $qb->expr()->quot('u.id', '2') => u.id / 2
public function quot($x, $y); // Returns Expr\Math instance
/** Pseudo-function objects **/
// Example - $qb->expr()->exists($qb2->getDql())
public function exists($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->all($qb2->getDql())
public function all($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->some($qb2->getDql())
public function some($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->any($qb2->getDql())
public function any($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1'))
public function not($restriction); // Returns Expr\Func instance
// Example - $qb->expr()->in('u.id', array(1, 2, 3))
// Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception.
// Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above)
@ -256,16 +319,16 @@ Here it is a complete list of supported helper methods available:
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
public function like($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->between('u.id', '1', '10')
public function between($val, $x, $y); // Returns Expr\Func
/** Function objects **/
// Example - $qb->expr()->trim('u.firstname')
public function trim($x); // Returns Expr\Func
// Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat(' ', 'u.lastname'))
public function concat($x, $y); // Returns Expr\Func
@ -303,13 +366,22 @@ Here it is a complete list of supported helper methods available:
public function countDistinct($x); // Returns Expr\Func
}
++++ Helper methods
Helper methods
^^^^^^^^^^^^^^
Until now we have described the lowest level (thought of as the hardcore method) of creating queries. It may be useful to work at this level for optimization purposes, but most of the time it is preferred to work at a higher level of abstraction.
To simplify even more the way you build a query in Doctrine, we can take advantage of what we call Helper methods. For all base code, there is a set of useful methods to simplify a programmer's life.
To illustrate how to work with them, here is the same example 6 re-written using `QueryBuilder` helper methods:
Until now we have described the lowest level (thought of as the
hardcore method) of creating queries. It may be useful to work at
this level for optimization purposes, but most of the time it is
preferred to work at a higher level of abstraction. To simplify
even more the way you build a query in Doctrine, we can take
advantage of what we call Helper methods. For all base code, there
is a set of useful methods to simplify a programmer's life. To
illustrate how to work with them, here is the same example 6
re-written using ``QueryBuilder`` helper methods:
[php]
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example9: how to define: "SELECT u FROM User u WHERE u.id = ?1 ORDER BY u.name ASC" using QueryBuilder helper methods
@ -318,10 +390,15 @@ To illustrate how to work with them, here is the same example 6 re-written using
->where('u.id = ?1')
->orderBy('u.name ASC');
`QueryBuilder` helper methods are considered the standard way to build DQL queries. Although it is supported, it should be avoided to use string based queries and greatly encouraged to use `$qb->expr()->*` methods.
Here is a converted example 8 to suggested standard way to build queries:
``QueryBuilder`` helper methods are considered the standard way to
build DQL queries. Although it is supported, it should be avoided
to use string based queries and greatly encouraged to use
``$qb->expr()->*`` methods. Here is a converted example 8 to
suggested standard way to build queries:
[php]
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ?1 OR u.nickname LIKE ?2 ORDER BY u.surname DESC" using QueryBuilder helper methods
@ -333,68 +410,71 @@ Here is a converted example 8 to suggested standard way to build queries:
))
->orderBy('u.surname', 'ASC'));
Here is a complete list of helper methods available in `QueryBuilder`:
Here is a complete list of helper methods available in
``QueryBuilder``:
[php]
.. code-block:: php
<?php
class QueryBuilder
{
// Example - $qb->select('u')
// Example - $qb->select(array('u', 'p'))
// Example - $qb->select($qb->expr()->select('u', 'p'))
public function select($select = null);
// Example - $qb->delete('User', 'u')
public function delete($delete = null, $alias = null);
// Example - $qb->update('Group', 'g')
public function update($update = null, $alias = null);
// Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold'))
// Example - $qb->set('u.numChilds', 'u.numChilds + ?1')
// Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1'))
public function set($key, $value);
// Example - $qb->from('Phonenumber', 'p')
public function from($from, $alias = null);
// Example - $qb->innerJoin('u.Group', 'g', Expr\Join::ON, $qb->expr()->and($qb->expr()->eq('u.group_id', 'g.id'), 'g.name = ?1'))
// Example - $qb->innerJoin('u.Group', 'g', 'ON', 'u.group_id = g.id AND g.name = ?1')
public function innerJoin($join, $alias = null, $conditionType = null, $condition = null);
// Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55))
// Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55')
public function leftJoin($join, $alias = null, $conditionType = null, $condition = null);
// NOTE: ->where() overrides all previously set conditions
//
// Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2'))
// Example - $qb->where($qb->expr()->andx($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
// Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
public function where($where);
// Example - $qb->andWhere($qb->expr()->orx($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
public function andWhere($where);
// Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10));
public function orWhere($where);
// NOTE: -> groupBy() overrides all previously set grouping conditions
//
// Example - $qb->groupBy('u.id')
public function groupBy($groupBy);
// Example - $qb->addGroupBy('g.name')
public function addGroupBy($groupBy);
// NOTE: -> having() overrides all previously set having conditions
//
// Example - $qb->having('u.salary >= ?1')
// Example - $qb->having($qb->expr()->gte('u.salary', '?1'))
public function having($having);
// Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0))
public function andHaving($having);
// Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100'))
public function orHaving($having);
@ -402,7 +482,9 @@ Here is a complete list of helper methods available in `QueryBuilder`:
//
// Example - $qb->orderBy('u.surname', 'DESC')
public function orderBy($sort, $order = null);
// Example - $qb->addOrderBy('u.firstName')
public function addOrderBy($sort, $order = null); // Default $order = 'ASC'
}

381
en/reference/tools.rst Normal file
View File

@ -0,0 +1,381 @@
Tools
=====
Doctrine Console
----------------
The Doctrine Console is a Command Line Interface tool for
simplifying common tasks during the development of a project that
uses Doctrine 2.
Installation
~~~~~~~~~~~~
If you installed Doctrine 2 through PEAR, the ``doctrine`` command
line tool should already be available to you.
If you use Doctrine through SVN or a release package you need to
copy the ``doctrine`` and ``doctrine.php`` files from the
``tools/sandbox`` or ``bin`` folder, respectively, to a location of
your choice, for example a ``tools`` folder of your project. You
probably need to edit ``doctrine.php`` to adjust some paths to the
new environment, most importantly the first line that includes the
``Doctrine\Common\ClassLoader``.
Getting Help
~~~~~~~~~~~~
Type ``doctrine`` on the command line and you should see an
overview of the available commands or use the --help flag to get
information on the available commands. If you want to know more
about the use of generate entities for example, you can call:
.. code-block:: php
doctrine orm:generate-entities --help
Configuration
~~~~~~~~~~~~~
Whenever the ``doctrine`` command line tool is invoked, it can
access alls Commands that were registered by developer. There is no
auto-detection mechanism at work. The ``bin\doctrine.php`` file
already registers all the commands that currently ship with
Doctrine DBAL and ORM. If you want to use additional commands you
have to register them yourself.
All the commands of the Doctrine Console require either the ``db``
or the ``em`` helpers to be defined in order to work correctly.
Doctrine Console requires the definition of a HelperSet that is the
DI tool to be injected in the Console. In case of a project that is
dealing exclusively with DBAL, the ConnectionHelper is required:
.. code-block:: php
<?php
$helperSet = new \Symfony\Components\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
));
$cli->setHelperSet($helperSet);
When dealing with the ORM package, the EntityManagerHelper is
required:
.. code-block:: php
<?php
$helperSet = new \Symfony\Components\Console\Helper\HelperSet(array(
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
$cli->setHelperSet($helperSet);
The HelperSet instance has to be generated in a separate file (i.e.
``cli-config.php``) that contains typical Doctrine bootstrap code
and predefines the needed HelperSet attributes mentioned above. A
typical ``cli-config.php`` file looks as follows:
.. code-block:: php
<?php
require_once __DIR__ . '/../../lib/Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Entities', __DIR__);
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__);
$classLoader->register();
$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
$config->setProxyDir(__DIR__ . '/Proxies');
$config->setProxyNamespace('Proxies');
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
$helperSet = new \Symfony\Components\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
It is important to define a correct HelperSet that doctrine.php
script will ultimately use. The Doctrine Binary will automatically
find the first instance of HelperSet in the global variable
namespace and use this.
You can also add your own commands on-top of the Doctrine supported
tools. To include a new command on Doctrine Console, you need to
do:
.. code-block:: php
<?php
$cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand());
Additionally, include multiple commands (and overriding previously
defined ones) is possible through the command:
.. code-block:: php
<?php
$cli->addCommands(array(
new \MyProject\Tools\Console\Commands\MyCustomCommand(),
new \MyProject\Tools\Console\Commands\SomethingCommand(),
new \MyProject\Tools\Console\Commands\AnotherCommand(),
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
));
Command Overview
~~~~~~~~~~~~~~~~
The following Commands are currently available:
- ``help`` Displays help for a command (?)
- ``list`` Lists commands
- ``dbal:import`` Import SQL file(s) directly to Database.
- ``dbal:run-sql`` Executes arbitrary SQL directly from the
command line.
- ``orm:clear-cache:metadata`` Clear all metadata cache of the
various cache drivers.
- ``orm:clear-cache:query`` Clear all query cache of the various
cache drivers.
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a
Doctrine 2.X schema.
- ``orm:convert-mapping`` Convert mapping information between
supported formats.
- ``orm:ensure-production-settings`` Verify that Doctrine is
properly configured for a production environment.
- ``orm:generate-entities`` Generate entity classes and method
stubs from your mapping information.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes.
- ``orm:generate-repositories`` Generate repository classes from
your mapping information.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either
create it directly on EntityManager Storage Connection or generate
the SQL output.
- ``orm:schema-tool:drop`` Processes the schema and either drop
the database schema of EntityManager Storage Connection or generate
the SQL output.
- ``orm:schema-tool:update`` Processes the schema and either
update the database schema of EntityManager Storage Connection or
generate the SQL output.
Database Schema Generation
--------------------------
.. note::
SchemaTool can do harm to your database. It will drop or alter
tables, indexes, sequences and such. Please use this tool with
caution in development and not on a production server. It is meant
for helping you develop your Database Schema, but NOT with
migrating schema from A to B in production. A safe approach would
be generating the SQL on development server and saving it into SQL
Migration files that are executed manually on the production
server.
SchemaTool assumes your Doctrine Project uses the given database on
its own. Update and Drop commands will mess with other tables if
they are not related to the current project that is using Doctrine.
Please be careful!
To generate your database schema from your Doctrine mapping files
you can use the ``SchemaTool`` class or the ``schema-tool`` Console
Command.
When using the SchemaTool class directly, create your schema using
the ``createSchema()`` method. First create an instance of the
``SchemaTool`` and pass it an instance of the ``EntityManager``
that you want to use to create the schema. This method receives an
array of ``ClassMetadataInfo`` instances.
.. code-block:: php
<?php
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$tool->createSchema($classes);
To drop the schema you can use the ``dropSchema()`` method.
.. code-block:: php
<?php
$tool->dropSchema($classes);
This drops all the tables that are currently used by your metadata
model. When you are changing your metadata a lot during development
you might want to drop the complete database instead of only the
tables of the current model to clean up with orphaned tables.
.. code-block:: php
<?php
$tool->dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE);
You can also use database introspection to update your schema
easily with the ``updateSchema()`` method. It will compare your
existing database schema to the passed array of
``ClassMetdataInfo`` instances.
.. code-block:: php
<?php
$tool->updateSchema($classes);
If you want to use this functionality from the command line you can
use the ``schema-tool`` command.
To create the schema use the ``create`` command:
.. code-block:: php
$ php doctrine orm:schema-tool:create
To drop the schema use the ``drop`` command:
.. code-block:: php
$ php doctrine orm:schema-tool:drop
If you want to drop and then recreate the schema then use both
options:
.. code-block:: php
$ php doctrine orm:schema-tool:drop
$ php doctrine orm:schema-tool:create
As you would think, if you want to update your schema use the
``update`` command:
.. code-block:: php
$ php doctrine orm:schema-tool:update
All of the above commands also accept a ``--dump-sql`` option that
will output the SQL for the ran operation.
.. code-block:: php
$ php doctrine orm:schema-tool:create --dump-sql
Before using the orm:schema-tool commands, remember to configure
your cli-config.php properly.
.. note::
When using the Annotation Mapping Driver you have to either setup
your autoloader in the cli-config.php correctly to find all the
entities, or you can use the second argument of the
``EntityManagerHelper`` to specify all the paths of your entities
(or mapping files), i.e.
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
Convert Mapping Information
---------------------------
To convert some mapping information between the various supported
formats you can use the ``ClassMetadataExporter`` to get exporter
instances for the different formats:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
Once you have a instance you can use it to get an exporter. For
example, the yml exporter:
.. code-block:: php
<?php
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
Now you can export some ``ClassMetadata`` instances:
.. code-block:: php
<?php
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$exporter->setMetadata($classes);
$exporter->export();
This functionality is also available from the command line to
convert your loaded mapping information to another format. The
``orm:convert-mapping`` command accepts two arguments, the type to
convert to and the path to generate it:
.. code-block:: php
$ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
Reverse Engineering
-------------------
You can use the ``DatabaseDriver`` to reverse engineer a database
to an array of ``ClassMetadataInfo`` instances and generate YAML,
XML, etc. from them.
First you need to retrieve the metadata instances with the
``DatabaseDriver``:
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataDriverImpl(
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
)
);
$cmf = new DisconnectedClassMetadataFactory($em);
$metadata = $cmf->getAllMetadata();
Now you can get an exporter instance and export the loaded metadata
to yml:
.. code-block:: php
<?php
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
You can also reverse engineer a database using the
``orm:convert-mapping`` command:
.. code-block:: php
$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
.. note::
Reverse Engineering is not always working perfectly
depending on special cases. It will only detect Many-To-One
relations (even if they are One-To-One) and will try to create
entities from Many-To-Many tables. It also has problems with naming
of foreign keys that have multiple column names. Any Reverse
Engineered Database-Schema needs considerable manual work to become
a useful domain model.

View File

@ -0,0 +1,354 @@
Transactions and Concurrency
============================
Transaction Demarcation
-----------------------
Transaction demarcation is the task of defining your transaction
boundaries. Proper transaction demarcation is very important
because if not done properly it can negatively affect the
performance of your application. Many databases and database
abstraction layers like PDO by default operate in auto-commit mode,
which means that every single SQL statement is wrapped in a small
transaction. Without any explicit transaction demarcation from your
side, this quickly results in poor performance because transactions
are not cheap.
For the most part, Doctrine 2 already takes care of proper
transaction demarcation for you: All the write operations
(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()``
is invoked which wraps all of these changes in a single
transaction.
However, Doctrine 2 also allows (and encourages) you to take over
and control transaction demarcation yourself.
These are two ways to deal with transactions when using the
Doctrine ORM and are now described in more detail.
Approach 1: Implicitly
~~~~~~~~~~~~~~~~~~~~~~
The first approach is to use the implicit transaction handling
provided by the Doctrine ORM EntityManager. Given the following
code snippet, without any explicit transaction demarcation:
.. code-block:: php
<?php
// $em instanceof EntityManager
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
Since we do not do any custom transaction demarcation in the above
code, ``EntityManager#flush()`` will begin and commit/rollback a
transaction. This behavior is made possible by the aggregation of
the DML operations by the Doctrine ORM and is sufficient if all the
data manipulation that is part of a unit of work happens through
the domain model and thus the ORM.
Approach 2: Explicitly
~~~~~~~~~~~~~~~~~~~~~~
The explicit alternative is to use the ``Doctrine\DBAL\Connection``
API directly to control the transaction boundaries. The code then
looks like this:
.. code-block:: php
<?php
// $em instanceof EntityManager
$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollback();
$em->close();
throw $e;
}
Explicit transaction demarcation is required when you want to
include custom DBAL operations in a unit of work or when you want
to make use of some methods of the ``EntityManager`` API that
require an active transaction. Such methods will throw a
``TransactionRequiredException`` to inform you of that
requirement.
A more convenient alternative for explicit transaction demarcation
is the use of provided control abstractions in the form of
``Connection#transactional($func)`` and
``EntityManager#transactional($func)``. When used, these control
abstractions ensure that you never forget to rollback the
transaction or close the ``EntityManager``, apart from the obvious
code reduction. An example that is functionally equivalent to the
previously shown code looks as follows:
.. code-block:: php
<?php
// $em instanceof EntityManager
$em->transactional(function($em) {
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
});
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and also closes the ``EntityManager`` properly when an
exception occurs (in addition to rolling back the transaction).
Exception Handling
~~~~~~~~~~~~~~~~~~
When using implicit transaction demarcation and an exception occurs
during ``EntityManager#flush()``, the transaction is automatically
rolled back and the ``EntityManager`` closed.
When using explicit transaction demarcation and an exception
occurs, the transaction should be rolled back immediately and the
``EntityManager`` closed by invoking ``EntityManager#close()`` and
subsequently discarded, as demonstrated in the example above. This
can be handled elegantly by the control abstractions shown earlier.
Note that when catching ``Exception`` you should generally re-throw
the exception. If you intend to recover from some exceptions, catch
them explicitly in earlier catch blocks (but do not forget to
rollback the transaction and close the ``EntityManager`` there as
well). All other best practices of exception handling apply
similarly (i.e. either log or re-throw, not both, etc.).
As a result of this procedure, all previously managed or removed
instances of the ``EntityManager`` become detached. The state of
the detached objects will be the state at the point at which the
transaction was rolled back. The state of the objects is in no way
rolled back and thus the objects are now out of synch with the
database. The application can continue to use the detached objects,
knowing that their state is potentially no longer accurate.
If you intend to start another unit of work after an exception has
occurred you should do that with a new ``EntityManager``.
Locking Support
---------------
Doctrine 2 offers support for Pessimistic- and Optimistic-locking
strategies natively. This allows to take very fine-grained control
over what kind of locking is required for your Entities in your
application.
Optimistic Locking
~~~~~~~~~~~~~~~~~~
Database transactions are fine for concurrency control during a
single request. However, a database transaction should not span
across requests, the so-called "user think time". Therefore a
long-running "business transaction" that spans multiple requests
needs to involve several database transactions. Thus, database
transactions alone can no longer control concurrency during such a
long-running business transaction. Concurrency control becomes the
partial responsibility of the application itself.
Doctrine has integrated support for automatic optimistic locking
via a version field. In this approach any entity that should be
protected against concurrent modifications during long-running
business transactions gets a version field that is either a simple
number (mapping type: integer) or a timestamp (mapping type:
datetime). When changes to such an entity are persisted at the end
of a long-running conversation the version of the entity is
compared to the version in the database and if they don't match, an
``OptimisticLockException`` is thrown, indicating that the entity
has been modified by someone else already.
You designate a version field in an entity as follows. In this
example we'll use an integer.
.. code-block:: php
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
// ...
}
Alternatively a datetime type can be used (which maps to an SQL
timestamp or datetime):
.. code-block:: php
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
// ...
}
Version numbers (not timestamps) should however be preferred as
they can not potentially conflict in a highly concurrent
environment, unlike timestamps where this is a possibility,
depending on the resolution of the timestamp on the particular
database platform.
When a version conflict is encountered during
``EntityManager#flush()``, an ``OptimisticLockException`` is thrown
and the active transaction rolled back (or marked for rollback).
This exception can be caught and handled. Potential responses to an
OptimisticLockException are to present the conflict to the user or
to refresh or reload objects in a new transaction and then retrying
the transaction.
With PHP promoting a share-nothing architecture, the time between
showing an update form and actually modifying the entity can in the
worst scenario be as long as your applications session timeout. If
changes happen to the entity in that time frame you want to know
directly when retrieving the entity that you will hit an optimistic
locking exception:
You can always verify the version of an entity during a request
either when calling ``EntityManager#find()``:
.. code-block:: php
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Or you can use ``EntityManager#lock()`` to find out:
.. code-block:: php
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Important Implementation Notes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can easily get the optimistic locking workflow wrong if you
compare the wrong versions. Say you have Alice and Bob accessing a
hypothetical bank account:
- Alice reads the headline of the blog post being "Foo", at
optimistic lock version 1 (GET Request)
- Bob reads the headline of the blog post being "Foo", at
optimistic lock version 1 (GET Request)
- Bob updates the headline to "Bar", upgrading the optimistic lock
version to 2 (POST Request of a Form)
- Alice updates the headline to "Baz", ... (POST Request of a
Form)
Now at the last stage of this scenario the blog post has to be read
again from the database before Alice's headline can be applied. At
this point you will want to check if the blog post is still at
version 1 (which it is not in this scenario).
Using optimistic locking correctly, you *have* to add the version
as an additional hidden field (or into the SESSION for more
safety). Otherwise you cannot verify the version is still the one
being originally read from the database when Alice performed her
GET request for the blog post. If this happens you might see lost
updates you wanted to prevent with Optimistic Locking.
See the example code, The form (GET Request):
.. code-block:: php
<?php
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
And the change headline action (POST Request):
.. code-block:: php
<?php
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~
Doctrine 2 supports Pessimistic Locking at the database level. No
attempt is being made to implement pessimistic locking inside
Doctrine, rather vendor-specific and ANSI-SQL commands are used to
acquire row-level locks. Every Entity can be part of a pessimistic
lock, there is no special metadata required to use this feature.
However for Pessimistic Locking to work you have to disable the
Auto-Commit Mode of your Database and start a transaction around
your pessimistic lock use-case using the "Approach 2: Explicit
Transaction Demarcation" described above. Doctrine 2 will throw an
Exception if you attempt to acquire an pessimistic lock and no
transaction is running.
Doctrine 2 currently supports two pessimistic lock modes:
- Pessimistic Write
(``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the
underlying database rows for concurrent Read and Write Operations.
- Pessimistic Read (``Doctrine\DBAL\LockMode::PESSIMISTIC_READ``),
locks other concurrent requests that attempt to update or lock rows
in write mode.
You can use pessimistic locks in three different scenarios:
1. Using
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
2. Using
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
3. Using
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``

View File

@ -0,0 +1,520 @@
Working with Associations
=========================
Associations between entities are represented just like in regular
object-oriented PHP, with references to other objects or
collections of objects. When it comes to persistence, it is
important to understand three main things:
- The :ref:`concept of owning and inverse sides <association-mapping-owning-inverse>`
in bidirectional associations.
- If an entity is removed from a collection, the association is
removed, not the entity itself. A collection of entities always
only represents the association to the containing entities, not the
entity itself.
- Collection-valued :ref:`persistent fields <architecture_persistent_fields>` have to be instances of the
``Doctrine\Common\Collections\Collection`` interface.
Changes to associations in your code are not synchronized to the
database directly, but upon calling ``EntityManager#flush()``.
To describe all the concepts of working with associations we
introduce a specific set of example entities that show all the
different flavors of association management in Doctrine.
Association Example Entities
----------------------------
We will use a simple comment system with Users and Comments as
entities to show examples of association management. See the PHP
docblocks of each association in the following example for
information about its type and if its the owning or inverse side.
.. code-block:: php
<?php
/** @Entity */
class User
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")}
* )
*/
private $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")}
* )
*/
private $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
*/
private $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
}
/** @Entity */
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
*/
private $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="authoredComments")
*/
private $author;
}
This two entities generate the following MySQL Schema (Foreign Key
definitions omitted):
.. code-block:: sql
CREATE TABLE User (
id VARCHAR(255) NOT NULL,
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, comment_id)
) ENGINE = InnoDB;
Establishing Associations
-------------------------
Establishing an association between two entities is
straight-forward. Here are some examples for the unidirectional
relations of the ``User``:
.. code-block:: php
<?php
class User
{
// ...
public function getReadComments() {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
}
The interaction code would then look like in the following snippet
(``$em`` here is an instance of the EntityManager):
.. code-block:: php
<?php
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
In the case of bi-directional associations you have to update the
fields on both sides:
.. code-block:: php
<?php
class User
{
// ..
public function getAuthoredComments() {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
return $this->favorites;
}
}
class Comment
{
// ...
public function getUserFavorites() {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
$this->author = $author;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist($newComment);
$em->flush();
Notice how always both sides of the bidirectional association are
updated. The previous unidirectional associations were simpler to
handle.
Removing Associations
---------------------
Removing an association between two entities is similarly
straight-forward. There are two strategies to do so, by key and by
element. Here are some examples:
.. code-block:: php
<?php
// Remove by Elements
$user->getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->removeElement($ithComment);
$comment->setAuthor(null);
You need to call ``$em->flush()`` to make persist these changes in
the database permanently.
Notice how both sides of the bidirectional association are always
updated. Unidirectional associations are consequently simpler to
handle. Also note that if you type-hint your methods, i.e.
``setAddress(Address $address)``, then PHP does only allows null
values if ``null`` is set as default value. Otherwise
setAddress(null) will fail for removing the association. If you
insist on type-hinting a typical way to deal with this is to
provide a special method, like ``removeAddress()``. This can also
provide better encapsulation as it hides the internal meaning of
not having an address.
When working with collections, keep in mind that a Collection is
essentially an ordered map (just like a PHP array). That is why the
``remove`` operation accepts an index/key. ``removeElement`` is a
separate method that has O(n) complexity using ``array_search``,
where n is the size of the map.
.. note::
Since Doctrine always only looks at the owning side of a
bidirectional association for updates, it is not necessary for
write operations that an inverse collection of a bidirectional
one-to-many or many-to-many association is updated. This knowledge
can often be used to improve performance by avoiding the loading of
the inverse collection.
You can also clear the contents of a whole collection using the
``Collections::clear()`` method. You should be aware that using
this method can lead to a straight and optimized database delete or
update call during the flush operation that is not aware of
entities that have been re-added to the collection.
Say you clear a collection of tags by calling
``$post->getTags()->clear();`` and then call
``$post->getTags()->add($tag)``. This will not recognize tag being
already added before and issue two database calls.
Association Management Methods
------------------------------
It is generally a good idea to encapsulate proper association
management inside the entity classes. This makes it easier to use
the class correctly and can encapsulate details about how the
association is maintained.
The following code shows updates to the previous User and Comment
example that encapsulate much of the association management code:
.. code-block:: php
<?php
class User
{
//...
public function markCommentRead(Comment $comment) {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
$this->comments[] = $comment;
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
}
class Comment
{
// ..
public function addUserFavorite(User $user) {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
$this->userFavorites->removeElement($user);
}
}
You will notice that ``addUserFavorite`` and ``removeUserFavorite``
do not call ``addFavorite`` and ``removeFavorite``, thus the
bidirectional association is strictly-speaking still incomplete.
However if you would naively add the ``addFavorite`` in
``addUserFavorite``, you end up with an infinite loop, so more work
is needed. As you can see, proper bidirectional association
management in plain OOP is a non-trivial task and encapsulating all
the details inside the classes can be challenging.
.. note::
If you want to make sure that your collections are perfectly
encapsulated you should not return them from a
``getCollectionName()`` method directly, but call
``$collection->toArray()``. This way a client programmer for the
entity cannot circumvent the logic you implement on your entity for
association management. For example:
.. code-block:: php
<?php
class User {
public function getReadComments() {
return $this->commentsRead->toArray();
}
}
This will however always initialize the collection, with all the
performance penalties given the size. In some scenarios of large
collections it might even be a good idea to completely hide the
read access behind methods on the EntityRepository.
There is no single, best way for association management. It greatly
depends on the requirements of your concrete domain model as well
as your preferences.
Synchronizing Bidirectional Collections
---------------------------------------
In the case of Many-To-Many associations you as the developer are
responsible to keep the collections on the owning and inverse side
up in sync, when you apply changes to them. Doctrine can only
guarantee a consistent state for the hydration, not for your client
code.
Using the User-Comment entities from above, a very simple example
can show the possible caveats you can encounter:
.. code-block:: php
<?php
$user->getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUserFavorites()->contains($user); // FALSE
There are to approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections,
BUT never read from them in requests that changed their state. In
the next Request Doctrine hydrates the consistent collection state
again.
2. Always keep the bidirectional collections in sync through
association management methods. Reads of the Collections directly
after changes are consistent then.
Transitive persistence / Cascade Operations
-------------------------------------------
Persisting, removing, detaching and merging individual entities can
become pretty cumbersome, especially when a larger object graph
with collections is involved. Therefore Doctrine 2 provides a
mechanism for transitive persistence through cascading of these
operations. Each association to another entity or a collection of
entities can be configured to automatically cascade certain
operations. By default, no operations are cascaded.
The following cascade options exist:
- persist : Cascades persist operations to the associated
entities.
- remove : Cascades remove operations to the associated entities.
- merge : Cascades merge operations to the associated entities.
- detach : Cascades detach operations to the associated entities.
- all : Cascades persist, remove, merge and detach operations to
associated entities.
The following example is an extension to the User-Comment example
of this chapter. Suppose in our application a user is created
whenever he writes his first comment. In this case we would use the
following code:
.. code-block:: php
<?php
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment);
$em->flush();
Even if you *persist* a new User that contains our new Comment this
code would fail if you removed the call to
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
cascade the persist operation to all nested entities that are new
as well.
More complicated is the deletion of all a users comments when he is
removed from the system:
.. code-block:: php
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
Without the loop over all the authored comments Doctrine would use
an UPDATE statement only to set the foreign key to NULL and only
the User would be deleted from the database during the
flush()-Operation.
To have Doctrine handle both cases automatically we can change the
``User#commentsAuthored`` property to cascade both the "persist"
and the "remove" operation.
.. code-block:: php
<?php
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
Even though automatic cascading is convenient it should be used
with care. Do not blindly apply cascade=all to all associations as
it will unnecessarily degrade the performance of your application.
For each cascade operation that gets activated Doctrine also
applies that operation to the association, be it single or
collection valued.
Persistence by Reachability: Cascade Persist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are additional semantics that apply to the Cascade Persist
operation. During each flush() operation Doctrine detects if there
are new entities in any collection and three possible cases can
happen:
1. New entities in a collection marked as cascade persist will be
directly persisted by Doctrine.
2. New entities in a collection not marked as cascade persist will
produce an Exception and rollback the flush() operation.
3. Collections without new entities are skipped.
This concept is called Persistence by Reachability: New entities
that are found on already managed entities are automatically
persisted as long as the association is defined as cascade
persist.

View File

@ -0,0 +1,813 @@
Working with Objects
====================
In this chapter we will help you understand the ``EntityManager``
and the ``UnitOfWork``. A Unit of Work is similar to an
object-level transaction. A new Unit of Work is implicitly started
when an EntityManager is initially created or after
``EntityManager#flush()`` has been invoked. A Unit of Work is
committed (and a new one started) by invoking
``EntityManager#flush()``.
A Unit of Work can be manually closed by calling
EntityManager#close(). Any changes to objects within this Unit of
Work that have not yet been persisted are lost.
.. note::
It is very important to understand that only
``EntityManager#flush()`` ever causes write operations against the
database to be executed. Any other methods such as
``EntityManager#persist($entity)`` or
``EntityManager#remove($entity)`` only notify the UnitOfWork to
perform these operations during flush.
Not calling ``EntityManager#flush()`` will lead to all changes
during that request being lost.
Entities and the Identity Map
-----------------------------
Entities are objects with identity. Their identity has a conceptual
meaning inside your domain. In a CMS application each article has a
unique id. You can uniquely identify each article by that id.
Take the following example, where you find an article with the
headline "Hello World" with the ID 1234:
.. code-block:: php
<?php
$article = $entityManager->find('CMS\Article', 1234);
$article->setHeadline('Hello World dude!');
$article2 = $entityManager->find('CMS\Article', 1234);
echo $article2->getHeadline();
In this case the Article is accessed from the entity manager twice,
but modified in between. Doctrine 2 realizes this and will only
ever give you access to one instance of the Article with ID 1234,
no matter how often do you retrieve it from the EntityManager and
even no matter what kind of Query method you are using (find,
Repository Finder or DQL). This is called "Identity Map" pattern,
which means Doctrine keeps a map of each entity and ids that have
been retrieved per PHP request and keeps returning you the same
instances.
In the previous example the echo prints "Hello World dude!" to the
screen. You can even verify that ``$article`` and ``$article2`` are
indeed pointing to the same instance by running the following
code:
.. code-block:: php
<?php
if ($article === $article2) {
echo "Yes we are the same!";
}
Sometimes you want to clear the identity map of an EntityManager to
start over. We use this regularly in our unit-tests to enforce
loading objects from the database again instead of serving them
from the identity map. You can call ``EntityManager#clear()`` to
achieve this result.
Entity Object Graph Traversal
-----------------------------
Although Doctrine allows for a complete separation of your domain
model (Entity classes) there will never be a situation where
objects are "missing" when traversing associations. You can walk
all the associations inside your entity models as deep as you
want.
Take the following example of a single ``Article`` entity fetched
from newly opened EntityManager.
.. code-block:: php
<?php
/** @Entity */
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string") */
private $headline;
/** @ManyToOne(targetEntity="User") */
private $author;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
public function __construct {
$this->comments = new ArrayCollection();
}
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
}
$article = $em->find('Article', 1);
This code only retrieves the ``User`` instance with id 1 executing
a single SELECT statement against the user table in the database.
You can still access the associated properties author and comments
and the associated objects they contain.
This works by utilizing the lazy loading pattern. Instead of
passing you back a real Author instance and a collection of
comments Doctrine will create proxy instances for you. Only if you
access these proxies for the first time they will go through the
EntityManager and load their state from the database.
This lazy-loading process happens behind the scenes, hidden from
your code. See the following code:
.. code-block:: php
<?php
$article = $em->find('Article', 1);
// accessing a method of the user instance triggers the lazy-load
echo "Author: " . $article->getAuthor()->getName() . "\n";
// Lazy Loading Proxies pass instanceof tests:
if ($article->getAuthor() instanceof User) {
// a User Proxy is a generated "UserProxy" class
}
// accessing the comments as an iterator triggers the lazy-load
// retrieving ALL the comments of this article from the database
// using a single SELECT statement
foreach ($article->getComments() AS $comment) {
echo $comment->getText() . "\n\n";
}
// Article::$comments passes instanceof tests for the Collection interface
// But it will NOT pass for the ArrayCollection interface
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
echo "This will always be true!";
}
A slice of the generated proxy classes code looks like the
following piece of code. A real proxy class override ALL public
methods along the lines of the ``getName()`` method shown below:
.. code-block:: php
<?php
class UserProxy extends User implements Proxy
{
private function _load()
{
// lazy loading code
}
public function getName()
{
$this->_load();
return parent::getName();
}
// .. other public methods of User
}
.. warning::
Traversing the object graph for parts that are lazy-loaded will
easily trigger lots of SQL queries and will perform badly if used
to heavily. Make sure to use DQL to fetch-join all the parts of the
object-graph that you need as efficiently as possible.
Persisting entities
-------------------
An entity can be made persistent by passing it to the
``EntityManager#persist($entity)`` method. By applying the persist
operation on some entity, that entity becomes MANAGED, which means
that its persistence is from now on managed by an EntityManager. As
a result the persistent state of such an entity will subsequently
be properly synchronized with the database when
``EntityManager#flush()`` is invoked.
.. note::
Invoking the ``persist`` method on an entity does NOT
cause an immediate SQL INSERT to be issued on the database.
Doctrine applies a strategy called "transactional write-behind",
which means that it will delay most SQL commands until
``EntityManager#flush()`` is invoked which will then issue all
necessary SQL statements to synchronize your objects with the
database in the most efficient way and a single, short transaction,
taking care of maintaining referential integrity.
Example:
.. code-block:: php
<?php
$user = new User;
$user->setName('Mr.Right');
$em->persist($user);
$em->flush();
.. note::
Generated entity identifiers / primary keys are
guaranteed to be available after the next successful flush
operation that involves the entity in question. You can not rely on
a generated identifier to be available directly after invoking
``persist``. The inverse is also true. You can not rely on a
generated identifier being not available after a failed flush
operation.
The semantics of the persist operation, applied on an entity X, are
as follows:
- If X is a new entity, it becomes managed. The entity X will be
entered into the database as a result of the flush operation.
- If X is a preexisting managed entity, it is ignored by the
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
"Transitive Persistence").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
Removing entities
-----------------
An entity can be removed from persistent storage by passing it to
the ``EntityManager#remove($entity)`` method. By applying the
``remove`` operation on some entity, that entity becomes REMOVED,
which means that its persistent state will be deleted once
``EntityManager#flush()`` is invoked.
.. note::
Just like ``persist``, invoking ``remove`` on an entity
does NOT cause an immediate SQL DELETE to be issued on the
database. The entity will be deleted on the next invocation of
``EntityManager#flush()`` that involves that entity. This
means that entities scheduled for removal can still be queried
for and appear in query and collection results. See
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
for more information.
Example:
.. code-block:: php
<?php
$em->remove($user);
$em->flush();
The semantics of the remove operation, applied to an entity X are
as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
"Transitive Persistence").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
- A removed entity X will be removed from the database as a result
of the flush operation.
After an entity has been removed its in-memory state is the same as
before the removal, except for generated identifiers.
Removing an entity will also automatically delete any existing
records in many-to-many join tables that link this entity. The
action taken depends on the value of the ``@joinColumn`` mapping
attribute "onDelete". Either Doctrine issues a dedicated ``DELETE``
statement for records of each join table or it depends on the
foreign key semantics of onDelete="CASCADE".
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2
will fetch this association. If its a Single association it will
pass this entity to
´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()\`.
In both cases the cascade remove semantics are applied recursively.
For large object graphs this removal strategy can be very costly.
2. Using a DQL ``DELETE`` statement allows you to delete multiple
entities of a type with a single command and without hydrating
these entities. This can be very efficient to delete large object
graphs from the database.
3. Using foreign key semantics ``onDelete="CASCADE"`` can force the
database to remove all associated objects internally. This strategy
is a bit tricky to get right but can be very powerful and fast. You
should be aware however that using strategy 1 (``CASCADE=REMOVE``)
completely by-passes any foreign key ``onDelete=CASCADE`` option,
because Doctrine will fetch and remove all associated entities
explicitly nevertheless.
Detaching entities
------------------
An entity is detached from an EntityManager and thus no longer
managed by invoking the ``EntityManager#detach($entity)`` method on
it or by cascading the detach operation to it. Changes made to the
detached entity, if any (including removal of the entity), will not
be synchronized to the database after the entity has been
detached.
Doctrine will not hold on to any references to a detached entity.
Example:
.. code-block:: php
<?php
$em->detach($entity);
The semantics of the detach operation, applied to an entity X are
as follows:
- If X is a managed entity, the detach operation causes it to
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
automatically without invoking the ``detach`` method:
- When ``EntityManager#clear()`` is invoked, all entities that are
currently managed by the EntityManager instance become detached.
- When serializing an entity. The entity retrieved upon subsequent
unserialization will be detached (This is the case for all entities
that are serialized and stored in some cache, i.e. when using the
Query Result Cache).
The ``detach`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``.
Merging entities
----------------
Merging entities refers to the merging of (usually detached)
entities into the context of an EntityManager so that they become
managed again. To merge the state of an entity into an
EntityManager use the ``EntityManager#merge($entity)`` method. The
state of the passed entity will be merged into a managed copy of
this entity and this copy will subsequently be returned.
Example:
.. code-block:: php
<?php
$detachedEntity = unserialize($serializedEntity); // some detached entity
$entity = $em->merge($detachedEntity);
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
.. note::
When you want to serialize/unserialize entities you
have to make all entity properties protected, never private. The
reason for this is, if you serialize a class that was a proxy
instance before, the private variables won't be serialized and a
PHP Notice is thrown.
The semantics of the merge operation, applied to an entity X, are
as follows:
- If X is a detached entity, the state of X is copied onto a
pre-existing managed entity instance X' of the same identity.
- If X is a new entity instance, a new managed copy X' will be
created and the state of X is copied onto this managed instance.
- If X is a removed entity instance, an InvalidArgumentException
will be thrown.
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see "Transitive
Persistence").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
that if X is managed then X is the same object as X'.)
- If X is an entity merged to X', with a reference to another
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
navigation of the same association from X' yields a reference to a
managed object Y' with the same persistent identity as Y.
The ``merge`` operation will throw an ``OptimisticLockException``
if the entity being merged uses optimistic locking through a
version field and the versions of the entity being merged and the
managed copy don't match. This usually means that the entity has
been modified while being detached.
The ``merge`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``. The most common scenario for
the ``merge`` operation is to reattach entities to an EntityManager
that come from some cache (and are therefore detached) and you want
to modify and persist such an entity.
.. warning::
If you need to perform multiple merges of entities that share certain subparts
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
multiple copies of the "same" object in the database, however with different ids.
.. note::
If you load some detached entities from a cache and you do
not need to persist or delete them or otherwise make use of them
without the need for persistence services there is no need to use
``merge``. I.e. you can simply pass detached objects from a cache
directly to the view.
Synchronization with the Database
---------------------------------
The state of persistent entities is synchronized with the database
on flush of an ``EntityManager`` which commits the underlying
``UnitOfWork``. The synchronization involves writing any updates to
persistent entities and their relationships to the database.
Thereby bidirectional relationships are persisted based on the
references held by the owning side of the relationship as explained
in the Association Mapping chapter.
When ``EntityManager#flush()`` is called, Doctrine inspects all
managed, new and removed entities and will perform the following
operations.
.. _workingobjects_database_uow_outofsync:
Effects of Database and UnitOfWork being Out-Of-Sync
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As soon as you begin to change the state ofentities, call persist or remove the
contents of the UnitOfWork and the database will drive out of sync. They can
only be sychronized by calling ``EntityManager#flush()`. This section
describes the effects of database and UnitOfWork being out of sync.
- Entities that are scheduled for removal can still be queried from the database.
They are returned from DQL and Repository queries and are visible in collections.
- Entities that are passed to ``EntityManager#persist`` do not turn up in query
results.
- Entities that have changed will not be overwritten with the state from the database.
This is because the identity map will detect the construction of an already existing
entity and assumes its the most up to date version.
``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually.
Synchronizing New and Managed Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flush operation applies to a managed entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
UPDATE statement, only if at least one persistent field has
changed.
- No SQL updates are executed if the entity did not change.
The flush operation applies to a new entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
INSERT statement.
For all (initialized) relationships of the new or managed entity
the following semantics apply to each associated entity X:
- If X is new and persist operations are configured to cascade on
the relationship, X will be persisted.
- If X is new and no persist operations are configured to cascade
on the relationship, an exception will be thrown as this indicates
a programming error.
- If X is removed and persist operations are configured to cascade
on the relationship, an exception will be thrown as this indicates
a programming error (X would be re-persisted by the cascade).
- If X is detached and persist operations are configured to
cascade on the relationship, an exception will be thrown (This is
semantically the same as passing X to persist()).
Synchronizing Removed Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flush operation applies to a removed entity by deleting its
persistent state from the database. No cascade options are relevant
for removed entities on flush, the cascade remove option is already
executed during ``EntityManager#remove($entity)``.
The size of a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~
The size of a Unit of Work mainly refers to the number of managed
entities at a particular point in time.
The cost of flushing
~~~~~~~~~~~~~~~~~~~~
How costly a flush operation is, mainly depends on two factors:
- The size of the EntityManager's current UnitOfWork.
- The configured change tracking policies
You can get the size of a UnitOfWork as follows:
.. code-block:: php
<?php
$uowSize = $em->getUnitOfWork()->size();
The size represents the number of managed entities in the Unit of
Work. This size affects the performance of flush() operations due
to change tracking (see "Change Tracking Policies") and, of course,
memory consumption, so you may want to check it from time to time
during development.
.. note::
Do not invoke ``flush`` after every change to an entity
or every single invocation of persist/remove/merge/... This is an
anti-pattern and unnecessarily reduces the performance of your
application. Instead, form units of work that operate on your
objects and call ``flush`` when you are done. While serving a
single HTTP request there should be usually no need for invoking
``flush`` more than 0-2 times.
Direct access to a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can get direct access to the Unit of Work by calling
``EntityManager#getUnitOfWork()``. This will return the UnitOfWork
instance the EntityManager is currently using.
.. code-block:: php
<?php
$uow = $em->getUnitOfWork();
.. note::
Directly manipulating a UnitOfWork is not recommended.
When working directly with the UnitOfWork API, respect methods
marked as INTERNAL by not using them and carefully read the API
documentation.
Entity State
~~~~~~~~~~~~
As outlined in the architecture overview an entity can be in one of
four possible states: NEW, MANAGED, REMOVED, DETACHED. If you
explicitly need to find out what the current state of an entity is
in the context of a certain ``EntityManager`` you can ask the
underlying ``UnitOfWork``:
.. code-block:: php
<?php
switch ($em->getUnitOfWork()->getEntityState($entity)) {
case UnitOfWork::MANAGED:
...
case UnitOfWork::REMOVED:
...
case UnitOfWork::DETACHED:
...
case UnitOfWork::NEW:
...
}
An entity is in MANAGED state if it is associated with an
``EntityManager`` and it is not REMOVED.
An entity is in REMOVED state after it has been passed to
``EntityManager#remove()`` until the next flush operation of the
same EntityManager. A REMOVED entity is still associated with an
``EntityManager`` until the next flush operation.
An entity is in DETACHED state if it has persistent state and
identity but is currently not associated with an
``EntityManager``.
An entity is in NEW state if has no persistent state and identity
and is not associated with an ``EntityManager`` (for example those
just created via the "new" operator).
Querying
--------
Doctrine 2 provides the following ways, in increasing level of
power and flexibility, to query for persistent objects. You should
always start with the simplest one that suits your needs.
By Primary Key
~~~~~~~~~~~~~~
The most basic way to query for a persistent object is by its
identifier / primary key using the
``EntityManager#find($entityName, $id)`` method. Here is an
example:
.. code-block:: php
<?php
// $em instanceof EntityManager
$user = $em->find('MyProject\Domain\User', $id);
The return value is either the found entity instance or null if no
instance could be found with the given identifier.
Essentially, ``EntityManager#find()`` is just a shortcut for the
following:
.. code-block:: php
<?php
// $em instanceof EntityManager
$user = $em->getRepository('MyProject\Domain\User')->find($id);
``EntityManager#getRepository($entityName)`` returns a repository
object which provides many ways to retrieve entities of the
specified type. By default, the repository instance is of type
``Doctrine\ORM\EntityRepository``. You can also use custom
repository classes as shown later.
By Simple Conditions
~~~~~~~~~~~~~~~~~~~~
To query for one or more entities based on several conditions that
form a logical conjunction, use the ``findBy`` and ``findOneBy``
methods on a repository as follows:
.. code-block:: php
<?php
// $em instanceof EntityManager
// All users that are 20 years old
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
// All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
An EntityRepository also provides a mechanism for more concise
calls through its use of ``__call``. Thus, the following two
examples are equivalent:
.. code-block:: php
<?php
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
By Eager Loading
~~~~~~~~~~~~~~~~
Whenever you query for an entity that has persistent associations
and these associations are mapped as EAGER, they will automatically
be loaded together with the entity being queried and is thus
immediately available to your application.
By Lazy Loading
~~~~~~~~~~~~~~~
Whenever you have a managed entity instance at hand, you can
traverse and use any associations of that entity that are
configured LAZY as if they were in-memory already. Doctrine will
automatically load the associated objects on demand through the
concept of lazy-loading.
By DQL
~~~~~~
The most powerful and flexible method to query for persistent
objects is the Doctrine Query Language, an object query language.
DQL enables you to query for persistent objects in the language of
objects. DQL understands classes, fields, inheritance and
associations. DQL is syntactically very similar to the familiar SQL
but *it is not SQL*.
A DQL query is represented by an instance of the
``Doctrine\ORM\Query`` class. You create a query using
``EntityManager#createQuery($dql)``. Here is a simple example:
.. code-block:: php
<?php
// $em instanceof EntityManager
// All users with an age between 20 and 30 (inclusive).
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
$users = $q->getResult();
Note that this query contains no knowledge about the relational
schema, only about the object model. DQL supports positional as
well as named parameters, many functions, (fetch) joins,
aggregates, subqueries and much more. Detailed information about
DQL and its syntax as well as the Doctrine class can be found in
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
For programmatically building up queries based on conditions that
are only known at runtime, Doctrine provides the special
``Doctrine\ORM\QueryBuilder`` class. More information on
constructing queries with a QueryBuilder can be found
:doc:`in Query Builder chapter <query-builder>`.
By Native Queries
~~~~~~~~~~~~~~~~~
As an alternative to DQL or as a fallback for special SQL
statements native queries can be used. Native queries are built by
using a hand-crafted SQL query and a ResultSetMapping that
describes how the SQL result set should be transformed by Doctrine.
More information about native queries can be found in
`the dedicated chapter <http://www.doctrine-project.org/documentation/manual/2_0/en/native-sql>`_.
Custom Repositories
~~~~~~~~~~~~~~~~~~~
By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Annotation, XML or YAML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.
.. code-block:: php
<?php
namespace MyDomain\Model;
use Doctrine\ORM\EntityRepository;
/**
* @entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
}
}
You can access your repository now by calling:
.. code-block:: php
<?php
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

View File

@ -0,0 +1,698 @@
XML Mapping
===========
The XML mapping driver enables you to provide the ORM metadata in
form of XML documents.
The XML driver is backed by an XML Schema document that describes
the structure of a mapping document. The most recent version of the
XML Schema document is available online at
`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
In order to point to the latest version of the document of a
particular stable release branch, just append the release number,
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
XML mapping files is to use an IDE/editor that can provide
code-completion based on such an XML Schema document. The following
is an outline of a XML mapping document with the proper xmlns/xsi
setup for the latest code in trunk.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
...
</doctrine-mapping>
The XML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated XML
mapping document.
- The name of the mapping document must consist of the fully
qualified name of the class, where namespace separators are
replaced by dots (.). For example an Entity with the fully
qualified class-name "MyProject" would require a mapping file
"MyProject.Entities.User.dcm.xml" unless the extension is changed.
- All mapping documents should get the extension ".dcm.xml" to
identify it as a Doctrine mapping file. This is more of a
convention and you are not forced to do this. You can change the
file extension easily enough.
-
.. code-block:: php
<?php
$driver->setFileExtension('.xml');
It is recommended to put all XML mapping documents in a single
folder but you can spread the documents over several folders if you
want to. In order to tell the XmlDriver where to look for your
mapping documents, supply an array of paths as the first argument
of the constructor, like this:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
$config->setMetadataDriverImpl($driver);
Example
-------
As a quick start, here is a small example document that makes use
of several common elements:
.. code-block:: xml
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
</indexes>
<unique-constraints>
<unique-constraint columns="name,user_email" name="search_idx" />
</unique-constraints>
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersistToo"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
</id>
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
<one-to-one field="address" target-entity="Address" inversed-by="user">
<cascade><cascade-remove /></cascade>
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
</one-to-one>
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user">
<cascade>
<cascade-persist/>
</cascade>
<order-by>
<order-by-field name="number" direction="ASC" />
</order-by>
</one-to-many>
<many-to-many field="groups" target-entity="Group">
<cascade>
<cascade-all/>
</cascade>
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id" nullable="false" unique="false" />
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id" column-definition="INT NULL" />
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
</doctrine-mapping>
Be aware that class-names specified in the XML files should be
fully qualified.
XML-Element Reference
---------------------
The XML-Element reference explains all the tags and attributes that
the Doctrine Mapping XSD Schema defines. You should read the
Basic-, Association- and Inheritance Mapping chapters to understand
what each of this definitions means in detail.
Defining an Entity
~~~~~~~~~~~~~~~~~~
Each XML Mapping File contains the definition of one entity,
specified as the ``<entity />`` element as a direct child of the
``<doctrine-mapping />`` element:
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\User" table="cms_users" repository-class="MyProject\UserRepository">
<!-- definition here -->
</entity>
</doctrine-mapping>
Required attributes:
- name - The fully qualified class-name of the entity.
Optional attributes:
- table - The Table-Name to be used for this entity. Otherwise the
Unqualified Class-Name is used by default.
- repository-class - The fully qualified class-name of an
alternative ``Doctrine\ORM\EntityRepository`` implementation to be
used with this entity.
- inheritance-type - The type of inheritance, defaults to none. A
more detailed description follows in the
*Defining Inheritance Mappings* section.
Defining Fields
~~~~~~~~~~~~~~~
Each entity class can contain zero to infinite fields that are
managed by Doctrine. You can define them using the ``<field />``
element as a children to the ``<entity />`` element. The field
element is only used for primitive types that are not the ID of the
entity. For the ID mapping you have to use the ``<id />`` element.
.. code-block:: xml
<entity name="MyProject\User">
<field name="name" type="string" length="50" />
<field name="username" type="string" unique="true" />
<field name="age" type="integer" nullable="true" />
<field name="isActive" column="is_active" type="boolean" />
<field name="weight" type="decimal" scale="5" precision="2" />
</entity>
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
Optional attributes:
- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to
"string"
- column - Name of the column in the database, defaults to the
field name.
- length - The length of the given type, for use with strings
only.
- unique - Should this field contain a unique value across the
table? Defaults to false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- version - Should this field be used for optimistic locking? Only
works on fields with type integer or datetime.
- scale - Scale of a decimal type.
- precision - Precision of a decimal type.
- column-definition - Optional alternative SQL representation for
this column. This definition begin after the field-name and has to
specify the complete column definition. Using this feature will
turn this field dirty for Schema-Tool update commands at all
times.
Defining Identity and Generator Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An entity has to have at least one ``<id />`` element. For
composite keys you can specify more than one id-element, however
surrogate keys are recommended for use with Doctrine 2. The Id
field allows to define properties of the identifier and allows a
subset of the ``<field />`` element attributes:
.. code-block:: xml
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id" />
</entity>
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
- type - The ``Doctrine\DBAL\Types\Type`` name, preferably
"string" or "integer".
Optional attributes:
- column - Name of the column in the database, defaults to the
field name.
Using the simplified definition above Doctrine will use no
identifier strategy for this entity. That means you have to
manually set the identifier before calling
``EntityManager#persist($entity)``. This is the so called
``ASSIGNED`` strategy.
If you want to switch the identifier generation strategy you have
to nest a ``<generator />`` element inside the id-element. This of
course only works for surrogate keys. For composite keys you always
have to use the ``ASSIGNED`` strategy.
.. code-block:: xml
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id">
<generator strategy="AUTO" />
</id>
</entity>
The following values are allowed for the ``<generator />`` strategy
attribute:
- AUTO - Automatic detection of the identifier strategy based on
the preferred solution of the database vendor.
- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs
available to Doctrine AFTER the INSERT statement has been executed.
- SEQUENCE - Use of a database sequence to retrieve the
entity-ids. This is possible before the INSERT statement is
executed.
If you are using the SEQUENCE strategy you can define an additional
element to describe the sequence:
.. code-block:: xml
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id">
<generator strategy="SEQUENCE" />
<sequence-generator sequence-name="user_seq" allocation-size="5" initial-value="1" />
</id>
</entity>
Required attributes for ``<sequence-generator />``:
- sequence-name - The name of the sequence
Optional attributes for ``<sequence-generator />``:
- allocation-size - By how much steps should the sequence be
incremented when a value is retrieved. Defaults to 1
- initial-value - What should the initial value of the sequence
be.
**NOTE**
If you want to implement a cross-vendor compatible application you
have to specify and additionally define the <sequence-generator />
element, if Doctrine chooses the sequence strategy for a
platform.
Defining a Mapped Superclass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you want to define a class that multiple entities inherit
from, which itself is not an entity however. The chapter on
*Inheritance Mapping* describes a Mapped Superclass in detail. You
can define it in XML using the ``<mapped-superclass />`` tag.
.. code-block:: xml
<doctrine-mapping>
<mapped-superclass name="MyProject\BaseClass">
<field name="created" type="datetime" />
<field name="updated" type="datetime" />
</mapped-superclass>
</doctrine-mapping>
Required attributes:
- name - Class name of the mapped superclass.
You can nest any number of ``<field />`` and unidirectional
``<many-to-one />`` or ``<one-to-one />`` associations inside a
mapped superclass.
Defining Inheritance Mappings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are currently two inheritance persistence strategies that you
can choose from when defining entities that inherit from each
other. Single Table inheritance saves the fields of the complete
inheritance hierarchy in a single table, joined table inheritance
creates a table for each entity combining the fields using join
conditions.
You can specify the inheritance type in the ``<entity />`` element
and then use the ``<discriminator-column />`` and
``<discriminator-mapping />`` attributes.
.. code-block:: xml
<entity name="MyProject\Animal" inheritance-type="JOINED">
<discriminator-column name="discr" type="string" />
<discriminator-map>
<discriminator-mapping value="cat" class="MyProject\Cat" />
<discriminator-mapping value="dog" class="MyProject\Dog" />
<discriminator-mapping value="mouse" class="MyProject\Mouse" />
</discriminator-map>
</entity>
The allowed values for inheritance-type attribute are ``JOINED`` or
``SINGLE_TABLE``.
.. note::
All inheritance related definitions have to be defined on the root
entity of the hierarchy.
Defining Lifecycle Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can define the lifecycle callback methods on your entities
using the ``<lifecycle-callbacks />`` element:
.. code-block:: xml
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="onPrePersist" />
</lifecycle-callbacks>
</entity>
Defining One-To-One Relations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can define One-To-One Relations/Associations using the
``<one-to-one />`` element. The required and optional attributes
depend on the associations being on the inverse or owning side.
For the inverse side the mapping is as simple as:
.. code-block:: xml
<entity class="MyProject\User">
<one-to-one field="address" target-entity="Address" mapped-by="user" />
</entity>
Required attributes for inverse One-To-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
- mapped-by - Name of the field on the owning side (here Address
entity) that contains the owning side association.
For the owning side this mapping would look like:
.. code-block:: xml
<entity class="MyProject\Address">
<one-to-one field="user" target-entity="User" inversed-by="address" />
</entity>
Required attributes for owning One-to-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes for owning One-to-One:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- orphan-removal - If true, the inverse side entity is always
deleted when the owning side entity is. Defaults to false.
- fetch - Either LAZY or FETCH, defaults to LAZY. This attribute
makes only sense on the owning side, the inverse side *ALWAYS* has
to use the ``FETCH`` strategy.
The definition for the owning side relies on a bunch of mapping
defaults for the join column names. Without the nested
``<join-column />`` element Doctrine assumes to foreign key to be
called ``user_id`` on the Address Entities table. This is because
the ``MyProject\Address`` entity is the owning side of this
association, which means it contains the foreign key.
The completed explicitly defined mapping is:
.. code-block:: xml
<entity class="MyProject\Address">
<one-to-one field="user" target-entity="User" inversed-by="address">
<join-column name="user_id" referenced-column-name="id" />
</one-to-one>
</entity>
Defining Many-To-One Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The many-to-one association is *ALWAYS* the owning side of any
bidirectional association. This simplifies the mapping compared to
the one-to-one case. The minimal mapping for this association looks
like:
.. code-block:: xml
<entity class="MyProject\Article">
<many-to-one field="author" target-entity="User" />
</entity>
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- orphan-removal - If true the entity on the inverse side is
always deleted when the owning side entity is and it is not
connected to any other owning side entity anymore. Defaults to
false.
- fetch - Either LAZY or FETCH, defaults to LAZY.
This definition relies on a bunch of mapping defaults with regards
to the naming of the join-column/foreign key. The explicitly
defined mapping includes a ``<join-column />`` tag nested inside
the many-to-one association tag:
.. code-block:: xml
<entity class="MyProject\Article">
<many-to-one field="author" target-entity="User">
<join-column name="author_id" referenced-column-name="id" />
</many-to-one>
</entity>
The join-column attribute ``name`` specifies the column name of the
foreign key and the ``referenced-column-name`` attribute specifies
the name of the primary key column on the User entity.
Defining One-To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The one-to-many association is *ALWAYS* the inverse side of any
association. There exists no such thing as a uni-directional
one-to-many association, which means this association only ever
exists for bi-directional associations.
.. code-block:: xml
<entity class="MyProject\User">
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user" />
</entity>
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
- mapped-by - Name of the field on the owning side (here
Phonenumber entity) that contains the owning side association.
Optional attributes:
- fetch - Either LAZY or FETCH, defaults to LAZY.
Defining Many-To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
From all the associations the many-to-many has the most complex
definition. When you rely on the mapping defaults you can omit many
definitions and rely on their implicit values.
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group" />
</entity>
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
- mapped-by - Name of the field on the owning side that contains
the owning side association if the defined many-to-many association
is on the inverse side.
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- fetch - Either LAZY or FETCH, defaults to LAZY.
The mapping defaults would lead to a join-table with the name
"User\_Group" being created that contains two columns "user\_id"
and "group\_id". The explicit definition of this mapping would be:
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id"/>
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id"/>
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
Here both the ``<join-columns>`` and ``<inverse-join-columns>``
tags are necessary to tell Doctrine for which side the specified
join-columns apply. These are nested inside a ``<join-table />``
attribute which allows to specify the table name of the
many-to-many join-table.
Cascade Element
~~~~~~~~~~~~~~~
Doctrine allows cascading of several UnitOfWork operations to
related entities. You can specify the cascade operations in the
``<cascade />`` element inside any of the association mapping
tags.
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<cascade>
<cascade-all/>
</cascade>
</many-to-many>
</entity>
Besides ``<cascade-all />`` the following operations can be
specified by their respective tags:
- ``<cascade-persist />``
- ``<cascade-merge />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
Join Column Element
~~~~~~~~~~~~~~~~~~~
In any explicitly defined association mapping you will need the
``<join-column />`` tag. It defines how the foreign key and primary
key names are called that are used for joining two entities.
Required attributes:
- name - The column name of the foreign key.
- referenced-column-name - The column name of the associated
entities primary key
Optional attributes:
- unique - If the join column should contain a UNIQUE constraint.
This makes sense for Many-To-Many join-columns only to simulate a
one-to-many unidirectional using a join-table.
- nullable - should the join column be nullable, defaults to true.
- on-delete - Foreign Key Cascade action to perform when entity is
deleted, defaults to NO ACTION/RESTRICT but can be set to
"CASCADE".
Defining Order of To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can require one-to-many or many-to-many associations to be
retrieved using an additional ``ORDER BY``.
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<order-by>
<order-by-field name="name" direction="ASC" />
</order-by>
</many-to-many>
</entity>
Defining Indexes or Unique Constraints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To define additional indexes or unique constraints on the entities
table you can use the ``<indexes />`` and
``<unique-constraints />`` elements:
.. code-block:: xml
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
</indexes>
<unique-constraints>
<unique-constraint columns="name,user_email" name="search_idx" />
</unique-constraints>
</entity>
You have to specify the column and not the entity-class field names
in the index and unique-constraint definitions.

View File

@ -1,29 +1,52 @@
The YAML mapping driver enables you to provide the ORM metadata in form of YAML documents.
YAML Mapping
============
The YAML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions:
The YAML mapping driver enables you to provide the ORM metadata in
form of YAML documents.
* Each entity/mapped superclass must get its own dedicated YAML mapping document.
* The name of the mapping document must consist of the fully qualified name of the class, where namespace separators are replaced by dots (.).
* All mapping documents should get the extension ".dcm.yml" to identify it as a Doctrine mapping file. This is more of a convention and you are not forced to do this. You can change the file extension easily enough.
The YAML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated YAML
mapping document.
- The name of the mapping document must consist of the fully
qualified name of the class, where namespace separators are
replaced by dots (.).
- All mapping documents should get the extension ".dcm.yml" to
identify it as a Doctrine mapping file. This is more of a
convention and you are not forced to do this. You can change the
file extension easily enough.
-
[php]
.. code-block:: php
<?php
$driver->setFileExtension('.yml');
It is recommended to put all YAML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the YamlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this:
It is recommended to put all YAML mapping documents in a single
folder but you can spread the documents over several folders if you
want to. In order to tell the YamlDriver where to look for your
mapping documents, supply an array of paths as the first argument
of the constructor, like this:
[php]
.. code-block:: php
<?php
// $config instanceof Doctrine\ORM\Configuration
$driver = new YamlDriver(array('/path/to/files'));
$config->setMetadataDriverImpl($driver);
Example
-------
++ Example
As a quick start, here is a small example document that makes use
of several common elements:
As a quick start, here is a small example document that makes use of several common elements:
.. code-block:: yaml
[yml]
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
Doctrine\Tests\ORM\Mapping\User:
type: entity
@ -63,4 +86,7 @@ As a quick start, here is a small example document that makes use of several com
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
Be aware that class-names specified in the YAML files should be fully qualified.
Be aware that class-names specified in the YAML files should be
fully qualified.

File diff suppressed because it is too large Load Diff

2
generate-docs.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
sphinx-build en /var/www/docs

View File

@ -1,26 +0,0 @@
+ Introduction
+ Architecture
+ Configuration
+ Basic Mapping
+ Association Mapping
+ Inheritance Mapping
+ Working with objects
+ Working with associations
+ Transactions and Concurrency
+ Events
+ Batch processing
+ DQL (Doctrine Query Language)
+ Query Builder
+ Native SQL
+ Change Tracking Policies
+ Partial Objects
+ XML Mapping
+ YAML Mapping
+ Annotations Reference
+ PHP Mapping
+ Caching
+ Improving Performance
+ Tools
+ Metadata Drivers
+ Best Practices
+ Limitations and Known Issues

View File

@ -1,612 +0,0 @@
In this chapter a reference of every Doctrine 2 Annotation is given with short explanations on their context and usage.
++ Index
* [@Column](#ann_column)
* [@ChangeTrackingPolicy](#ann_changetrackingpolicy)
* [@DiscriminatorColumn](#ann_discriminatorcolumn)
* [@DiscriminatorMap](#ann_discriminatormap)
* [@Entity](#ann_entity)
* [@GeneratedValue](#ann_generatedvalue)
* [@HasLifecycleCallbacks](#ann_haslifecyclecallbacks)
* [@Index](#ann_indexes)
* [@Id](#ann_id)
* [@InheritanceType](#ann_inheritancetype)
* [@JoinColumn](#ann_joincolumn)
* [@JoinTable](#ann_jointable)
* [@ManyToOne](#ann_manytoone)
* [@ManyToMany](#ann_manytomany)
* [@MappedSuperclass](#ann_mappedsuperclass)
* [@OneToOne](#ann_onetoone)
* [@OneToMany](#ann_onetomany)
* [@OrderBy](#ann_orderby)
* [@PostLoad](#ann_postload)
* [@PostPersist](#ann_postpersist)
* [@PostRemove](#ann_postremove)
* [@PostUpdate](#ann_postupdate)
* [@PrePersist](#ann_prepersist)
* [@PreRemove](#ann_preremove)
* [@PreUpdate](#ann_preupdate)
* [@SequenceGenerator](#ann_sequencegenerator)
* [@Table](#ann_table)
* [@UniqueConstraint](#ann_uniqueconstraint)
* [@Version](#ann_version)
++ Reference
<a name="ann_column"></a>
+++ @Column
Marks an annotated instance variable as "persistent". It has to be inside the instance variables PHP DocBlock comment.
Any value hold inside this variable will be saved to and loaded from the database as part of the lifecycle of the instance variables entity-class.
Required attributes:
* type - Name of the Doctrine Type which is converted between PHP and Database representation.
Optional attributes:
* name - By default the property name is used for the database column name also, however the 'name' attribute allows you to determine the column name.
* length - Used by the "string" type to determine its maximum length in the database. Doctrine does not validate the length of a string values for you.
* precision - The precision for a decimal (exact numeric) column (Applies only for decimal column)
* scale - The scale for a decimal (exact numeric) column (Applies only for decimal column)
* unique - Boolean value to determine if the value of the column should be unique across all rows of the underlying entities table.
* nullable - Determines if NULL values allowed for this column.
* columnDefinition - DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. However you should make careful use of this feature and the consequences. Additionally you should remember that the "type" attribute still handles the conversion between PHP and Database values. If you use this attribute on a column that is used for joins between tables you should also take a look at [@JoinColumn](#ann_joincolumn).
Examples:
[php]
/**
* @Column(type="string", length=32, unique=true, nullable=false)
*/
protected $username;
/**
* @Column(type="string", columnDefinition="CHAR(2) NOT NULL")
*/
protected $country;
/**
* @Column(type="decimal", precision=2, scale=1)
*/
protected $height;
<a name="ann_changetrackingpolicy"></a>
+++ @ChangeTrackingPolicy
The Change Tracking Policy annotation allows to specify how the Doctrine 2 UnitOfWork should detect changes
in properties of entities during flush. By default each entity is checked according to a deferred implicit
strategy, which means upon flush UnitOfWork compares all the properties of an entity to a previously stored
snapshot. This works out of the box, however you might want to tweak the flush performance where using
another change tracking policy is an interesting option.
The [details on all the available change tracking policies](/../configuration#change-tracking-policies)
can be found in the configuration section.
Example:
[php]
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_IMPLICIT")
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
* @ChangeTrackingPolicy("NOTIFY")
*/
class User {}
<a name="ann_discriminatorcolumn"></a>
+++ @DiscrimnatorColumn
This annotation is a required annotation for the topmost/super class of an inheritance hierarchy. It specifies
the details of the column which saves the name of the class, which the entity is actually instantiated as.
Required attributes:
* name - The column name of the discriminator. This name is also used during Array hydration as key to specify the class-name.
Optional attributes:
* type - By default this is string.
* length - By default this is 255.
<a name="ann_discriminatormap"></a>
+++ @DiscriminatorMap
The discriminator map is a required annotation on the top-most/super class in an inheritance hierarchy. It takes
an array as only argument which defines which class should be saved under which name in the database. Keys
are the database value and values are the classes, either as fully- or as unqualified class names depending
if the classes are in the namespace or not.
[php]
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
<a name="ann_entity"></a>
+++ @Entity
Required annotation to mark a PHP class as Entity. Doctrine manages the persistence of all classes marked as entity.
Optional attributes:
* repositoryClass - Specifies the FQCN of a subclass of the Doctrine\ORM\EntityRepository. Use of repositories for entities is encouraged to keep specialized DQL and SQL operations separated from the Model/Domain Layer.
Example:
[php]
/**
* @Entity(repositoryClass="MyProject\UserRepository")
*/
class User
{
//...
}
<a name="ann_generatedvalue"></a>
+++ @GeneratedValue
Specifies which strategy is used for identifier generation for an instance variable which is annotated by [@Id](#ann_id).
This annotation is optional and only has meaning when used in conjunction with @Id.
If this annotation is not specified with @Id the NONE strategy is used as default.
Required attributes:
* strategy - Set the name of the identifier generation strategy. Valid values are AUTO, SEQUENCE, TABLE, IDENTITY and NONE.
Example:
[php]
/**
* @Id
* @Column(type="integer")
* @generatedValue(strategy="IDENTITY")
*/
protected $id = null;
<a name="ann_haslifecyclecallbacks"></a>
+++ @HasLifecycleCallbacks
Annotation which has to be set on the entity-class PHP DocBlock to notify Doctrine that this entity has entity life-cycle
callback annotations set on at least one of its methods. Using @PostLoad, @PrePersist, @PostPersist, @PreRemove, @PostRemove,
@PreUpdate or @PostUpdate without this marker annotation will make Doctrine ignore the callbacks.
Example:
[php]
/**
* @Entity
* @HasLifecycleCallbacks
*/
class User
{
/**
* @PostPersist
*/
public function sendOptinMail() {}
}
<a name="ann_indexes"></a>
+++ @Index
Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the
SchemaTool to generate a database index on the specified table columns. It only has meaning in the SchemaTool
schema generation context.
Required attributes:
* name - Name of the Index
* columns - Array of columns.
Example:
[php]
/**
* @Entity
* @Table(name="ecommerce_products",indexes={@index(name="search_idx", columns={"name", "email"})})
*/
class ECommerceProduct
{
}
<a name="ann_id"></a>
+++ @Id
The annotated instance variable will be marked as entity identifier, the primary key in the database.
This annotation is a marker only and has no required or optional attributes. For entities that have multiple
identifier columns each column has to be marked with @Id.
Example:
[php]
/**
* @Id
* @Column(type="integer")
*/
protected $id = null;
<a name="ann_inheritancetype"></a>
+++ @InheritanceType
In an inheritance hierarchy you have to use this annotation on the topmost/super class to define which
strategy should be used for inheritance. Currently Single Table and Class Table Inheritance are supported.
This annotation has always been used in conjunction with the [@DiscriminatorMap](#ann_discriminatormap) and
[@DiscriminatorColumn](#ann_discriminatorcolumn) annotations.
Examples:
[php]
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
<a name="ann_joincolumn"></a>
+++ @JoinColumn
This annotation is used in the context of relations in [@ManyToOne](#ann_manytoone), [@OneToOne](#ann_onetoone) fields
and in the Context of [@JoinTable](#ann_jointable) nested inside a @ManyToMany. This annotation is not required.
If its not specified the attributes *name* and *referencedColumnName* are inferred from the table and primary key names.
Required attributes:
* name - Column name that holds the foreign key identifier for this relation. In the context of @JoinTable it specifies the column name in the join table.
* referencedColumnName - Name of the primary key identifier that is used for joining of this relation.
Optional attributes:
* unique - Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false.
* nullable - Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true.
* onDelete - Cascade Action (Database-level)
* onUpdate - Cascade Action (Database-level)
* columnDefinition - DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on @JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a "columnDefinition" attribute on [@Column](#ann_column) also sets the related @JoinColumn's columnDefinition. This is necessary to make foreign keys work.
Example:
[php]
/**
* @OneToOne(targetEntity="Customer")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
<a name="ann_joincolumns"></a>
+++ @JoinColumns
An array of @JoinColumn annotations for a [@ManyToOne](#ann_manytoone) or [@OneToOne](#ann_onetoone) relation
with an entity that has multiple identifiers.
<a name="ann_jointable"></a>
+++ @JoinTable
Using [@OneToMany](#ann_onetomany) or [@ManyToMany](#ann_manytomany) on the owning side of the relation requires to specify
the @JoinTable annotation which describes the details of the database join table. If you do not specify @JoinTable on
these relations reasonable mapping defaults apply using the affected table and the column names.
Required attributes:
* name - Database name of the join-table
* joinColumns - An array of @JoinColumn annotations describing the join-relation between the owning entities table and the join table.
* inverseJoinColumns - An array of @JoinColumn annotations describing the join-relation between the inverse entities table and the join table.
Optional attributes:
* schema - Database schema name of this table.
Example:
[php]
/**
* @ManyToMany(targetEntity="Phonenumber")
* @JoinTable(name="users_phonenumbers",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
*/
public $phonenumbers;
<a name="ann_manytoone"></a>
+++ @ManyToOne
Defines that the annotated instance variable holds a reference that describes a many-to-one relationship between two entities.
Required attributes:
* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash!
Optional attributes:
* cascade - Cascade Option
* fetch - One of LAZY or EAGER
* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship.
Example:
[php]
/**
* @ManyToOne(targetEntity="Cart", cascade={"ALL"}, fetch="EAGER")
*/
private $cart;
<a name="ann_manytomany"></a>
+++ @ManyToMany
Defines an instance variable holds a many-to-many relationship between two entities. [@JoinTable](#ann_jointable)
is an additional, optional annotation that has reasonable default configuration values using the table
and names of the two related entities.
Required attributes:
* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash!
Optional attributes:
* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship.
* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship.
* cascade - Cascade Option
* fetch - One of LAZY or EAGER
> **NOTE**
> For ManyToMany bidirectional relationships either side may be the owning side (the side
> that defines the @JoinTable and/or does not make use of the mappedBy attribute, thus
> using a default join table).
Example:
[php]
/**
* Owning Side
*
* @ManyToMany(targetEntity="Group", inversedBy="features")
* @JoinTable(name="user_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
private $groups;
/**
* Inverse Side
*
* @ManyToMany(targetEntity="User", mappedBy="groups")
*/
private $features;
<a name="ann_mappedsuperclass"></a>
+++ @MappedSuperclass
An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information
for its subclasses, but which is not itself an entity. This annotation is specified on the Class docblock
and has no additional attributes.
The @MappedSuperclass annotation cannot be used in conjunction with @Entity. See the Inheritance Mapping
section for [more details on the restrictions of mapped superclasses](/../inheritance-mapping#mapped-superclasses).
<a name="ann_onetoone"></a>
+++ @OneToOne
The @OneToOne annotation works almost exactly as the [@ManyToOne](#ann_manytoone) with one additional option
that can be specified. The configuration defaults for [@JoinColumn](#ann_joincolumn) using the target entity table and primary key column names
apply here too.
Required attributes:
* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash!
Optional attributes:
* cascade - Cascade Option
* fetch - One of LAZY or EAGER
* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any
owning instance, should be removed by Doctrine. Defaults to false.
* inversedBy - The inversedBy attribute designates the field in the entity that is the inverse side of the relationship.
Example:
[php]
/**
* @OneToOne(targetEntity="Customer")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
<a name="ann_onetomany"></a>
+++ @OneToMany
Required attributes:
* targetEntity - FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. *IMPORTANT:* No leading backslash!
Optional attributes:
* cascade - Cascade Option
* orphanRemoval - Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any
owning instance, should be removed by Doctrine. Defaults to false.
* mappedBy - This option specifies the property name on the targetEntity that is the owning side of this relation. Its a required attribute for the inverse side of a relationship.
Example:
[php]
/**
* @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true)
*/
public $phonenumbers;
<a name="ann_orderby"></a>
+++ @OrderBy
Optional annotation that can be specified with a [@ManyToMany](#ann_manytomany) or [@OneToMany](#ann_onetomany)
annotation to specify by which criteria the collection should be retrieved from the database by using an ORDER BY
clause.
This annotation requires a single non-attributed value with an DQL snippet:
Example:
[php]
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
*/
private $groups;
The DQL Snippet in OrderBy is only allowed to consist of unqualified,
unquoted field names and of an optional ASC/DESC positional statement.
Multiple Fields are separated by a comma (,). The referenced field
names have to exist on the `targetEntity` class of the `@ManyToMany` or
`@OneToMany` annotation.
<a name="ann_postload"></a>
+++ @PostLoad
Marks a method on the entity to be called as a @PostLoad event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock.
<a name="ann_postpersist"></a>
+++ @PostPersist
Marks a method on the entity to be called as a @PostPersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock.
<a name="ann_postremove"></a>
+++ @PostRemove
Marks a method on the entity to be called as a @PostRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock.
<a name="ann_postupdate"></a>
+++ @PostUpdate
Marks a method on the entity to be called as a @PostUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock.
<a name="ann_prepersist"></a>
+++ @PrePersist
Marks a method on the entity to be called as a @PrePersist event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock.
<a name="ann_preremove"></a>
+++ @PreRemove
Marks a method on the entity to be called as a @PreRemove event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock.
<a name="ann_preupdate"></a>
+++ @PreUpdate
Marks a method on the entity to be called as a @PreUpdate event. Only works with @HasLifecycleCallbacks in the entity class PHP DocBlock.
<a name="ann_sequencegenerator"></a>
+++ @SequenceGenerator
For the use with @generatedValue(strategy="SEQUENCE") this annotation allows to specify details about the sequence,
such as the increment size and initial values of the sequence.
Required attributes:
* sequenceName - Name of the sequence
Optional attributes:
* allocationSize - Increment the sequence by the allocation size when its fetched. A value larger than 1 allows to optimize for scenarios where you create more than one new entity per request. Defaults to 10
* initialValue - Where does the sequence start, defaults to 1.
Example:
[php]
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @Column(type="integer")
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
<a name="ann_table"></a>
+++ @Table
Annotation describes the table an entity is persisted in. It is placed on the entity-class PHP DocBlock and is optional.
If it is not specified the table name will default to the entities unqualified classname.
Required attributes:
* name - Name of the table
Optional attributes:
* schema - Database schema name of this table.
* indexes - Array of @Index annotations
* uniqueConstraints - Array of @UniqueConstraint annotations.
Example:
[php]
/**
* @Entity
* @Table(name="user",
* uniqueConstraints={@UniqueConstraint(name="user_unique",columns={"username"})},
* indexes={@Index(name="user_idx", columns={"email"})}
* )
*/
class User { }
<a name="ann_uniqueconstraint"></a>
+++ @UniqueConstraint
Annotation is used inside the [@Table](#ann_table) annotation on the entity-class level. It allows to hint the
SchemaTool to generate a database unique constraint on the specified table columns. It only has meaning in the SchemaTool
schema generation context.
Required attributes:
* name - Name of the Index
* columns - Array of columns.
Example:
[php]
/**
* @Entity
* @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"})})
*/
class ECommerceProduct
{
}
<a name="ann_version"></a>
+++ @Version
Marker annotation that defines a specified column as version attribute used in an optimistic locking scenario.
It only works on [@Column](#ann_column) annotations that have the type integer or datetime. Combining @Version
with [@Id](#ann_id) is not supported.
Example:
[php]
/**
* @column(type="integer")
* @version
*/
protected $version;

View File

@ -1,82 +0,0 @@
This chapter gives an overview of the overall architecture, terminology and constraints of
Doctrine 2. It is recommended to read this chapter carefully.
++ Entities
An entity is a lightweight, persistent domain object. An entity can be any regular
PHP class observing the following restrictions:
* An entity class must not be final or contain final methods.
* All persistent properties/field of any entity class should always be private or protected, otherwise lazy-loading might not work as expected.
* An entity class must not implement `__clone` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone).
* An entity class must not implement `__wakeup` or [do so safely](http://www.doctrine-project.org/documentation/cookbook/2_0/en/implementing-wakeup-or-clone).
Also consider implementing [Serializable](http://de3.php.net/manual/en/class.serializable.php]) instead.
* Any two entity classes in a class hierarchy that inherit directly or indirectly from one another must not have a mapped property with the same name.
That is, if B inherits from A then B must not have a mapped field with the same name as an already mapped field that is inherited from A.
Entities support inheritance, polymorphic associations, and polymorphic queries.
Both abstract and concrete classes can be entities. Entities may extend non-entity
classes as well as entity classes, and non-entity classes may extend entity classes.
> **TIP**
> The constructor of an entity is only ever invoked when *you* construct a new instance
> with the *new* keyword. Doctrine never calls entity constructors, thus you are free to use
> them as you wish and even have it require arguments of any type.
+++ Entity states
An entity instance can be characterized as being NEW, MANAGED, DETACHED or REMOVED.
* A NEW entity instance has no persistent identity, and is not yet associated with an EntityManager and a UnitOfWork (i.e. those just created with the "new" operator).
* A MANAGED entity instance is an instance with a persistent identity that is associated with an EntityManager and whose persistence is thus managed.
* A DETACHED entity instance is an instance with a persistent identity that is not (or no longer) associated with an EntityManager and a UnitOfWork.
* A REMOVED entity instance is an instance with a persistent identity, associated with an EntityManager, that will be removed from the database upon transaction commit.
+++ Persistent fields
The persistent state of an entity is represented by instance variables. An
instance variable must be directly accessed only from within the methods of the
entity by the entity instance itself. Instance variables must not be accessed by
clients of the entity. The state of the entity is available to clients only through
the entitys methods, i.e. accessor methods (getter/setter methods) or other
business methods.
Collection-valued persistent fields and properties must be defined in terms of
the `Doctrine\Common\Collections\Collection` interface. The collection
implementation type may be used by the application to initialize fields or
properties before the entity is made persistent. Once the entity becomes
managed (or detached), subsequent access must be through the interface type.
+++ Serializing entities
Serializing entities can be problematic and is not really recommended, at least not as long as an
entity instance still holds references to proxy objects or is still managed by an EntityManager.
If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects
you may run into problems with private properties because of technical limitations.
Proxy objects implement `__sleep` and it is not possible for `__sleep` to return names of
private properties in parent classes. On the other hand it is not a solution for proxy objects
to implement `Serializable` because Serializable does not work well with any potential cyclic
object references (at least we did not find a way yet, if you did, please contact us).
++ The EntityManager
The `EntityManager` class is a central access point to the ORM functionality
provided by Doctrine 2. The `EntityManager` API is used to manage the persistence
of your objects and to query for persistent objects.
+++ Transactional write-behind
An `EntityManager` and the underlying `UnitOfWork` employ a strategy called
"transactional write-behind" that delays the execution of SQL statements in
order to execute them in the most efficient way and to execute them at the end
of a transaction so that all write locks are quickly released. You should see
Doctrine as a tool to synchronize your in-memory objects with the database in
well defined units of work. Work with your objects and modify them as usual and
when you're done call `EntityManager#flush()` to make your changes persistent.
+++ The Unit of Work
Internally an `EntityManager` uses a `UnitOfWork`, which is a typical
implementation of the [Unit of Work pattern](http://martinfowler.com/eaaCatalog/unitOfWork.html), to keep track of all the things that need to be done
the next time `flush` is invoked. You usually do not directly interact with
a `UnitOfWork` but with the `EntityManager` instead.

View File

@ -1,321 +0,0 @@
This chapter explains the basic mapping of objects and properties. Mapping of associations will be covered in the next chapter "Association Mapping".
++ Mapping Drivers
Doctrine provides several different ways for specifying object-relational mapping metadata:
* Docblock Annotations
* XML
* YAML
This manual usually uses docblock annotations in all the examples that are spread throughout all chapters. There are dedicated chapters for XML and YAML mapping, respectively.
> **NOTE**
> If you're wondering which mapping driver gives the best performance, the answer is:
> None. Once the metadata of a class has been read from the source (annotations, xml or
> yaml) it is stored in an instance of the `Doctrine\ORM\Mapping\ClassMetadata` class
> and these instances are stored in the metadata cache. Therefore at the end of the day
> all drivers perform equally well. If you're not using a metadata cache (not
> recommended!) then the XML driver might have a slight edge in performance due to the
> powerful native XML support in PHP.
++ Introduction to Docblock Annotations
You've probably used docblock annotations in some form already, most likely to provide documentation metadata for a tool like `PHPDocumentor` (@author, @link, ...). Docblock annotations are a tool to embed metadata inside the documentation section which can then be processed by some tool. Doctrine 2 generalizes the concept of docblock annotations so that they can be used for any kind of metadata and so that it is easy to define new docblock annotations. In order to allow more involved annotation values and to reduce the chances of clashes with other docblock annotations, the Doctrine 2 docblock annotations feature an alternative syntax that is heavily inspired by the Annotation syntax introduced in Java 5.
The implementation of these enhanced docblock annotations is located in the `Doctrine\Common\Annotations` namespace and therefore part of the Common package. Doctrine 2 docblock annotations support namespaces and nested annotations among other things. The Doctrine 2 ORM defines its own set of docblock annotations for supplying object-relational mapping metadata.
> **NOTE**
> If you're not comfortable with the concept of docblock annotations, don't worry, as
> mentioned earlier Doctrine 2 provides XML and YAML alternatives and you could easily
> implement your own favourite mechanism for defining ORM metadata.
++ Persistent classes
In order to mark a class for object-relational persistence it needs to be designated as an entity. This can be done through the `@Entity` marker annotation.
[php]
/** @Entity */
class MyPersistentClass
{
//...
}
By default, the entity will be persisted to a table with the same name as the class name. In order to change that, you can use the `@Table` annotation as follows:
[php]
/**
* @Entity
* @Table(name="my_persistent_class")
*/
class MyPersistentClass
{
//...
}
Now instances of MyPersistentClass will be persisted into a table named `my_persistent_class`.
++ Doctrine Mapping Types
A Doctrine Mapping Type defines the mapping between a PHP type and an SQL type. All Doctrine Mapping Types that ship with Doctrine are fully portable between different RDBMS. You can even write your own custom mapping types that might or might not be portable, which is explained later in this chapter.
For example, the Doctrine Mapping Type `string` defines the mapping from a PHP string to an SQL VARCHAR (or VARCHAR2 etc. depending on the RDBMS brand). Here is a quick overview of the built-in mapping 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 a SQL CLOB to a PHP object using `serialize()` and `unserialize()`
* `array`: Type that maps a SQL CLOB to a PHP object using `serialize()` and `unserialize()`
* `float`: Type that maps a SQL Float (Double Precision) to a PHP double. *IMPORTANT*: Works only with locale settings that use decimal points as separator.
> **NOTE**
> Doctrine Mapping Types are NOT SQL types and NOT PHP types! They are mapping types
> between 2 types.
> **CAUTION**
> Mapping types are *case-sensitive*. For example, using a DateTime column will NOT match the datetime type
> that ships with Doctrine 2!
++ Property Mapping
After a class has been marked as an entity it can specify mappings for its instance fields. Here we will only look at simple fields that hold scalar values like strings, numbers, etc. Associations to other objects are covered in the chapter "Association Mapping".
To mark a property for relational persistence the `@Column` docblock annotation is used. This annotation usually requires at least 1 attribute to be set, the `type`. The `type` attribute specifies the Doctrine Mapping Type to use for the field. If the type is not specified, 'string' is used as the default mapping type since it is the most flexible.
Example:
[php]
/** @Entity */
class MyPersistentClass
{
/** @Column(type="integer") */
private $id;
/** @Column(length=50) */
private $name; // type defaults to string
//...
}
In that example we mapped the field `id` to the column `id` using the mapping type `integer` and the field `name` is mapped to the column `name` with the default mapping type `string`. As you can see, by default the column names are assumed to be the same as the field names. To specify a different name for the column, you can use the `name` attribute of the Column annotation as follows:
[php]
/** @Column(name="db_name") */
private $name;
The Column annotation has some more attributes. Here is a complete list:
* `type`: (optional, defaults to 'string') The mapping type to use for the column.
* `name`: (optional, defaults to field name) The name of the column in the database.
* `length`: (optional, default 255) The length of the column in the database. (Applies only if a string-valued column is used).
* `unique`: (optional, default FALSE) Whether the column is a unique key.
* `nullable`: (optional, default FALSE) Whether the database column is nullable.
* `precision`: (optional, default 0) The precision for a decimal (exact numeric) column. (Applies only if a decimal column is used.)
* `scale`: (optional, default 0) The scale for a decimal (exact numeric) column. (Applies only if a decimal column is used.)
++ Custom Mapping Types
Doctrine allows you to create new mapping types. This can come in handy when you're missing a specific mapping type
or when you want to replace the existing implementation of a mapping type.
In order to create a new mapping type you need to subclass `Doctrine\DBAL\Types\Type` and implement/override
the methods as you wish. Here is an example skeleton of such a custom type class:
[php]
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
class MyType extends Type
{
const MYTYPE = 'mytype'; // modify to match your type name
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
// return the SQL used to create your column type. To create a portable column type, use the $platform.
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
Restrictions to keep in mind:
* If the value of the field is *NULL* the method `convertToDatabaseValue()` is not called.
* The `UnitOfWork` never passes values to the database convert method that did not change in the request.
When you have implemented the type you still need to let Doctrine know about it. This can be achieved
through the `Doctrine\DBAL\Configuration#setCustomTypes(array $types)` method.
> **NOTE**
> `Doctrine\ORM\Configuration` is a subclass of `Doctrine\DBAL\Configuration`, so the
> methods are available on your ORM Configuration instance as well.
Here is an example:
[php]
// in bootstrapping code
// ...
use Doctrine\DBAL\Types\Type;
// ...
// Register my type
Type::addType('mytype', 'My\Project\Types\MyType');
To have Schema-Tool convert the underlying database type of your new "mytype" directly into an instance of `MyType`
you have to additionally register this mapping with your database platform:
[php]
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
Now using Schema-Tool, whenever it detects a column having the `db_mytype` it will convert it into a `mytype`
Doctrine Type instance for Schema representation. Keep in mind that you can easily produce clashes this way,
each database type can only map to exactly one Doctrine mapping type.
As can be seen above, when registering the custom types in the configuration you specify a unique name
for the mapping type and map that to the corresponding fully qualified class name. Now you can use your new type in your mapping like this:
[php]
class MyPersistentClass
{
/** @Column(type="mytype") */
private $field;
}
++ Identifiers / Primary Keys
Every entity class needs an identifier/primary key. You designate the field that serves as the identifier with the `@Id` marker annotation. Here is an example:
[php]
class MyPersistentClass
{
/** @Id @Column(type="integer") */
private $id;
//...
}
Without doing anything else, the identifier is assumed to be manually assigned. That means your code would need to properly set the identifier property before passing a new entity to `EntityManager#persist($entity)`.
A common alternative strategy is to use a generated value as the identifier. To do this, you use the `@GeneratedValue` annotation like this:
[php]
class MyPersistentClass
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
}
This tells Doctrine to automatically generate a value for the identifier. How this value is generated is specified by the `strategy` attribute, which is optional and defaults to 'AUTO'. A value of `AUTO` tells Doctrine to use the generation strategy that is preferred by the currently used database platform. See below for details.
+++ Identifier Generation Strategies
The previous example showed how to use the default identifier generation strategy without knowing the underlying database with the AUTO-detection strategy.
It is also possible to specify the identifier generation strategy more explicitly, which allows to make use of some additional features.
Here is the list of possible generation strategies:
* `AUTO` (default): Tells Doctrine to pick the strategy that is preferred by the used database platform.
The preferred strategies are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle and PostgreSQL.
This strategy provides full portability.
* `SEQUENCE`: Tells Doctrine to use a database sequence for ID generation. This strategy does currently not provide full portability. Sequences are supported by Oracle and PostgreSql.
* `IDENTITY`: Tells Doctrine to use special identity columns in the database that generate a value on insertion of a row. This strategy does currently not provide full portability and
is supported by the following platforms: MySQL/SQLite (AUTO_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
* `TABLE`: Tells Doctrine to use a separate table for ID generation. This strategy provides full portability. ***This strategy is not yet implemented!***
* `NONE`: Tells Doctrine that the identifiers are assigned (and thus generated) by your code.
The assignment must take place before a new entity is passed to `EntityManager#persist`.
NONE is the same as leaving off the @GeneratedValue entirely.
++++ Sequence Generator
The Sequence Generator can currently be used in conjunction with Oracle or Postgres and allows some additional configuration options besides
specifying the sequence's name:
[php]
class User {
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(name="tablename_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
}
The initial value specifies at which value the sequence should start.
The allocationSize is a powerful feature to optimize INSERT performance of Doctrine. The allocationSize specifies
by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than 1 (one)
Doctrine can generate identifier values for the allocationSizes amount of entities. In the above example with
`allocationSize=100` Doctrine 2 would only need to access the sequence once to generate the identifiers for
100 new entities.
*The default allocationSize for a @SequenceGenerator is currently 10.*
> **CAUTION**
> The allocationSize is detected by SchemaTool and transformed into an "INCREMENT BY <allocationSize>" clause
> in the CREATE SEQUENCE statement. For a database schema created manually (and not SchemaTool) you have to
> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value,
> otherwise you may get duplicate keys.
> **TIP**
> It is possible to use strategy="AUTO" and at the same time specifying a @SequenceGenerator.
> In such a case, your custom sequence settings are used in the case where the preferred
> strategy of the underlying platform is SEQUENCE, such as for Oracle and PostgreSQL.
+++ Composite Keys
Doctrine 2 allows to use composite primary keys. There are however some restrictions opposed to using a single identifier.
The use of the `@GeneratedValue` annotation is only supported for simple (not composite) primary keys, which means
you can only use composite keys if you generate the primary key values yourself before calling `EntityManager#persist()`
on the entity.
To designate a composite primary key / identifier, simply put the @Id marker annotation on all fields that make up the primary key.
++ Quoting Reserved Words
It may sometimes be necessary to quote a column or table name because it conflicts with a reserved word of the particular RDBMS in use. This is often referred to as "Identifier Quoting". To let Doctrine know that you would like a table or column name to be quoted in all SQL statements, enclose the table or column name in backticks. Here is an example:
[php]
/** @Column(name="`number`", type="integer") */
private $number;
Doctrine will then quote this column name in all SQL statements according to the used database platform.
> **CAUTION**
> Identifier Quoting is not supported for join column names or discriminator column names.
> **CAUTION**
> Identifier Quoting is a feature that is mainly intended to support legacy database
> schemas. The use of reserved words and identifier quoting is generally discouraged.
> Identifier quoting should not be used to enable the use non-standard-characters such
> as a dash in a hypothetical column `test-name`. Also Schema-Tool will likely have
> troubles when quoting is used for case-sensitivity reasons (in Oracle for example).

View File

@ -1,122 +0,0 @@
This chapter shows you how to accomplish bulk inserts, updates and deletes with Doctrine in an efficient way. The main problem with bulk operations is usually not to run out of memory and this is especially what the strategies presented here provide help with.
> **CAUTION**
> An ORM tool is not primarily well-suited for mass inserts, updates or deletions.
> Every RDBMS has its own, most effective way of dealing with such operations and if
> the options outlined below are not sufficient for your purposes we recommend you
> use the tools for your particular RDBMS for these bulk operations.
++ Bulk Inserts
Bulk inserts in Doctrine are best performed in batches, taking advantage of the transactional write-behind behavior of an `EntityManager`. The following code shows an example for inserting 10000 objects with a batch size of 20. You may need to experiment with the batch size to find the size that works best for you. Larger batch sizes mean more prepared statement reuse internally but also mean more work during `flush`.
[php]
$batchSize = 20;
for ($i = 1; $i <= 10000; ++$i) {
$user = new CmsUser;
$user->setStatus('user');
$user->setUsername('user' . $i);
$user->setName('Mr.Smith-' . $i);
$em->persist($user);
if (($i % $batchSize) == 0) {
$em->flush();
$em->clear(); // Detaches all objects from Doctrine!
}
}
++ Bulk Updates
There are 2 possibilities for bulk updates with Doctrine.
+++ DQL UPDATE
The by far most efficient way for bulk updates is to use a DQL UPDATE query. Example:
[php]
$q = $em->createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9');
$numUpdated = $q->execute();
+++ Iterating results
An alternative solution for bulk updates is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this, combining the iteration with the batching strategy that was already used for bulk inserts:
[php]
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach($iterableResult AS $row) {
$user = $row[0];
$user->increaseCredit();
$user->calculateNewBonuses();
if (($i % $batchSize) == 0) {
$em->flush(); // Executes all updates.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
> **NOTE**
> Iterating results is not possible with queries that fetch-join a collection-valued
> association. The nature of such SQL result sets is not suitable for incremental
> hydration.
++ Bulk Deletes
There are two possibilities for bulk deletes with Doctrine. You can either issue
a single DQL DELETE query or you can iterate over results removing them one at a time.
+++ DQL DELETE
The by far most efficient way for bulk deletes is to use a DQL DELETE query.
Example:
[php]
$q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000');
$numDeleted = $q->execute();
+++ Iterating results
An alternative solution for bulk deletes is to use the `Query#iterate()` facility to iterate over the query results step by step instead of loading the whole result into memory at once. The following example shows how to do this:
[php]
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
while (($row = $iterableResult->next()) !== false) {
$em->remove($row[0]);
if (($i % $batchSize) == 0) {
$em->flush(); // Executes all deletions.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
> **NOTE**
> Iterating results is not possible with queries that fetch-join a collection-valued
> association. The nature of such SQL result sets is not suitable for incremental
> hydration.
++ Iterating Large Results for Data-Processing
You can use the `iterate()` method just to iterate over a large result and no UPDATE or DELETE
intention. The `IterableResult` instance returned from `$query->iterate()` implements the
Iterator interface so you can process a large result without memory problems using the
following approach:
[php]
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult AS $row) {
// do stuff with the data in the row, $row[0] is always the object
// detach from Doctrine, so that it can be Garbage-Collected immediately
$this->_em->detach($row[0]);
}
> **NOTE**
> Iterating results is not possible with queries that fetch-join a collection-valued
> association. The nature of such SQL result sets is not suitable for incremental
> hydration.

View File

@ -1,77 +0,0 @@
> **NOTE**
> The best practices mentioned here that affect database design generally refer to best
> practices when working with Doctrine and do not necessarily reflect best practices for
> database design in general.
++ Don't use public properties on entities
It is very important that you don't map public properties on entities, but only protected or private ones.
The reason for this is simple, whenever you access a public property of a proxy object that hasn't been initialized
yet the return value will be null. Doctrine cannot hook into this process and magically make the entity lazy load.
This can create situations where it is very hard to debug the current failure. We therefore urge you to map only
private and protected properties on entities and use getter methods or magic __get() to access them.
++ Constrain relationships as much as possible
It is important to constrain relationships as much as possible. This means:
* Impose a traversal direction (avoid bidirectional associations if possible)
* Eliminate nonessential associations
This has several benefits:
* Reduced coupling in your domain model
* Simpler code in your domain model (no need to maintain bidirectionality properly)
* Less work for Doctrine
++ Avoid composite keys
Even though Doctrine fully supports composite keys it is best not to use them if possible. Composite keys require additional work by Doctrine and thus have a higher probability of errors.
++ Use events judiciously
The event system of Doctrine is great and fast. Even though making heavy use of events, especially lifecycle events, can have a negative impact on the performance of your application. Thus you should use events judiciously.
++ Use cascades judiciously
Automatic cascades of the persist/remove/merge/etc. operations are very handy but should be used wisely. Do NOT simply add all cascades to all associations. Think about which cascades actually do make sense for you for a particular association, given the scenarios it is most likely used in.
++ Don't use special characters
Avoid using any non-ASCII characters in class, field, table or column names. Doctrine itself is not unicode-safe in many places and will not be until PHP itself is fully unicode-aware (PHP6).
++ Don't use identifier quoting
Identifier quoting is a workaround for using reserved words that often causes problems in edge cases. Do not use identifier quoting and avoid using reserved words as table or column names.
++ Initialize collections in the constructor
It is recommended best practice to initialize any business collections in entities in the constructor. Example:
[php]
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
private $addresses;
private $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;
}
}
++ Don't map foreign keys to fields in an entity
Foreign keys have no meaning whatsoever in an object model. Foreign keys are how a relational database establishes relationships. Your object model establishes relationships through object references. Thus mapping foreign keys to object fields heavily leaks details of the relational model into the object model, something you really should not do.
++ Use explicit transaction demarcation
While Doctrine will automatically wrap all DML operations in a transaction on flush(), it is considered best practice to explicitly set the transaction boundaries yourself.
Otherwise every single query is wrapped in a small transaction (Yes, SELECT queries, too) since you can not talk to your database outside of a transaction.
While such short transactions for read-only (SELECT) queries generally don't have any noticeable performance impact, it is still preferable to use fewer, well-defined transactions
that are established through explicit transaction boundaries.

View File

@ -1,350 +0,0 @@
Doctrine provides cache drivers in the `Common` package for some of the most
popular caching implementations such as APC, Memcache and Xcache. We also provide
an `ArrayCache` driver which stores the data in a PHP array. Obviously, the cache
does not live between requests but this is useful for testing in a development
environment.
++ Cache Drivers
The cache drivers follow a simple interface that is defined in `Doctrine\Common\Cache\Cache`.
All the cache drivers extend a base class `Doctrine\Common\Cache\AbstractCache`
which implements the before mentioned interface.
The interface defines the following methods for you to publicly use.
* fetch($id) - Fetches an entry from the cache.
* contains($id) - Test if an entry exists in the cache.
* save($id, $data, $lifeTime = false) - Puts data into the cache.
* delete($id) - Deletes a cache entry.
Each driver extends the `AbstractCache` class which defines a few abstract
protected methods that each of the drivers must implement.
* _doFetch($id)
* _doContains($id)
* _doSave($id, $data, $lifeTime = false)
* _doDelete($id)
The public methods `fetch()`, `contains()`, etc. utilize the above protected methods
that are implemented by the drivers. The code is organized this way so that the
protected methods in the drivers do the raw interaction with the cache implementation
and the `AbstractCache` can build custom functionality on top of these methods.
+++ APC
In order to use the APC cache driver you must have it compiled and enabled in
your php.ini. You can read about APC [here](http://us2.php.net/apc) on the PHP
website. It will give you a little background information about what it is and
how you can use it as well as how to install it.
Below is a simple example of how you could use the APC cache driver by itself.
[php]
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$cacheDriver->save('cache_id', 'my_data');
+++ Memcache
In order to use the Memcache cache driver you must have it compiled and enabled in
your php.ini. You can read about Memcache [here](http://us2.php.net/memcache) on
the PHP website. It will give you a little background information about what it is
and how you can use it as well as how to install it.
Below is a simple example of how you could use the Memcache cache driver by itself.
[php]
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache()
$cacheDriver->save('cache_id', 'my_data');
+++ Xcache
In order to use the Xcache cache driver you must have it compiled and enabled in
your php.ini. You can read about Xcache [here](http://xcache.lighttpd.net/). It
will give you a little background information about what it is and how you can
use it as well as how to install it.
Below is a simple example of how you could use the Xcache cache driver by itself.
[php]
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
$cacheDriver->save('cache_id', 'my_data');
++ Using Cache Drivers
In this section we'll describe how you can fully utilize the API of the cache
drivers to save cache, check if some cache exists, fetch the cached data and
delete the cached data. We'll use the `ArrayCache` implementation as our
example here.
[php]
$cacheDriver = new \Doctrine\Common\Cache\ArrayCache();
+++ Saving
To save some data to the cache driver it is as simple as using the `save()` method.
[php]
$cacheDriver->save('cache_id', 'my_data');
The `save()` method accepts three arguments which are described below.
* `$id` - The cache id
* `$data` - The cache entry/data.
* `$lifeTime` - The lifetime. If != false, sets a specific lifetime for this cache entry (null => infinite lifeTime).
You can save any type of data whether it be a string, array, object, etc.
[php]
$array = array(
'key1' => 'value1',
'key2' => 'value2'
);
$cacheDriver->save('my_array', $array);
+++ Checking
Checking whether some cache exists is very simple, just use the `contains()` method.
It accepts a single argument which is the ID of the cache entry.
[php]
if ($cacheDriver->contains('cache_id')) {
echo 'cache exists';
} else {
echo 'cache does not exist';
}
+++ Fetching
Now if you want to retrieve some cache entry you can use the `fetch()` method. It
also accepts a single argument just like `contains()` which is the ID of the cache entry.
[php]
$array = $cacheDriver->fetch('my_array');
+++ Deleting
As you might guess, deleting is just as easy as saving, checking and fetching.
We have a few ways to delete cache entries. You can delete by an individual ID,
regular expression, prefix, suffix or you can delete all entries.
++++ By Cache ID
[php]
$cacheDriver->delete('my_array');
You can also pass wild cards to the `delete()` method and it will return an array
of IDs that were matched and deleted.
[php]
$deleted = $cacheDriver->delete('users_*');
++++ By Regular Expression
If you need a little more control than wild cards you can use a PHP regular
expression to delete cache entries.
[php]
$deleted = $cacheDriver->deleteByRegex('/users_.*/');
++++ By Prefix
Because regular expressions are kind of slow, if simply deleting by a prefix or
suffix is sufficient, it is recommended that you do that instead of using a regular
expression because it will be much faster if you have many cache entries.
[php]
$deleted = $cacheDriver->deleteByPrefix('users_');
++++ By Suffix
Just like we did above with the prefix you can do the same with a suffix.
[php]
$deleted = $cacheDriver->deleteBySuffix('_my_account');
++++ All
If you simply want to delete all cache entries you can do so with the `deleteAll()`
method.
[php]
$deleted = $cacheDriver->deleteAll();
+++ Counting
If you want to count how many entries are stored in the cache driver instance
you can use the `count()` method.
[php]
echo $cacheDriver->count();
> **NOTE**
> In order to use `deleteByRegex()`, `deleteByPrefix()`, `deleteBySuffix()`,
> `deleteAll()`, `count()` or `getIds()` you must enable an option for the cache
> driver to manage your cache IDs internally. This is necessary because APC,
> Memcache, etc. don't have any advanced functionality for fetching and deleting.
> We add some functionality on top of the cache drivers to maintain an index of
> all the IDs stored in the cache driver so that we can allow more granular deleting
> operations.
>
> [php]
> $cacheDriver->setManageCacheIds(true);
+++ Namespaces
If you heavily use caching in your application and utilize it in multiple parts
of your application, or use it in different applications on the same server you
may have issues with cache naming collisions. This can be worked around by using
namespaces. You can set the namespace a cache driver should use by using the
`setNamespace()` method.
[php]
$cacheDriver->setNamespace('my_namespace_');
++ Integrating with the ORM
The Doctrine ORM package is tightly integrated with the cache drivers to allow
you to improve performance of various aspects of Doctrine by just simply making
some additional configurations and method calls.
+++ Query Cache
It is highly recommended that in a production environment you cache the
transformation of a DQL query to its SQL counterpart. It doesn't make sense to
do this parsing multiple times as it doesn't change unless you alter the DQL
query.
This can be done by configuring the query cache implementation to use on your ORM
configuration.
[php]
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
+++ Result Cache
The result cache can be used to cache the results of your queries so that we
don't have to query the database or hydrate the data again after the first time.
You just need to configure the result cache implementation.
[php]
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now when you're executing DQL queries you can configure them to use the result cache.
[php]
$query = $em->createQuery('select u from \Entities\User u');
$query->useResultCache(true);
You can also configure an individual query to use a different result cache driver.
[php]
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
> **NOTE**
> Setting the result cache driver on the query will automatically enable the
> result cache for the query. If you want to disable it pass false to
> `useResultCache()`.
>
> [php]
> $query->useResultCache(false);
If you want to set the time the cache has to live you can use the `setResultCacheLifetime()`
method.
[php]
$query->setResultCacheLifetime(3600);
The ID used to store the result set cache is a hash which is automatically generated
for you if you don't set a custom ID yourself with the `setResultCacheId()` method.
[php]
$query->setResultCacheId('my_custom_id');
You can also set the lifetime and cache ID by passing the values as the second
and third argument to `useResultCache()`.
[php]
$query->useResultCache(true, 3600, 'my_custom_id');
+++ Metadata Cache
Your class metadata can be parsed from a few different sources like YAML, XML,
Annotations, etc. Instead of parsing this information on each request we should
cache it using one of the cache drivers.
Just like the query and result cache we need to configure it first.
[php]
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now the metadata information will only be parsed once and stored in the cache
driver.
++ Clearing the Cache
We've already shown you previously how you can use the API of the cache drivers
to manually delete cache entries. For your convenience we offer a command line task
for you to help you with clearing the query, result and metadata cache.
From the Doctrine command line you can run the following command.
$ ./doctrine clear-cache
Running this task with no arguments will clear all the cache for all the configured
drivers. If you want to be more specific about what you clear you can use the
following options.
To clear the query cache use the `--query` option.
$ ./doctrine clear-cache --query
To clear the metadata cache use the `--metadata` option.
$ ./doctrine clear-cache --metadata
To clear the result cache use the `--result` option.
$ ./doctrine clear-cache --result
When you use the `--result` option you can use some other options to be more
specific about what queries result sets you want to clear.
Just like the API of the cache drivers you can clear based on an ID, regular
expression, prefix or suffix.
$ ./doctrine clear-cache --result --id=cache_id
Or if you want to clear based on a regular expressions.
$ ./doctrine clear-cache --result --regex=users_.*
Or with a prefix.
$ ./doctrine clear-cache --result --prefix=users_
And finally with a suffix.
$ ./doctrine clear-cache --result --suffix=_my_account
> **NOTE**
> Using the `--id`, `--regex`, etc. options with the `--query` and `--metadata`
> are not allowed as it is not necessary to be specific about what you clear.
> You only ever need to completely clear the cache to remove stale entries.
++ Cache Slams
Something to be careful of when utilizing the cache drivers is cache slams. If
you have a heavily trafficked website with some code that checks for the existence
of a cache record and if it does not exist it generates the information and saves
it to the cache. Now if 100 requests were issued all at the same time and each one
sees the cache does not exist and they all try and insert the same cache entry
it could lock up APC, Xcache, etc. and cause problems. Ways exist to work around
this, like pre-populating your cache and not letting your users requests populate
the cache.
You can read more about cache slams [here](http://t3.dotgnu.info/blog/php/user-cache-timebomb).

View File

@ -1,128 +0,0 @@
++ Change Tracking Policies
Change tracking is the process of determining what has changed in managed
entities since the last time they were synchronized with the database.
Doctrine provides 3 different change tracking policies, each having its
particular advantages and disadvantages. The change tracking policy can
be defined on a per-class basis (or more precisely, per-hierarchy).
+++ Deferred Implicit
The deferred implicit policy is the default change tracking policy and the most
convenient one. With this policy, Doctrine detects the changes by a
property-by-property comparison at commit time and also detects changes
to entities or new entities that are referenced by other managed entities
("persistence by reachability"). Although the most convenient policy, it can
have negative effects on performance if you are dealing with large units of
work (see "Understanding the Unit of Work"). Since Doctrine can't know what
has changed, it needs to check all managed entities for changes every time you
invoke EntityManager#flush(), making this operation rather costly.
+++ Deferred Explicit
The deferred explicit policy is similar to the deferred implicit policy in that
it detects changes through a property-by-property comparison at commit time. The
difference is that only entities are considered that have been explicitly marked
for change detection through a call to EntityManager#persist(entity) or through
a save cascade. All other entities are skipped. This policy therefore gives
improved performance for larger units of work while sacrificing the behavior
of "automatic dirty checking".
Therefore, flush() operations are potentially cheaper with this policy. The
negative aspect this has is that if you have a rather large application and
you pass your objects through several layers for processing purposes and
business tasks you may need to track yourself which entities have changed
on the way so you can pass them to EntityManager#persist().
This policy can be configured as follows:
[php]
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class User
{
// ...
}
+++ Notify
This policy is based on the assumption that the entities notify interested
listeners of changes to their properties. For that purpose, a class that
wants to use this policy needs to implement the `NotifyPropertyChanged`
interface from the Doctrine\Common namespace. As a guideline, such an
implementation can look as follows:
[php]
use Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->_listeners[] = $listener;
}
}
Then, in each property setter of this class or derived classes, you need to
notify all the `PropertyChangedListener` instances. As an example we
add a convenience method on `MyEntity` that shows this behaviour:
[php]
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data)
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
You have to invoke `_onPropertyChanged` inside every method that changes the
persistent state of `MyEntity`.
The check whether the new value is different from the old one is not mandatory
but recommended. That way you also have full control over when you consider a
property changed.
The negative point of this policy is obvious: You need implement an interface
and write some plumbing code. But also note that we tried hard to keep this
notification functionality abstract. Strictly speaking, it has nothing to do
with the persistence layer and the Doctrine ORM or DBAL. You may find that
property notification events come in handy in many other scenarios as well.
As mentioned earlier, the `Doctrine\Common` namespace is not that evil and
consists solely of very small classes and interfaces that have almost no
external dependencies (none to the DBAL and none to the ORM) and that you
can easily take with you should you want to swap out the persistence layer.
This change tracking policy does not introduce a dependency on the Doctrine
DBAL/ORM or the persistence layer.
The positive point and main advantage of this policy is its effectiveness. It
has the best performance characteristics of the 3 policies with larger units of
work and a flush() operation is very cheap when nothing has changed.

View File

@ -1,307 +0,0 @@
++ Bootstrapping
Bootstrapping Doctrine is a relatively simple procedure that roughly exists of
just 2 steps:
* Making sure Doctrine class files can be loaded on demand.
* Obtaining an EntityManager instance.
+++ Class loading
Lets start with the class loading setup. We need to set up some class loaders
(often called "autoloader") so that Doctrine class files are loaded on demand.
The Doctrine\Common namespace contains a very fast and minimalistic class loader
that can be used for Doctrine and any other libraries where the coding standards
ensure that a class's location in the directory tree is reflected by its name
and namespace and where there is a common root namespace.
> **NOTE**
> You are not forced to use the Doctrine class loader to load Doctrine
> classes. Doctrine does not care how the classes are loaded, if you want to use a
> different class loader or your own to load Doctrine classes, just do that.
> Along the same lines, the class loader in the Doctrine\Common namespace is not
> meant to be only used for Doctrine classes, too. It is a generic class loader that can
> be used for any classes that follow some basic naming standards as described above.
The following example shows the setup of a `ClassLoader` for the different types
of Doctrine Installations:
> **NOTE**
> This assumes you've created some kind of script to test the following code in.
> Something like a `test.php` file.
++++ PEAR or Tarball Download
[php]
// test.php
require '/path/to/libraries/Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine', '/path/to/libraries');
$classLoader->register(); // register on SPL autoload stack
++++ Git
The Git bootstrap assumes that you have fetched the related packages through `git submodule update --init`
[php]
// test.php
$lib = '/path/to/doctrine2-orm/lib/';
require $lib . 'vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine\Common', $lib . 'vendor/doctrine-common/lib');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine\DBAL', $lib . 'vendor/doctrine-dbal/lib');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine\ORM', $lib);
$classLoader->register();
++++ Additional Symfony Components
If you don't use Doctrine2 in combination with Symfony2 you have to register an additional namespace to be able to use
the Doctrine-CLI Tool or the YAML Mapping driver:
[php]
// PEAR or Tarball setup
$classloader = new \Doctrine\Common\ClassLoader('Symfony', '/path/to/libraries/Doctrine');
$classloader->register();
// Git Setup
$classloader = new \Doctrine\Common\ClassLoader('Symfony', $lib . 'vendor/');
$classloader->register();
For best class loading performance it is recommended that you keep your include_path short, ideally it should only contain the path to the PEAR libraries, and any other class libraries should be registered with their full base path.
+++ Obtaining an EntityManager
Once you have prepared the class loading, you acquire an *EntityManager* instance.
The EntityManager class is the primary access point to ORM functionality provided by Doctrine.
A simple configuration of the EntityManager requires a `Doctrine\ORM\Configuration`
instance as well as some database connection parameters:
[php]
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration;
// ...
if ($applicationMode == "development") {
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$cache = new \Doctrine\Common\Cache\ApcCache;
}
$config = new Configuration;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
if ($applicationMode == "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
}
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
$em = EntityManager::create($connectionOptions, $config);
> **CAUTION**
> Do not use Doctrine without a metadata and query cache! Doctrine is highly
> optimized for working with caches. The main parts in Doctrine that are optimized
> for caching are the metadata mapping information with the metadata cache and the
> DQL to SQL conversions with the query cache. These 2 caches require only an absolute
> minimum of memory yet they heavily improve the runtime performance of Doctrine.
> The recommended cache driver to use with Doctrine is [APC](http://www.php.net/apc).
> APC provides you with an opcode-cache (which is highly recommended anyway) and
> a very fast in-memory cache storage that you can use for the metadata and query
> caches as seen in the previous code snippet.
++ Configuration Options
The following sections describe all the configuration options available on a `Doctrine\ORM\Configuration` instance.
+++ Proxy Directory (***REQUIRED***)
[php]
$config->setProxyDir($dir);
$config->getProxyDir();
Gets or sets the directory where Doctrine generates any proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down.
+++ Proxy Namespace (***REQUIRED***)
[php]
$config->setProxyNamespace($namespace);
$config->getProxyNamespace();
Gets or sets the namespace to use for generated proxy classes. For a detailed explanation on proxy classes and how they are used in Doctrine, refer to the "Proxy Objects" section further down.
+++ Metadata Driver (***REQUIRED***)
[php]
$config->setMetadataDriverImpl($driver);
$config->getMetadataDriverImpl();
Gets or sets the metadata driver implementation that is used by Doctrine to acquire the object-relational metadata for your classes.
There are currently 4 available implementations:
* `Doctrine\ORM\Mapping\Driver\AnnotationDriver`
* `Doctrine\ORM\Mapping\Driver\XmlDriver`
* `Doctrine\ORM\Mapping\Driver\YamlDriver`
* `Doctrine\ORM\Mapping\Driver\DriverChain`
Throughout the most part of this manual the AnnotationDriver is used in the examples. For information on the usage of the XmlDriver or YamlDriver please refer to the dedicated chapters `XML Mapping` and `YAML Mapping`.
The annotation driver can be configured with a factory method on the `Doctrine\ORM\Configuration`:
[php]
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the annotation driver, because otherwise
mass-operations on all entities through the console could not work correctly. All of metadata
drivers accept either a single directory as a string or an array of directories. With this feature a
single driver can support multiple directories of Entities.
+++ Metadata Cache (***RECOMMENDED***)
[php]
$config->setMetadataCacheImpl($cache);
$config->getMetadataCacheImpl();
Gets or sets the cache implementation to use for caching metadata information, that is, all the information you supply via annotations, xml or yaml, so that they do not need to be parsed and loaded from scratch on every single request which is a waste of resources.
The cache implementation must implement the `Doctrine\Common\Cache\Cache` interface.
Usage of a metadata cache is highly recommended.
The recommended implementations for production are:
* `Doctrine\Common\Cache\ApcCache`
* `Doctrine\Common\Cache\MemcacheCache`
* `Doctrine\Common\Cache\XcacheCache`
For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis.
+++ Query Cache (***RECOMMENDED***)
[php]
$config->setQueryCacheImpl($cache);
$config->getQueryCacheImpl();
Gets or sets the cache implementation to use for caching DQL queries, that is, the result of a DQL parsing process that includes the final SQL as well as meta information about how to process the SQL result set of a query. Note that the query cache does not affect query results. You do not get stale data. This is a pure optimization cache without any negative side-effects (except some minimal memory usage in your cache).
Usage of a query cache is highly recommended.
The recommended implementations for production are:
* `Doctrine\Common\Cache\ApcCache`
* `Doctrine\Common\Cache\MemcacheCache`
* `Doctrine\Common\Cache\XcacheCache`
For development you should use the `Doctrine\Common\Cache\ArrayCache` which only caches data on a per-request basis.
+++ SQL Logger (***Optional***)
[php]
$config->setSQLLogger($logger);
$config->getSQLLogger();
Gets or sets the logger to use for logging all SQL statements executed by Doctrine. The logger class must implement the `Doctrine\DBAL\Logging\SqlLogger` interface. A simple default implementation that logs to the standard output using `echo` and `var_dump` can be found at `Doctrine\DBAL\Logging\EchoSqlLogger`.
+++ Auto-generating Proxy Classes (***OPTIONAL***)
[php]
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
Gets or sets whether proxy classes should be generated automatically at runtime by Doctrine. If set to `FALSE`, proxy classes must be generated manually through the doctrine command line task `generate-proxies`. The strongly recommended value for a production environment is `FALSE`.
++ Development vs Production Configuration
You should code your Doctrine2 bootstrapping with two different runtime models in mind. There are some serious
benefits of using APC or Memcache in production. In development however this will frequently give you fatal
errors, when you change your entities and the cache still keeps the outdated metadata. That is why we recommend
the `ArrayCache` for development.
Furthermore you should have the Auto-generating Proxy Classes option to true in development and to false
in production. If this option is set to `TRUE` it can seriously hurt your script performance if several proxy
classes are re-generated during script execution. Filesystem calls of that magnitude can even slower than all
the database queries Doctrine issues. Additionally writing a proxy sets an exclusive file lock which can cause
serious performance bottlenecks in systems with regular concurrent requests.
++ Connection Options
The `$connectionOptions` passed as the first argument to `EntityManager::create()` has to be either an array
or an instance of `Doctrine\DBAL\Connection`. If an array is passed it is directly passed along to the
DBAL Factory `Doctrine\DBAL\DriverManager::getConnection()`. The DBAL configuration is explained
in the [DBAL section](./../../../../../dbal/2.0/docs/reference/configuration/en).
++ Proxy Objects
A proxy object is an object that is put in place or used instead of the "real" object. A proxy object can add behavior to the object being proxied without that object being aware of it. In Doctrine 2, proxy objects are used to realize several features but mainly for transparent lazy-loading.
Proxy objects with their lazy-loading facilities help to keep the subset of objects that are already in memory connected to the rest of the objects. This is an essential property as without it there would always be fragile partial objects at the outer edges of your object graph.
Doctrine 2 implements a variant of the proxy pattern where it generates classes that extend your entity classes and adds lazy-loading capabilities to them. Doctrine can then give you an instance of such a proxy class whenever you request an object of the class being proxied. This happens in two situations:
**Reference Proxies**
The method `EntityManager#getReference($entityName, $identifier)` lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier. You could simply do this:
[php]
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Here, we added an Item to a Cart without loading the Item from the database. If you invoke any method on the Item instance, it would fully initialize its state transparently from the database. Here $item is actually an instance of the proxy class that was generated for the Item class but your code does not need to care. In fact it **should not care**. Proxy objects should be transparent to your code.
**Association proxies**
The second most important situation where Doctrine uses proxy objects is when querying for objects. Whenever you query for an object that has a single-valued association to another object that is configured LAZY, without joining that association in the same query, Doctrine puts proxy objects in place where normally the associated object would be.
Just like other proxies it will transparently initialize itself on first access.
> **NOTE**
> Joining an association in a DQL or native query essentially means eager loading of that
> association in that query. This will override the 'fetch' option specified in
> the mapping for that association, but only for that query.
+++ Generating Proxy classes
Proxy classes can either be generated manually through the Doctrine Console or automatically by Doctrine. The configuration option that controls this behavior is:
[php]
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
The default value is `TRUE` for convenient development. However, this setting is not optimal for performance and therefore not recommended for a production environment.
To eliminate the overhead of proxy class generation during runtime, set this configuration option to `FALSE`. When you do this in a development environment, note that you may get class/file not found errors if certain proxy classes are not available or failing lazy-loads if new methods were added to the entity class that are not yet in the proxy class. In such a case, simply use the Doctrine Console to (re)generate the proxy classes like so:
$ ./doctrine orm:generate-proxies
++ Multiple Metadata Sources
When using different components using Doctrine 2 you may end up with them using two different metadata drivers,
for example XML and YAML. You can use the DriverChain Metadata implementations to aggregate these drivers
based on namespaces:
[php]
$chain = new DriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is delegated to the appropriate driver. The chain
semantics come from the fact that the driver loops through all namespaces and matches the entity class name
against the namespace using a `strpos() === 0` call. This means you need to order the drivers correctly if
sub-namespaces use different metadata driver implementations.

View File

@ -1,467 +0,0 @@
Doctrine 2 features a lightweight event system that is part of the Common package.
++ The Event System
The event system is controlled by the `EventManager`. It is the central point
of Doctrine's event listener system. Listeners are registered on the manager
and events are dispatched through the manager.
[php]
$evm = new EventManager();
Now we can add some event listeners to the `$evm`. Let's create a `EventTest` class
to play around with.
[php]
class EventTest
{
const preFoo = 'preFoo';
const postFoo = 'postFoo';
private $_evm;
public $preFooInvoked = false;
public $postFooInvoked = false;
public function __construct($evm)
{
$evm->addEventListener(array(self::preFoo, self::postFoo), $this);
}
public function preFoo(EventArgs $e)
{
$this->preFooInvoked = true;
}
public function postFoo(EventArgs $e)
{
$this->postFooInvoked = true;
}
}
// Create a new instance
$test = new EventTest($evm);
Events can be dispatched by using the `dispatchEvent()` method.
[php]
$evm->dispatchEvent(EventTest::preFoo);
$evm->dispatchEvent(EventTest::postFoo);
You can easily remove a listener with the `removeEventListener()` method.
[php]
$evm->removeEventListener(array(self::preFoo, self::postFoo), $this);
The Doctrine 2 event system also has a simple concept of event subscribers. We
can define a simple `TestEventSubscriber` class which implements the
`\Doctrine\Common\EventSubscriber` interface and implements a `getSubscribedEvents()`
method which returns an array of events it should be subscribed to.
[php]
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
{
public $preFooInvoked = false;
public function preFoo()
{
$this->preFooInvoked = true;
}
public function getSubscribedEvents()
{
return array(TestEvent::preFoo);
}
}
$eventSubscriber = new TestEventSubscriber();
$evm->addEventSubscriber($eventSubscriber);
Now when you dispatch an event any event subscribers will be notified for that event.
[php]
$evm->dispatchEvent(TestEvent::preFoo);
Now you can test the `$eventSubscriber` instance to see if the `preFoo()` method was invoked.
[php]
if ($eventSubscriber->preFooInvoked) {
echo 'pre foo invoked!';
}
+++ Naming convention
Events being used with the Doctrine 2 EventManager are best named with camelcase and the value of the corresponding constant should be the name of the constant itself, even with spelling. This has several reasons:
* It is easy to read.
* Simplicity.
* Each method within an EventSubscriber is named after the corresponding constant. If constant name and constant value differ, you MUST use the new value and thus, your code might be subject to codechanges when the value changes. This contradicts the intention of a constant.
An example for a correct notation can be found in the example `EventTest` above.
++ Lifecycle Events
The EntityManager and UnitOfWork trigger a bunch of events during the life-time of their registered entities.
* preRemove - The preRemove event occurs for a given entity before the respective EntityManager remove operation for that entity is executed.
* postRemove - The postRemove event occurs for an entity after the entity has been deleted. It will be invoked after the database delete operations.
* prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed.
* postPersist - The postPersist event occurs for an entity after the entity has been made persistent. It will be invoked after the database insert operations. Generated primary key values are available in the postPersist event.
* preUpdate - The preUpdate event occurs before the database update operations to entity data.
* postUpdate - The postUpdate event occurs after the database update operations to entity data.
* postLoad - The postLoad event occurs for an entity after the entity has been loaded into the current EntityManager from the database or after the refresh operation has been applied to it.
* loadClassMetadata - The loadClassMetadata event occurs after the mapping metadata for a class has been loaded from a mapping source (annotations/xml/yaml).
* onFlush - The onFlush event occurs after the change-sets of all managed entities are computed. This event is not a lifecycle callback.
> **CAUTION**
> Note that the postLoad event occurs for an entity before any associations have been
> initialized. Therefore it is not safe to access associations in a postLoad callback
> or event handler.
You can access the Event constants from the `Events` class in the ORM package.
[php]
use Doctrine\ORM\Events;
echo Events::preUpdate;
These can be hooked into by two different types of event listeners:
* Lifecycle Callbacks are methods on the entity classes that are called when the event is triggered. They receive absolutely no arguments and are specifically designed to allow changes inside the entity classes state.
* Lifecycle Event Listeners are classes with specific callback methods that receives some kind of `EventArgs` instance which give access to the entity, EntityManager or other relevant data.
> **NOTE**
> All Lifecycle events that happen during the `flush()` of an EntityManager have very specific constraints on the allowed
> operations that can be executed. Please read the *Implementing Event Listeners* section very carefully to understand
> which operations are allowed in which lifecycle event.
++ Lifecycle Callbacks
A lifecycle event is a regular event with the additional feature of providing
a mechanism to register direct callbacks inside the corresponding entity classes
that are executed when the lifecycle event occurs.
[php]
/** @Entity @HasLifecycleCallbacks */
class User
{
// ...
/**
* @Column(type="string", length=255)
*/
public $value;
/** @Column(name="created_at", type="string", length=255) */
private $createdAt;
/** @PrePersist */
public function doStuffOnPrePersist()
{
$this->createdAt = date('Y-m-d H:m:s');
}
/** @PrePersist */
public function doOtherStuffOnPrePersist()
{
$this->value = 'changed from prePersist callback!';
}
/** @PostPersist */
public function doStuffOnPostPersist()
{
$this->value = 'changed from postPersist callback!';
}
/** @PostLoad */
public function doStuffOnPostLoad()
{
$this->value = 'changed from postLoad callback!';
}
/** @PreUpdate */
public function doStuffOnPreUpdate()
{
$this->value = 'changed from preUpdate callback!';
}
}
Note that when using annotations you have to apply the @HasLifecycleCallbacks marker annotation on the entity class.
If you want to register lifecycle callbacks from YAML or XML you can do it with
the following.
[yml]
User:
type: entity
fields:
# ...
name:
type: string(50)
lifecycleCallbacks:
doStuffOnPrePersist: prePersist
doStuffOnPostPersist: postPersist
XML would look something like this:
[xml]
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
<entity name="User">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
You just need to make sure a public `doStuffOnPrePersist()` and `doStuffOnPostPersist()` method is defined on your `User` model.
[php]
// ...
class User
{
// ...
public function doStuffOnPrePersist()
{
// ...
}
public function doStuffOnPostPersist()
{
// ...
}
}
The `key` of the lifecycleCallbacks is the name of the method and the value is
the event type. The allowed event types are the ones listed in the previous Lifecycle Events section.
++ Listening to Lifecycle Events
Lifecycle event listeners are much more powerful than the simple lifecycle callbacks that are defined on the entity
classes. They allow to implement re-usable behaviors between different entity classes, yet require much more detailed
knowledge about the inner workings of the EntityManager and UnitOfWork. Please read the *Implementing Event Listeners*
section carefully if you are trying to write your own listener.
To register an event listener you have to hook it into the EventManager that is passed to the EntityManager factory:
[php]
$eventManager = new EventManager();
$eventManager->addEventListener(array(Events::preUpdate), MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
You can also retrieve the event manager instance after the EntityManager was created:
[php]
$entityManager->getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener());
$entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());
++ Implementing Event Listeners
This section explains what is and what is not allowed during specific lifecycle events of the UnitOfWork.
Although you get passed the EntityManager in all of these events, you have to follow this restrictions very
carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and
lost updates/persists/removes.
For the described events that are also lifecycle callback events the restrictions
apply as well, with the additional restriction that you do not have access to the EntityManager
or UnitOfWork APIs inside these events.
+++ prePersist
There are two ways for the `prePersist` event to be triggered. One is obviously
when you call `EntityManager#persist()`. The event is also called for all
cascaded associations.
There is another way for `prePersist` to be called, inside the `flush()`
method when changes to associations are computed and this association
is marked as cascade persist. Any new entity found during this operation
is also persisted and `prePersist` called on it. This is called "persistence by reachability".
In both cases you get passed a `LifecycleEventArgs`
instance which has access to the entity and the entity manager.
The following restrictions apply to `prePersist`:
* If you are using a PrePersist Identity Generator such as sequences the ID value
will *NOT* be available within any PrePersist events.
* Doctrine will not recognize changes made to relations in a pre persist event
called by "reachability" through a cascade persist unless you use the internal
`UnitOfWork` API. We do not recommend such operations in the persistence by
reachability context, so do this at your own risk and possibly supported by unit-tests.
+++ preRemove
The `preRemove` event is called on every entity when its passed to
the `EntityManager#remove()` method. It is cascaded for all
associations that are marked as cascade delete.
There are no restrictions to what methods can be called inside
the `preRemove` event, except when the remove method itself was
called during a flush operation.
+++ onFlush
OnFlush is a very powerful event. It is called inside `EntityManager#flush()`
after the changes to all the managed entities and their associations have
been computed. This means, the `onFlush` event has access to the sets of:
* Entities scheduled for insert
* Entities scheduled for update
* Entities scheduled for removal
* Collections scheduled for update
* Collections scheduled for removal
To make use of the onFlush event you have to be familiar with the internal UnitOfWork API,
which grants you access to the previously mentioned sets. See this example:
[php]
class FlushExampleListener
{
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
}
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
}
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
}
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
}
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
}
}
}
The following restrictions apply to the onFlush event:
* Calling `EntityManager#persist()` does not suffice to trigger a persist on an entity.
You have to execute an additional call to `$unitOfWork->computeChangeSet($classMetadata, $entity)`.
* Changing primitive fields or associations requires you to explicitly trigger
a re-computation of the changeset of the affected entity. This can be done
by either calling `$unitOfWork->computeChangeSet($classMetadata, $entity)`
or `$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)`. The second
method has lower overhead, but only re-computes primitive fields, never associations.
+++ preUpdate
PreUpdate is the most restrictive to use event, since it is called right before
an update statement is called for an entity inside the `EntityManager#flush()`
method.
Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to
correctly handle referential integrity at this point of the flush operation. This
event has a powerful feature however, it is executed with a `PreUpdateEventArgs`
instance, which contains a reference to the computed change-set of this entity.
This means you have access to all the fields that have changed for this entity
with their old and new value. The following methods are available on the `PreUpdateEventArgs`:
* `getEntity()` to get access to the actual entity.
* `getEntityChangeSet()` to get a copy of the changeset array. Changes to this returned array do not affect updating.
* `hasChangedField($fieldName)` to check if the given field name of the current entity changed.
* `getOldValue($fieldName)` and `getNewValue($fieldName)` to access the values of a field.
* `setNewValue($fieldName, $value)` to change the value of a field to be updated.
A simple example for this event looks like:
[php]
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}
You could also use this listener to implement validation of all the fields that have changed.
This is more efficient than using a lifecycle callback when there are expensive validations
to call:
[php]
class ValidCreditCardListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof Account) {
if ($eventArgs->hasChangedField('creditCard')) {
$this->validateCreditCard($eventArgs->getNewValue('creditCard'));
}
}
}
private function validateCreditCard($no)
{
// throw an exception to interrupt flush event. Transaction will be rolled back.
}
}
Restrictions for this event:
* Changes to associations of the passed entities are not recognized by the flush operation anymore.
* Changes to fields of the passed entities are not recognized by the flush operation anymore, use the computed change-set passed to the event to modify primitive field values.
* Any calls to `EntityManager#persist()` or `EntityManager#remove()`, even in combination with the UnitOfWork API are strongly discouraged and don't work as expected outside the flush operation.
+++ postUpdate, postRemove, postPersist
The three post events are called inside `EntityManager#flush()`. Changes in here
are not relevant to the persistence in the database, but you can use this events
to
+++ postLoad
This event is called after an entity is constructed by the EntityManager.
++ Load ClassMetadata Event
When the mapping information for an entity is read, it is populated in to a
`ClassMetadataInfo` instance. You can hook in to this process and manipulate
the instance.
[php]
$test = new EventTest();
$metadataFactory = $em->getMetadataFactory();
$evm = $em->getEventManager();
$evm->addEventListener(Events::loadClassMetadata, $test);
class EventTest
{
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$fieldMapping = array(
'fieldName' => 'about',
'type' => 'string',
'length' => 255
);
$classMetadata->mapField($fieldMapping);
}
}

View File

@ -1,25 +0,0 @@
++ Bytecode Cache
It is highly recommended to make use of a bytecode cache like APC. A bytecode cache removes the need for parsing PHP code on every request and can greatly improve performance.
> **NOTE**
> "If you care about performance and don't use a bytecode cache then you don't really care
> about performance. Please get one and start using it." (Stas Malyshev, Core Contributor
> to PHP and Zend Employee).
++ Metadata and Query caches
As already mentioned earlier in the chapter about configuring Doctrine, it is strongly discouraged to use Doctrine without a Metadata and Query cache (preferably with APC or Memcache as the cache driver). Operating Doctrine without these caches means Doctrine will need to load your mapping information on every single request and has to parse each DQL query on every single request. This is a waste of resources.
++ Alternative Query Result Formats
Make effective use of the available alternative query result formats like nested array graphs or pure scalar results, especially in scenarios where data is loaded for read-only purposes.
++ Apply Best Practices
A lot of the points mentioned in the Best Practices chapter will also positively affect the performance of Doctrine.

View File

@ -1,172 +0,0 @@
++ Mapped Superclasses
An mapped superclass is an abstract or concrete class that provides persistent entity state and mapping information
for its subclasses, but which is not itself an entity. Typically, the purpose of such a mapped superclass is to define
state and mapping information that is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can appear in the middle of an otherwise
mapped inheritance hierarchy (through Single Table Inheritance or Class Table Inheritance).
> **NOTE**
>
> A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped
> superclass must be unidirectional. For further support of inheritance, the single or joined table inheritance
> features have to be used.
Example:
[php]
/** @MappedSuperclass */
class MappedSuperclassBase
{
/** @Column(type="integer") */
private $mapped1;
/** @Column(type="string") */
private $mapped2;
/**
* @OneToOne(targetEntity="MappedSuperclassRelated1")
* @JoinColumn(name="related1_id", referencedColumnName="id")
*/
private $mappedRelated1;
// ... more fields and methods
}
/** @Entity */
class EntitySubClass extends MappedSuperclassBase
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
// ... more fields and methods
}
The DDL for the corresponding database schema would look something like this (this is for SQLite):
[sql]
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
As you can see from this DDL snippet, there is only a single table for the entity subclass. All the mappings from the mapped superclass were inherited to the subclass as if they had been defined on that class directly.
++ Single Table Inheritance
[Single Table Inheritance](http://martinfowler.com/eaaCatalog/singleTableInheritance.html) is an inheritance mapping strategy where all classes of a hierarchy are mapped to a single database table. In order to distinguish which row represents which type in the hierarchy a so-called discriminator column is used.
Example:
[php]
namespace Zoo\Entities;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"animal" = "Animal", "cat" = "Cat", "dog" = "Dog"})
*/
class Animal
{
// ...
}
/**
* @Entity
*/
class Cat extends Animal
{
// ...
}
/**
* @Entity
*/
class Dog extends Animal
{
// ...
}
Things to note:
* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified *ONLY* on the topmost class that is part of the mapped entity hierarchy.
* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "animal" identifies a row as being of type `Animal`, "cat" identifies a row as being of type `Cat` and "dog" is an instance of `Dog`.
* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied.
+++ Design-time considerations
This mapping approach works well when the type hierarchy is fairly simple and stable. Adding a new type to the hierarchy and adding fields to existing supertypes simply involves adding new columns to the table, though in large deployments this may have an adverse impact on the index and column layout inside the database.
+++ Performance impact
This strategy is very efficient for querying across all types in the hierarchy or for specific types. No table joins are required, only a WHERE clause listing the type identifiers. In particular, relationships involving types that employ this mapping strategy are very performant.
There is a general performance consideration with Single Table Inheritance: If you use a STI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly.
+++ SQL Schema considerations
For Single-Table-Inheritance to work in scenarios where you are using either a legacy database schema or a
self-written database schema you have to make sure that all columns that are not in the root entity but in any
of the different sub-entities has to allows null values. Columns that have NOT NULL constraints have to be on the
root entity of the single-table inheritance hierarchy.
++ Class Table Inheritance
[Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html) is an inheritance mapping strategy where each class in a hierarchy is mapped to several tables: its own table and the tables of all parent classes. The table of a child class is linked to the table of a parent class through a foreign key constraint.
Doctrine 2 implements this strategy through the use of a discriminator column in the topmost table of the hierarchy because this is the easiest way to achieve polymorphic queries with Class Table Inheritance.
Example:
[php]
namespace Zoo\Entities;
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"animal" = "Animal", "cat" = "Cat", "dog" = "Dog"})
*/
class Animal
{
// ...
}
Things to note:
* The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap must be specified *ONLY* on the topmost class that is part of the mapped entity hierarchy.
* The @DiscriminatorMap specifies which values of the discriminator column identify a row as being of a certain type. In the case above a value of "animal" identifies a row as being of type `Animal`, "cat" identifies a row as being of type `Cat` and "dog" is an instance of `Dog`.
* The names of the classes in the discriminator map do not need to be fully qualified if the classes are contained in the same namespace as the entity class on which the discriminator map is applied.
> **NOTE**
> When you do not use the SchemaTool to generate the required SQL you should know that deleting a class table inheritance
> makes use of the foreign key property `ON DELETE CASCADE` in all database implementations. A failure to implement this
> yourself will lead to dead rows in the database.
+++ Design-time considerations
Introducing a new type to the hierarchy, at any level, simply involves interjecting a new table into the schema. Subtypes of that type will automatically join with that new type at runtime. Similarly, modifying any entity type in the hierarchy by adding, modifying or removing fields affects only the immediate table mapped to that type. This mapping strategy provides the greatest flexibility at design time, since changes to any type are always limited to that type's dedicated table.
+++ Performance impact
This strategy inherently requires multiple JOIN operations to perform just about any query which can have a negative impact on performance, especially with large tables and/or large hierarchies. When partial objects are allowed, either globally or on the specific query, then querying for any type will not cause the tables of subtypes to be OUTER JOINed which can increase performance but the resulting partial objects will not fully load themselves on access of any subtype fields, so accessing fields of subtypes after such a query is not safe.
There is a general performance consideration with Class Table Inheritance: If you use a CTI entity as a many-to-one or one-to-one entity you should never use one of the classes at the upper levels of the inheritance hierachy as "targetEntity", only those that have no subclasses. Otherwise Doctrine *CANNOT* create proxy instances of this entity and will *ALWAYS* load the entity eagerly.
+++ SQL Schema considerations
For each entity in the Class-Table Inheritance hierarchy all the mapped fields have to be columns on the
table of this entity. Additionally each child table has to have an id column that matches the id column
definition on the root table (except for any sequence or auto-increment details). Furthermore each child table has
to have a foreign key pointing from the id column to the root table id column and cascading on delete.
++ Querying Inheritance Hierachies
By definition you cannot query for the type of a MappedSuperclass. However in Single- or Class-Table Inheritance scenarios
you can query for any instance specified in the DiscriminatorMap or the root entity name. For both inheritance scenarios
the following queries would work:
[php]
$animals = $em->getRepository('Zoo\Entities\Animal')->findAll();
$cats = $em->getRepository('Zoo\Entities\Cat')->findAll();
$dogs = $em->getRepository('Zoo\Entities\Dog')->findAll();
This works for all finder methods, the Query Builder and DQL.

View File

@ -1,309 +0,0 @@
++ Welcome
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides
transparent persistence for PHP objects. It sits on top of a powerful database
abstraction layer (DBAL). Object-Relational Mappers primary task is the transparent
translation between (PHP) objects and relational database rows.
One of Doctrines key features is the option to write database queries in a
proprietary object oriented SQL dialect called Doctrine
Query Language (DQL), inspired by Hibernates HQL. Besides DQLs slight
differences to SQL it abstracts the mapping between database rows and
objects considerably, allowing developers to write powerful queries
in a simple and flexible fashion.
++ Disclaimer
This is the Doctrine 2 reference documentation. Introductory guides and tutorials
that you can follow along from start to finish, like the "Guide to Doctrine" book
known from the Doctrine 1.x series, will be available at a later date.
++ Using an Object-Relational Mapper
As the term ORM already hints at, Doctrine 2 aims to simplify the translation
between database rows and the PHP object model. The primary use case for Doctrine
are therefore applications that utilize the Object-Oriented Programming Paradigm.
For applications that not primarily work with objects Doctrine 2 is not suited very well.
++ Requirements
Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved performance it
is also recommended that you use APC with PHP.
++ Doctrine 2 Packages
Doctrine 2 is divided into three main packages.
* Common
* DBAL (includes Common)
* ORM (includes DBAL+Common)
This manual mainly covers the ORM package, sometimes touching parts of the
underlying DBAL and Common packages. The Doctrine code base is split in to these
packages for a few reasons and they are to...
* ...make things more maintainable and decoupled
* ...allow you to use the code in Doctrine Common without the ORM or DBAL
* ...allow you to use the DBAL without the ORM
+++ The Common Package
The Common package contains highly reusable components that have no dependencies
beyond the package itself (and PHP, of course). The root namespace of the
Common package is `Doctrine\Common`.
+++ The DBAL Package
The DBAL package contains an enhanced database abstraction layer on top of
PDO but is not strongly bound to PDO. The purpose of this layer is to provide a
single API that bridges most of the differences between the different RDBMS vendors.
The root namespace of the DBAL package is `Doctrine\DBAL`.
+++ The ORM Package
The ORM package contains the object-relational mapping toolkit that provides
transparent relational persistence for plain PHP objects. The root namespace of
the ORM package is `Doctrine\ORM`.
++ Installing
Doctrine can be installed many different ways. We will describe all the different
ways and you can choose which one suits you best.
+++ PEAR
You can easily install any of the three Doctrine packages from the PEAR command
line installation utility.
To install just the `Common` package you can run the following command:
$ sudo pear install pear.doctrine-project.org/DoctrineCommon-<version>
If you want to use the Doctrine Database Abstraction Layer you can install it
with the following command.
$ sudo pear install pear.doctrine-project.org/DoctrineDBAL-<version>
Or, if you want to get the works and go for the ORM you can install it with the
following command.
$ sudo pear install pear.doctrine-project.org/DoctrineORM-<version>
> **NOTE**
> The `<version>` tag above represents the version you want to install. For example the
> current version at the time of writing this is `2.0.0BETA3` for the ORM, so you could
> install it like the following:
>
> $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.0.0BETA3
When you have a package installed via PEAR you can require and load the
`ClassLoader` with the following code.
[php]
require 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader();
The packages are installed in to your shared PEAR PHP code folder in a folder
named `Doctrine`. You also get a nice command line utility installed and made
available on your system. Now when you run the `doctrine` command you will
see what you can do with it.
$ doctrine
Doctrine Command Line Interface version 2.0.0BETA3-DEV
Usage:
[options] command [arguments]
Options:
--help -h Display this help message.
--quiet -q Do not output any message.
--verbose -v Increase verbosity of messages.
--version -V Display this program version.
--color -c Force ANSI color output.
--no-interaction -n Do not ask any interactive question.
Available commands:
help Displays help for a command (?)
list Lists commands
dbal
:import Import SQL file(s) directly to Database.
:run-sql Executes arbitrary SQL directly from the command line.
orm
:convert-d1-schema Converts Doctrine 1.X schema into a Doctrine 2.X schema.
:convert-mapping Convert mapping information between supported formats.
:ensure-production-settings Verify that Doctrine is properly configured for a production environment.
:generate-entities Generate entity classes and method stubs from your mapping information.
:generate-proxies Generates proxy classes for entity classes.
:generate-repositories Generate repository classes from your mapping information.
:run-dql Executes arbitrary DQL directly from the command line.
:validate-schema Validate that the mapping files.
orm:clear-cache
:metadata Clear all metadata cache of the various cache drivers.
:query Clear all query cache of the various cache drivers.
:result Clear result cache of the various cache drivers.
orm:schema-tool
:create Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.
:drop Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output.
:update Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
+++ Package Download
You can also use Doctrine 2 by downloading the latest release package
from [the download page](http://www.doctrine-project.org/download).
See the configuration section on how to configure and bootstrap a downloaded
version of Doctrine.
+++ GitHub
Alternatively you can clone the latest version of Doctrine 2 via GitHub.com:
$ git clone git://github.com/doctrine/doctrine2.git doctrine
This downloads all the sources of the ORM package. You need to initialize the Github
submodules for the Common and DBAL package dependencies:
$ git submodule init
$ git submodule update
This updates your Git checkout to use the Doctrine\Common and Doctrine\DBAL package
versions that are recommended for the cloned Master version of Doctrine 2.
See the configuration chapter on how to configure a Github installation of Doctrine
with regards to autoloading.
> **NOTE**
>
> You should not combine the Doctrine-Common, Doctrine-DBAL and Doctrine-ORM master commits
> with each other in combination. The ORM may not work with the current Common or DBAL master versions.
> Instead the ORM ships with the Git Submodules that are required.
+++ Subversion
> **NOTE**
>
> Using the SVN Mirror is not recommended. It only allows access to the latest master commit
> and does not automatically fetch the submodules.
If you prefer subversion you can also checkout the code from GitHub.com through
the subversion protocol:
$ svn co http://svn.github.com/doctrine/doctrine2.git doctrine2
However this only allows you to check out the current master of Doctrine 2, without
the Common and DBAL dependencies. You have to grab them yourself, but might run
into version incompatibilities between the different master branches of Common, DBAL
and ORM.
++ Sandbox Quickstart
> **NOTE**
> The sandbox is only available via the Doctrine2 Github Repository or soon as a separate download on the downloads
> page. You will find it in the $root/tools/sandbox folder.
The sandbox is a pre-configured environment for evaluating and playing
with Doctrine 2.
+++ Overview
After navigating to the sandbox directory, you should see the following structure:
sandbox/
Entities/
Address.php
User.php
xml/
Entities.Address.dcm.xml
Entities.User.dcm.xml
yaml/
Entities.Address.dcm.yml
Entities.User.dcm.yml
cli-config.php
doctrine
doctrine.php
index.php
Here is a short overview of the purpose of these folders and files:
* The `Entities` folder is where any model classes are created. Two example entities are already there.
* The `xml` folder is where any XML mapping files are created (if you want to use XML mapping). Two example mapping documents for the 2 example entities are already there.
* The `yaml` folder is where any YAML mapping files are created (if you want to use YAML mapping). Two example mapping documents for the 2 example entities are already there.
* The `cli-config.php` contains bootstrap code for a configuration that is used by the Console tool `doctrine` whenever you execute a task.
* `doctrine`/`doctrine.php` is a command-line tool.
* `index.php` is a basic classical bootstrap file of a php application that uses Doctrine 2.
+++ Mini-tutorial
1) From within the tools/sandbox folder, run the following command and you should
see the same output.
$ php doctrine orm:schema-tool:create
Creating database schema...
Database schema created successfully!
2) Take another look into the tools/sandbox folder. A SQLite database should
have been created with the name `database.sqlite`.
3) Open `index.php` and at the bottom edit it so it looks like the following:
[php]
//... bootstrap stuff
## PUT YOUR TEST CODE BELOW
$user = new \Entities\User;
$user->setName('Garfield');
$em->persist($user);
$em->flush();
echo "User saved!";
Open index.php in your browser or execute it on the command line. You should see
the output "User saved!".
4) Inspect the SQLite database. Again from within the tools/sandbox folder,
execute the following command:
$ php doctrine dbal:run-sql "select * from users"
You should get the following output:
array(1) {
[0]=>
array(2) {
["id"]=>
string(1) "1"
["name"]=>
string(8) "Garfield"
}
}
You just saved your first entity with a generated ID in an SQLite database.
5) Replace the contents of index.php with the following:
[php]
//... bootstrap stuff
## PUT YOUR TEST CODE BELOW
$q = $em->createQuery('select u from Entities\User u where u.name = ?1');
$q->setParameter(1, 'Garfield');
$garfield = $q->getSingleResult();
echo "Hello " . $garfield->getName() . "!";
You just created your first DQL query to retrieve the user with the name
'Garfield' from an SQLite database (Yes, there is an easier way to do it,
but we wanted to introduce you to DQL at this point. Can you **find** the easier way?).
> **TIP**
> When you create new model classes or alter existing ones you can recreate the database
> schema with the command `doctrine orm:schema-tool --drop` followed by
> `doctrine orm:schema-tool --create`.
6) Explore Doctrine 2!
See the following links if you want to start with more complex tutorials rather than reading the manual:
* Doctrine2 Cookbook: [Getting Started XML Edition](http://www.doctrine-project.org/projects/orm/2.0/docs/cookbook/getting-started-xml-edition/en)

View File

@ -1,203 +0,0 @@
We try to make using Doctrine2 a very pleasant experience. Therefore we think it is very important
to be honest about the current limitations to our users.
Much like every other piece of software Doctrine2 is not perfect and far from feature complete.
This section should give you an overview of current limitations of Doctrine 2 as well as critical known issues that
you should know about.
++ Current Limitations
There is a set of limitations that exist currently which might be solved in the future. Any of this
limitations now stated has at least one ticket in the Tracker and is discussed for future releases.
+++ Foreign Keys as Identifiers
There are many use-cases where you would want to use an Entity-Attribute-Value approach to modelling and
define a table-schema like the following:
[sql]
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This is currently *NOT* possible with Doctrine2. You have to define a surrogate key on the `product_attributes`
table and use a unique-constraint for the `product_id` and `attribute_name`.
[sql]
CREATE TABLE product_attributes (
attribute_id, INTEGER,
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (attribute_id),
UNIQUE (product_id, attribute_name)
);
Although we state that we support composite primary keys that does not currently include foreign keys as primary key
columns. To see the fundamental difference between the two different `product_attributes` tables you should see
how they translate into a Doctrine Mapping (Using Annotations):
[php]
/**
* Scenario 1: THIS IS NOT POSSIBLE CURRENTLY
* @Entity @Table(name="product_attributes")
*/
class ProductAttribute
{
/** @Id @ManyToOne(targetEntity="Product") */
private $product;
/** @Id @Column(type="string", name="attribute_name") */
private $name;
/** @Column(type="string", name="attribute_value") */
private $value;
}
/**
* Scenario 2: Using the surrogate key workaround
* @Entity
* @Table(name="product_attributes", uniqueConstraints={@UniqueConstraint(columns={"product_id", "attribute_name"})}))
*/
class ProductAttribute
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @ManyToOne(targetEntity="Product") */
private $product;
/** @Column(type="string", name="attribute_name") */
private $name;
/** @Column(type="string", name="attribute_value") */
private $value;
}
The following Jira Issue [contains the feature request to allow @ManyToOne and @OneToOne annotations
along the @Id annotation](http://www.doctrine-project.org/jira/browse/DDC-117).
+++ Mapping Arrays to a Join Table
Related to the previous limitation with "Foreign Keys as Identifier" you might be interested in mapping the same
table structure as given above to an array. However this is not yet possible either. See the following example:
[sql]
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This schema should be mapped to a Product Entity as follows:
class Product
{
private $id;
private $name;
private $attributes = array();
}
Where the `attribute_name` column contains the key and `attribute_value` contains the value
of each array element in `$attributes`.
The feature request for persistence of primitive value arrays [is described in the DDC-298 ticket](http://www.doctrine-project.org/jira/browse/DDC-298).
+++ Value Objects
There is currently no native support value objects in Doctrine other than for `DateTime` instances or if you
serialize the objects using `serialize()/deserialize()` which the DBAL Type "object" supports.
The feature request for full value-object support [is described in the DDC-93 ticket](http://www.doctrine-project.org/jira/browse/DDC-93).
+++ Applying Filter Rules to any Query
There are scenarios in many applications where you want to apply additional filter rules to each query implicitly. Examples include:
* In I18N Applications restrict results to a entities annotated with a specific locale
* For a large collection always only return objects in a specific date range/where condition applied.
* Soft-Delete
There is currently no way to achieve this consistently across both DQL and Repository/Persister generated queries, but
as this is a pretty important feature we plan to add support for it in the future.
+++ Custom Persisters
A Persister in Doctrine is an object that is responsible for the hydration and write operations of an entity against the database.
Currently there is no way to overwrite the persister implementation for a given entity, however there are several use-cases that
can benefit from custom persister implementations:
* [Add Upsert Support](http://www.doctrine-project.org/jira/browse/DDC-668)
* [Evaluate possible ways in which stored-procedures can be used](http://www.doctrine-project.org/jira/browse/DDC-445)
* The previous Filter Rules Feature Request
+++ Persist Keys of Collections
PHP Arrays are ordered hash-maps and so should be the `Doctrine\Common\Collections\Collection` interface. We plan
to evaluate a feature that optionally persists and hydrates the keys of a Collection instance.
[Ticket DDC-213](http://www.doctrine-project.org/jira/browse/DDC-213)
+++ Mapping many tables to one entity
It is not possible to map several equally looking tables onto one entity. For example if you have
a production and an archive table of a certain business concept then you cannot have both tables
map to the same entity.
+++ Behaviors
Doctrine 2 *will never* include a behavior system like Doctrine 1 in the core library. We don't think behaviors
add more value than they cost pain and debugging hell. Please see the many different blog posts we have written on this
topics:
* [Doctrine2 "Behaviors" in a Nutshell](http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell)
* [A re-usable Versionable behavior for Doctrine2](http://www.doctrine-project.org/blog/doctrine2-versionable)
* [Write your own ORM on top of Doctrine2](http://www.doctrine-project.org/blog/your-own-orm-doctrine2)
Doctrine 2 has enough hooks and extension points so that *you* can add whatever you want on top of it.
None of this will ever become core functionality of Doctrine2 however, you will have to rely on third party extensions
for magical behaviors.
+++ Nested Set
NestedSet was offered as a behavior in Doctrine 1 and will not be included in the core of Doctrine 2. However there
are already two extensions out there that offer support for Nested Set with Doctrine 2:
* [Doctrine2 Hierachical-Structural Behavior](http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior)
* [Doctrine2 NestedSet](http://github.com/blt04/doctrine2-nestedset)
++ Known Issues
The Known Issues section describes critical/blocker bugs and other issues that are either complicated to fix,
not fixable due to backwards compatibility issues or where no simple fix exists (yet). We don't
plan to add every bug in the tracker there, just those issues that can potentially cause nightmares
or pain of any sort.
+++ Identifier Quoting and Legacy Databases
For compatibility reasons between all the supported vendors and edge case problems
Doctrine 2 does *NOT* do automatic identifier quoting. This can lead to problems when trying to get
legacy-databases to work with Doctrine 2.
* You can quote column-names as described in the [Basic-Mapping](basic-mapping) section.
* You cannot quote join column names.
* You cannot use non [a-zA-Z0-9_]+ characters, they will break several SQL statements.
Having problems with these kind of column names? Many databases support all CRUD operations on views that
semantically map to certain tables. You can create views for all your problematic tables and column names
to avoid the legacy quoting nightmare.

View File

@ -1,155 +0,0 @@
The heart of an object relational mapper is the mapping information that glues
everything together. It instructs the EntityManager how it should behave when
dealing with the different entities.
++ Core Metadata Drivers
Doctrine provides a few different ways for you to specify your metadata:
* **XML files** (XmlDriver)
* **Class DocBlock Annotations** (AnnotationDriver)
* **YAML files** (YamlDriver)
* **PHP Code in files or static functions** (PhpDriver)
Something important to note about the above drivers is they are all an intermediate
step to the same end result. The mapping information is populated to
`Doctrine\ORM\Mapping\ClassMetadata` instances. So in the end, Doctrine
only ever has to work with the API of the `ClassMetadata` class to get mapping
information for an entity.
> **TIP**
> The populated `ClassMetadata` instances are also cached so in a production
> environment the parsing and populating only ever happens once. You can configure
> the metadata cache implementation using the `setMetadataCacheImpl()` method on
> the `Doctrine\ORM\Configuration` class:
>
> [php]
> $em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
If you want to use one of the included core metadata drivers you just need to
configure it. All the drivers are in the `Doctrine\ORM\Mapping\Driver` namespace:
[php]
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver('/path/to/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
++ Implementing Metadata Drivers
In addition to the included metadata drivers you can very easily implement
your own. All you need to do is define a class which implements the `Driver`
interface:
[php]
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
interface Driver
{
/**
* Loads the metadata for the specified class into the provided container.
*
* @param string $className
* @param ClassMetadataInfo $metadata
*/
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array The names of all mapped classes known to this driver.
*/
function getAllClassNames();
/**
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a
* MappedSuperclass.
*
* @param string $className
* @return boolean
*/
function isTransient($className);
}
If you want to write a metadata driver to parse information from some file format
we've made your life a little easier by providing the `AbstractFileDriver`
implementation for you to extend from:
[php]
class MyMetadataDriver extends AbstractFileDriver
{
/**
* {@inheritdoc}
*/
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$data = $this->_loadMappingFile($file);
// populate ClassMetadataInfo instance from $data
}
/**
* {@inheritdoc}
*/
protected function _loadMappingFile($file)
{
// parse contents of $file and return php data structure
}
}
> **NOTE**
> When using the `AbstractFileDriver` it requires that you only have one entity
> defined per file and the file named after the class described inside where
> namespace separators are replaced by periods. So if you have an entity named
> `Entities\User` and you wanted to write a mapping file for your driver above
> you would need to name the file `Entities.User.dcm.ext` for it to be recognized.
Now you can use your `MyMetadataDriver` implementation by setting it with the
`setMetadataDriverImpl()` method:
[php]
$driver = new MyMetadataDriver('/path/to/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
++ ClassMetadata
The last piece you need to know and understand about metadata in Doctrine 2 is
the API of the `ClassMetadata` classes. You need to be familiar with them in order
to implement your own drivers but more importantly to retrieve mapping information
for a certain entity when needed.
You have all the methods you need to manually specify the mapping information
instead of using some mapping file to populate it from. The base `ClassMetadataInfo`
class is responsible for only data storage and is not meant for runtime use. It
does not require that the class actually exists yet so it is useful for describing some
entity before it exists and using that information to generate for example the
entities themselves. The class `ClassMetadata` extends `ClassMetadataInfo` and
adds some functionality required for runtime usage and requires that the PHP
class is present and can be autoloaded.
You can read more about the API of the `ClassMetadata` classes in the PHP Mapping
chapter.
++ Getting ClassMetadata Instances
If you want to get the `ClassMetadata` instance for an entity in your project
to programatically use some mapping information to generate some HTML or something
similar you can retrieve it through the `ClassMetadataFactory`:
[php]
$cmf = $em->getMetadataFactory();
$class = $cmf->getMetadataFor('MyEntityName');
Now you can learn about the entity and use the data stored in the `ClassMetadata`
instance to get all mapped fields for example and iterate over them:
[php]
foreach ($class->fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}

View File

@ -1,242 +0,0 @@
A `NativeQuery` lets you execute native SQL, mapping the results according to your specifications.
Such a specification that describes how an SQL result set is mapped to a Doctrine result is
represented by a `ResultSetMapping`. It describes how each column of the database result should
be mapped by Doctrine in terms of the object graph. This allows you to map arbitrary SQL code to objects, such as
highly vendor-optimized SQL or stored-procedures.
++ The NativeQuery class
To create a `NativeQuery` you use the method `EntityManager#createNativeQuery($sql, $resultSetMapping)`. As you can see in the signature of this method, it expects 2 ingredients: The SQL you want to execute and the `ResultSetMapping` that describes how the results will be mapped.
Once you obtained an instance of a `NativeQuery`, you can bind parameters to it and finally execute it.
++ The ResultSetMapping
Understanding the `ResultSetMapping` is the key to using a `NativeQuery`.
A Doctrine result can contain the following components:
* Entity results. These represent root result elements.
* Joined entity results. These represent joined entities in associations of root entity results.
* Field results. These represent a column in the result set that maps to a field of an entity. A field result always belongs to an entity result or joined entity result.
* Scalar results. These represent scalar values in the result set that will appear in each result row. Adding scalar results to a ResultSetMapping can also cause the overall result to become **mixed** (see DQL - Doctrine Query Language) if the same ResultSetMapping also contains entity results.
* Meta results. These represent columns that contain meta-information, such as foreign keys and discriminator columns.
When querying for objects (`getResult()`), all meta columns of root entities or joined entities must be present in the SQL query
and mapped accordingly using `ResultSetMapping#addMetaResult`.
> **TIP**
> It might not surprise you that Doctrine uses `ResultSetMapping`s internally when you
> create DQL queries. As the query gets parsed and transformed to SQL, Doctrine fills
> a `ResultSetMapping` that describes how the results should be processed by the hydration
> routines.
We will now look at each of the result types that can appear in a ResultSetMapping in detail.
+++ Entity results
An entity result describes an entity type that appears as a root element in the transformed result. You add an entity result through `ResultSetMapping#addEntityResult()`.
Let's take a look at the method signature in detail:
[php]
/**
* Adds an entity result to this ResultSetMapping.
*
* @param string $class The class name of the entity.
* @param string $alias The alias for the class. The alias must be unique among all entity
* results or joined entity results within this ResultSetMapping.
*/
public function addEntityResult($class, $alias)
The first parameter is the fully qualified name of the entity class. The second parameter is some arbitrary alias for this entity result that must be unique within a `ResultSetMapping`. You use this alias to attach field results to the entity result. It is very similar to an identification variable that you use in DQL to alias classes or relationships.
An entity result alone is not enough to form a valid `ResultSetMapping`. An entity result or joined entity result always needs a set of field results, which we will look at soon.
+++ Joined entity results
A joined entity result describes an entity type that appears as a joined relationship element in the transformed result, attached to a (root) entity result. You add a joined entity result through `ResultSetMapping#addJoinedEntityResult()`. Let's take a look at the method signature in detail:
[php]
/**
* Adds a joined entity result.
*
* @param string $class The class name of the joined entity.
* @param string $alias The unique alias to use for the joined entity.
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
* @param object $relation The association field that connects the parent entity result with the joined entity result.
*/
public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
The first parameter is the class name of the joined entity. The second parameter is an arbitrary alias for the joined entity that must be unique within the `ResultSetMapping`.
You use this alias to attach field results to the entity result. The third parameter is the alias of the entity result that is the parent type of the joined relationship. The fourth and last parameter is the name of the field on the parent entity result that should contain the joined entity result.
+++ Field results
A field result describes the mapping of a single column in an SQL result set to a field in an entity. As such, field results are inherently bound to entity results. You add a field result through `ResultSetMapping#addFieldResult()`. Again, let's examine the method signature in detail:
[php]
/**
* Adds a field result that is part of an entity result or joined entity result.
*
* @param string $alias The alias of the entity result or joined entity result.
* @param string $columnName The name of the column in the SQL result set.
* @param string $fieldName The name of the field on the (joined) entity.
*/
public function addFieldResult($alias, $columnName, $fieldName)
The first parameter is the alias of the entity result to which the field result will belong. The second parameter is the name of the column in the SQL result set. Note that this name is case sensitive, i.e. if you use a native query against Oracle it must be all uppercase. The third parameter is the name of the field on the entity result identified by `$alias` into which the value of the column should be set.
+++ Scalar results
A scalar result describes the mapping of a single column in an SQL result set to a scalar value in the Doctrine result. Scalar results are typically used for aggregate values but any column in the SQL result set can be mapped as a scalar value. To add a scalar result use `ResultSetMapping#addScalarResult()`. The method signature in detail:
[php]
/**
* Adds a scalar result mapping.
*
* @param string $columnName The name of the column in the SQL result set.
* @param string $alias The result alias with which the scalar result should be placed in the result structure.
*/
public function addScalarResult($columnName, $alias)
The first parameter is the name of the column in the SQL result set and the second parameter is the result alias under which the value of the column will be placed in the transformed Doctrine result.
+++ Meta results
A meta result describes a single column in an SQL result set that is either a foreign key or a discriminator column.
These columns are essential for Doctrine to properly construct objects out of SQL result sets.
To add a column as a meta result use `ResultSetMapping#addMetaResult()`. The method signature in detail:
[php]
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
* @param string $alias
* @param string $columnAlias
* @param string $columnName
*/
public function addMetaResult($alias, $columnAlias, $columnName)
The first parameter is the alias of the entity result to which the meta column belongs.
A meta result column (foreign key or discriminator column) always belongs to to an entity result.
The second parameter is the column alias/name of the column in the SQL result set and the third parameter is the
column name used in the mapping.
+++ Discriminator Column
When joining an inheritance tree you have to give Doctrine a hint which meta-column is the discriminator column
of this tree.
[php]
/**
* Sets a discriminator column for an entity result or joined entity result.
* The discriminator column will be used to determine the concrete class name to
* instantiate.
*
* @param string $alias The alias of the entity result or joined entity result the discriminator
* column should be used for.
* @param string $discrColumn The name of the discriminator column in the SQL result set.
* @todo Rename: addDiscriminatorColumn
*/
public function setDiscriminatorColumn($alias, $discrColumn)
+++ Examples
Understanding a ResultSetMapping is probably easiest through looking at some examples.
First a basic example that describes the mapping of a single entity.
[php]
// Equivalent DQL query: "select u from User u where u.name=?1"
// User owns no associations.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
The result would look like this:
array(
[0] => User (Object)
)
Note that this would be a partial object if the entity has more fields than just id and name. In the example above the column and field names are identical but that is not necessary, of course. Also note that the query string passed to createNativeQuery is **real native SQL**. Doctrine does not touch this SQL in any way.
In the previous basic example, a User had no relations and the table the class is mapped to owns no foreign keys.
The next example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress,
where the User is the owning side and thus owns the foreign key.
[php]
// Equivalent DQL query: "select u from User u where u.name=?1"
// User owns an association to an Address but the Address is not loaded in the query.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Foreign keys are used by Doctrine for lazy-loading purposes when querying for objects.
In the previous example, each user object in the result will have a proxy (a "ghost") in place
of the address that contains the address_id. When the ghost proxy is accessed, it loads itself
based on this key.
Consequently, associations that are *fetch-joined* do not require the foreign keys to be present
in the SQL result set, only associations that are lazy.
[php]
// Equivalent DQL query: "select u from User u join u.address a WHERE u.name = ?1"
// User owns association to an Address and the Address is loaded in the query.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address');
$rsm->addFieldResult('a', 'address_id', 'id');
$rsm->addFieldResult('a', 'street', 'street');
$rsm->addFieldResult('a', 'city', 'city');
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
In this case the nested entity `Address` is registered with the `ResultSetMapping#addJoinedEntityResult`
method, which notifies Doctrine that this entity is not hydrated at the root level, but as a joined entity
somewhere inside the object graph. In this case we specify the alias 'u' as third parameter and `address`
as fourth parameter, which means the `Address` is hydrated into the `User::$address` property.
If a fetched entity is part of a mapped hierarchy that requires a discriminator column, this
column must be present in the result set as a meta column so that Doctrine can create the
appropriate concrete type. This is shown in the following example where we assume that there
are one or more subclasses that extend User and either Class Table Inheritance or Single Table Inheritance
is used to map the hierarchy (both use a discriminator column).
[php]
// Equivalent DQL query: "select u from User u where u.name=?1"
// User is a mapped base class for other classes. User owns no associations.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Note that in the case of Class Table Inheritance, an example as above would result in partial objects
if any objects in the result are actually a subtype of User. When using DQL, Doctrine automatically
includes the necessary joins for this mapping strategy but with native SQL it is your responsibility.

View File

@ -1,49 +0,0 @@
A partial object is an object whose state is not fully initialized after being
reconstituted from the database and that is disconnected from the rest of its data.
The following section will describe why partial objects are problematic and what the approach of Doctrine2 to this problem is.
> **NOTE**
> The partial object problem in general does not apply to methods or
> queries where you do not retrieve the query result as objects. Examples are:
> `Query#getArrayResult()`, `Query#getScalarResult()`, `Query#getSingleScalarResult()`,
> etc.
++ What is the problem?
In short, partial objects are problematic because they are usually objects with
broken invariants. As such, code that uses these partial objects tends to be
very fragile and either needs to "know" which fields or methods can be safely
accessed or add checks around every field access or method invocation. The same
holds true for the internals, i.e. the method implementations, of such objects.
You usually simply assume the state you need in the method is available, after
all you properly constructed this object before you pushed it into the database,
right? These blind assumptions can quickly lead to null reference errors when
working with such partial objects.
It gets worse with the scenario of an optional association (0..1 to 1). When
the associated field is NULL, you don't know whether this object does not have
an associated object or whether it was simply not loaded when the owning object
was loaded from the database.
These are reasons why many ORMs do not allow partial objects at all and instead
you always have to load an object with all its fields (associations being proxied).
One secure way to allow partial objects is if the programming language/platform
allows the ORM tool to hook deeply into the object and instrument it in such a
way that individual fields (not only associations) can be loaded lazily on first
access. This is possible in Java, for example, through bytecode instrumentation.
In PHP though this is not possible, so there is no way to have "secure" partial
objects in an ORM with transparent persistence.
Doctrine, by default, does not allow partial objects. That means, any query that
only selects partial object data and wants to retrieve the result as objects
(i.e. `Query#getResult()`) will raise an exception telling you that
partial objects are dangerous. If you want to force a query to return you partial
objects, possibly as a performance tweak, you can use the `partial` keyword as follows:
[php]
$q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u");
++ When should I force partial objects?
Mainly for optimization purposes, but be careful of premature optimization as partial objects
lead to potentially more fragile code.

View File

@ -1,202 +0,0 @@
Doctrine 2 also allows you to provide the ORM metadata in the form of plain
PHP code using the `ClassMetadata` API. You can write the code in PHP files or
inside of a static function named `loadMetadata($class)` on the entity class itself.
++ PHP Files
If you wish to write your mapping information inside PHP files that are named
after the entity and included to populate the metadata for an entity you can do
so by using the `PHPDriver`:
[php]
$driver = new PHPDriver('/path/to/php/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now imagine we had an entity named `Entities\User` and we wanted to write a mapping
file for it using the above configured `PHPDriver` instance:
[php]
namespace Entities;
class User
{
private $id;
private $username;
}
To write the mapping information you just need to create a file named
`Entities.User.php` inside of the `/path/to/php/mapping/files` folder:
[php]
// /path/to/php/mapping/files/Entities.User.php
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string'
));
Now we can easily retrieve the populated `ClassMetadata` instance where the `PHPDriver`
includes the file and the `ClassMetadataFactory` caches it for later retrieval:
[php]
$class = $em->getClassMetadata('Entities\User');
// or
$class = $em->getMetadataFactory()->getMetadataFor('Entities\User');
++ Static Function
In addition to the PHP files you can also specify your mapping information inside
of a static function defined on the entity class itself. This is useful for cases
where you want to keep your entity and mapping information together but don't want
to use annotations. For this you just need to use the `StaticPHPDriver`:
[php]
$driver = new StaticPHPDriver('/path/to/entities');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now you just need to define a static function named `loadMetadata($metadata)` on your entity:
[php]
namespace Entities;
use Doctrine\ORM\Mapping\ClassMetadata;
class User
{
// ...
public static function loadMetadata(ClassMetadata $metadata)
{
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string'
));
}
}
++ ClassMetadataInfo API
The `ClassMetadataInfo` class is the base data object for storing the mapping
metadata for a single entity. It contains all the getters and setters you need
populate and retrieve information for an entity.
+++ General Setters
* `setTableName($tableName)`
* `setPrimaryTable(array $primaryTableDefinition)`
* `setCustomRepositoryClass($repositoryClassName)`
* `setIdGeneratorType($generatorType)`
* `setIdGenerator($generator)`
* `setSequenceGeneratorDefinition(array $definition)`
* `setChangeTrackingPolicy($policy)`
* `setIdentifier(array $identifier)`
+++ Inheritance Setters
* `setInheritanceType($type)`
* `setSubclasses(array $subclasses)`
* `setParentClasses(array $classNames)`
* `setDiscriminatorColumn($columnDef)`
* `setDiscriminatorMap(array $map)`
+++ Field Mapping Setters
* `mapField(array $mapping)`
* `mapOneToOne(array $mapping)`
* `mapOneToMany(array $mapping)`
* `mapManyToOne(array $mapping)`
* `mapManyToMany(array $mapping)`
+++ Lifecycle Callback Setters
* `addLifecycleCallback($callback, $event)`
* `setLifecycleCallbacks(array $callbacks)`
+++ Versioning Setters
* `setVersionMapping(array &$mapping)`
* `setVersioned($bool)`
* `setVersionField()`
+++ General Getters
* `getTableName()`
* `getTemporaryIdTableName()`
+++ Identifier Getters
* `getIdentifierColumnNames()`
* `usesIdGenerator()`
* `isIdentifier($fieldName)`
* `isIdGeneratorIdentity()`
* `isIdGeneratorSequence()`
* `isIdGeneratorTable()`
* `isIdentifierNatural()`
* `getIdentifierFieldNames()`
* `getSingleIdentifierFieldName()`
* `getSingleIdentifierColumnName()`
+++ Inheritance Getters
* `isInheritanceTypeNone()`
* `isInheritanceTypeJoined()`
* `isInheritanceTypeSingleTable()`
* `isInheritanceTypeTablePerClass()`
* `isInheritedField($fieldName)`
* `isInheritedAssociation($fieldName)`
+++ Change Tracking Getters
* `isChangeTrackingDeferredExplicit()`
* `isChangeTrackingDeferredImplicit()`
* `isChangeTrackingNotify()`
+++ Field & Association Getters
* `isUniqueField($fieldName)`
* `isNullable($fieldName)`
* `getColumnName($fieldName)`
* `getFieldMapping($fieldName)`
* `getAssociationMapping($fieldName)`
* `getAssociationMappings()`
* `getFieldName($columnName)`
* `hasField($fieldName)`
* `getColumnNames(array $fieldNames = null)`
* `getTypeOfField($fieldName)`
* `getTypeOfColumn($columnName)`
* `hasAssociation($fieldName)`
* `isSingleValuedAssociation($fieldName)`
* `isCollectionValuedAssociation($fieldName)`
+++ Lifecycle Callback Getters
* `hasLifecycleCallbacks($lifecycleEvent)`
* `getLifecycleCallbacks($event)`
++ ClassMetadata API
The `ClassMetadata` class extends `ClassMetadataInfo` and adds the runtime functionality
required by Doctrine. It adds a few extra methods related to runtime reflection
for working with the entities themselves.
* `getReflectionClass()`
* `getReflectionProperties()`
* `getReflectionProperty($name)`
* `getSingleIdReflectionProperty()`
* `getIdentifierValues($entity)`
* `setIdentifierValues($entity, $id)`
* `setFieldValue($entity, $field, $value)`
* `getFieldValue($entity, $field)`

View File

@ -1,254 +0,0 @@
++ The Doctrine Console
The Doctrine Console is a Command Line Interface tool for simplifying common tasks during the development of a project that uses Doctrine 2.
+++ Installation
If you installed Doctrine 2 through PEAR, the `doctrine` command line tool should already be available to you.
If you use Doctrine through SVN or a release package you need to copy the `doctrine` and `doctrine.php` files from the `tools/sandbox` or `bin` folder, respectively, to a location of your choice, for example a `tools` folder of your project.
You probably need to edit `doctrine.php` to adjust some paths to the new environment, most importantly the first line that includes the `Doctrine\Common\ClassLoader`.
+++ Getting Help
Type `doctrine` on the command line and you should see an overview of the available commands or use the --help flag to get information on the available commands. If you want to know more about the use of generate entities for example, you can call:
doctrine orm:generate-entities --help
+++ Configuration
Whenever the `doctrine` command line tool is invoked, it can access alls Commands that were registered by developer.
There is no auto-detection mechanism at work. The `bin\doctrine.php` file already registers all the commands that
currently ship with Doctrine DBAL and ORM. If you want to use additional commands you have to register them yourself.
All the commands of the Doctrine Console require either the `db` or the `em` helpers to be defined in order to work correctly. Doctrine Console requires the definition of a HelperSet that is the DI tool to be injected in the Console.
In case of a project that is dealing exclusively with DBAL, the ConnectionHelper is required:
[php]
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
));
$cli->setHelperSet($helperSet);
When dealing with the ORM package, the EntityManagerHelper is required:
[php]
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
$cli->setHelperSet($helperSet);
The HelperSet instance has to be generated in a separate file (i.e. `cli-config.php`) that contains typical Doctrine
bootstrap code and predefines the needed HelperSet attributes mentioned above. A typical `cli-config.php` file looks as follows:
[php]
require_once __DIR__ . '/../../lib/Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Entities', __DIR__);
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Proxies', __DIR__);
$classLoader->register();
$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
$config->setProxyDir(__DIR__ . '/Proxies');
$config->setProxyNamespace('Proxies');
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $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)
));
It is important to define a correct HelperSet that doctrine.php script will ultimately use. The Doctrine Binary
will automatically find the first instance of HelperSet in the global variable namespace and use this.
You can also add your own commands on-top of the Doctrine supported tools.
To include a new command on Doctrine Console, you need to do:
[php]
$cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand());
Additionally, include multiple commands (and overriding previously defined ones) is possible through the command:
[php]
$cli->addCommands(array(
new \MyProject\Tools\Console\Commands\MyCustomCommand(),
new \MyProject\Tools\Console\Commands\SomethingCommand(),
new \MyProject\Tools\Console\Commands\AnotherCommand(),
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
));
+++ Command Overview
The following Commands are currently available:
* `help` Displays help for a command (?)
* `list` Lists commands
* `dbal:import` Import SQL file(s) directly to Database.
* `dbal:run-sql` Executes arbitrary SQL directly from the command line.
* `orm:clear-cache:metadata` Clear all metadata cache of the various cache drivers.
* `orm:clear-cache:query` Clear all query cache of the various cache drivers.
* `orm:clear-cache:result` Clear result cache of the various cache drivers.
* `orm:convert-d1-schema` Converts Doctrine 1.X schema into a Doctrine 2.X schema.
* `orm:convert-mapping` Convert mapping information between supported formats.
* `orm:ensure-production-settings` Verify that Doctrine is properly configured for a production environment.
* `orm:generate-entities` Generate entity classes and method stubs from your mapping information.
* `orm:generate-proxies` Generates proxy classes for entity classes.
* `orm:generate-repositories` Generate repository classes from your mapping information.
* `orm:run-dql` Executes arbitrary DQL directly from the command line.
* `orm:schema-tool:create` Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output.
* `orm:schema-tool:drop` Processes the schema and either drop the database schema of EntityManager Storage Connection or generate the SQL output.
* `orm:schema-tool:update` Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
++ Database Schema Generation
> **Note**
>
> SchemaTool can do harm to your database. It will drop or alter tables, indexes, sequences and such. Please use
> this tool with caution in development and not on a production server. It is meant for helping you develop your
> Database Schema, but NOT with migrating schema from A to B in production. A safe approach would be generating
> the SQL on development server and saving it into SQL Migration files that are executed manually on the production
> server.
>
> SchemaTool assumes your Doctrine Project uses the given database on its own. Update and Drop commands will
> mess with other tables if they are not related to the current project that is using Doctrine. Please be careful!
To generate your database schema from your Doctrine mapping files you can use the
`SchemaTool` class or the `schema-tool` Console Command.
When using the SchemaTool class directly, create your schema using the `createSchema()` method. First create an instance of the `SchemaTool` and pass it an instance of the `EntityManager` that you want to use to create the schema. This method receives an array of `ClassMetadataInfo` instances.
[php]
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$tool->createSchema($classes);
To drop the schema you can use the `dropSchema()` method.
[php]
$tool->dropSchema($classes);
This drops all the tables that are currently used by your metadata model.
When you are changing your metadata a lot during development you might want
to drop the complete database instead of only the tables of the current model
to clean up with orphaned tables.
[php]
$tool->dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE);
You can also use database introspection to update your schema easily with the
`updateSchema()` method. It will compare your existing database schema to the
passed array of `ClassMetdataInfo` instances.
[php]
$tool->updateSchema($classes);
If you want to use this functionality from the command line you can use the
`schema-tool` command.
To create the schema use the `create` command:
$ php doctrine orm:schema-tool:create
To drop the schema use the `drop` command:
$ php doctrine orm:schema-tool:drop
If you want to drop and then recreate the schema then use both options:
$ php doctrine orm:schema-tool:drop
$ php doctrine orm:schema-tool:create
As you would think, if you want to update your schema use the `update` command:
$ php doctrine orm:schema-tool:update
All of the above commands also accept a `--dump-sql` option that will output the SQL
for the ran operation.
$ php doctrine orm:schema-tool:create --dump-sql
Before using the orm:schema-tool commands, remember to configure your cli-config.php properly.
> **NOTE**
>
> When using the Annotation Mapping Driver you have to either setup your autoloader in the cli-config.php
> correctly to find all the entities, or you can use the second argument of the `EntityManagerHelper` to
> specify all the paths of your entities (or mapping files), i.e.
> `new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);`
++ Convert Mapping Information
To convert some mapping information between the various supported formats you can
use the `ClassMetadataExporter` to get exporter instances for the different formats:
[php]
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
Once you have a instance you can use it to get an exporter. For example, the yml
exporter:
[php]
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
Now you can export some `ClassMetadata` instances:
[php]
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$exporter->setMetadata($classes);
$exporter->export();
This functionality is also available from the command line to convert your
loaded mapping information to another format. The `orm:convert-mapping` command
accepts two arguments, the type to convert to and the path to generate it:
$ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
++ Reverse Engineering
You can use the `DatabaseDriver` to reverse engineer a database to an array of
`ClassMetadataInfo` instances and generate YAML, XML, etc. from them.
First you need to retrieve the metadata instances with the `DatabaseDriver`:
[php]
$em->getConfiguration()->setMetadataDriverImpl(
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
)
);
$cmf = new DisconnectedClassMetadataFactory($em);
$metadata = $cmf->getAllMetadata();
Now you can get an exporter instance and export the loaded metadata to yml:
[php]
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
You can also reverse engineer a database using the `orm:convert-mapping` command:
$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
> **CAUTION**
> Reverse Engineering is not always working perfectly depending on special cases.
> It will only detect Many-To-One relations (even if they are One-To-One) and
> will try to create entities from Many-To-Many tables. It also has problems
> with naming of foreign keys that have multiple column names. Any Reverse Engineered
> Database-Schema needs considerable manual work to become a useful domain model.

View File

@ -1,249 +0,0 @@
++ Transaction Demarcation
Transaction demarcation is the task of defining your transaction boundaries. Proper transaction demarcation is very
important because if not done properly it can negatively affect the performance of your application.
Many databases and database abstraction layers like PDO by default operate in auto-commit mode,
which means that every single SQL statement is wrapped in a small transaction. Without any explicit
transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap.
For the most part, Doctrine 2 already takes care of proper transaction demarcation for you: All the write
operations (INSERT/UPDATE/DELETE) are queued until `EntityManager#flush()` is invoked which wraps all
of these changes in a single transaction.
However, Doctrine 2 also allows (and encourages) you to take over and control transaction demarcation yourself.
These are two ways to deal with transactions when using the Doctrine ORM and are now described in more detail.
+++ Approach 1: Implicitly
The first approach is to use the implicit transaction handling provided by the Doctrine ORM
EntityManager. Given the following code snippet, without any explicit transaction demarcation:
[php]
// $em instanceof EntityManager
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
Since we do not do any custom transaction demarcation in the above code, `EntityManager#flush()` will begin
and commit/rollback a transaction. This behavior is made possible by the aggregation of the DML operations
by the Doctrine ORM and is sufficient if all the data manipulation that is part of a unit of work happens
through the domain model and thus the ORM.
+++ Approach 2: Explicitly
The explicit alternative is to use the `Doctrine\DBAL\Connection` API
directly to control the transaction boundaries. The code then looks like this:
[php]
// $em instanceof EntityManager
$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollback();
$em->close();
throw $e;
}
Explicit transaction demarcation is required when you want to include custom DBAL operations in a unit of work
or when you want to make use of some methods of the `EntityManager` API that require an active transaction.
Such methods will throw a `TransactionRequiredException` to inform you of that requirement.
A more convenient alternative for explicit transaction demarcation is the use of provided control
abstractions in the form of `Connection#transactional($func)` and `EntityManager#transactional($func)`.
When used, these control abstractions ensure that you never forget to rollback the transaction or
close the `EntityManager`, apart from the obvious code reduction. An example that is functionally
equivalent to the previously shown code looks as follows:
[php]
// $em instanceof EntityManager
$em->transactional(function($em) {
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
});
The difference between `Connection#transactional($func)` and `EntityManager#transactional($func)` is
that the latter abstraction flushes the `EntityManager` prior to transaction commit and also closes
the `EntityManager` properly when an exception occurs (in addition to rolling back the transaction).
+++ Exception Handling
When using implicit transaction demarcation and an exception occurs during `EntityManager#flush()`, the transaction
is automatically rolled back and the `EntityManager` closed.
When using explicit transaction demarcation and an exception occurs, the transaction should be rolled back immediately
and the `EntityManager` closed by invoking `EntityManager#close()` and subsequently discarded, as demonstrated in
the example above. This can be handled elegantly by the control abstractions shown earlier.
Note that when catching `Exception` you should generally re-throw the exception. If you intend to
recover from some exceptions, catch them explicitly in earlier catch blocks (but do not forget to rollback the
transaction and close the `EntityManager` there as well). All other best practices of exception handling apply
similarly (i.e. either log or re-throw, not both, etc.).
As a result of this procedure, all previously managed or removed instances of the `EntityManager` become detached.
The state of the detached objects will be the state at the point at which the transaction was rolled back.
The state of the objects is in no way rolled back and thus the objects are now out of synch with the database.
The application can continue to use the detached objects, knowing that their state is potentially no longer
accurate.
If you intend to start another unit of work after an exception has occurred you should do that with a new `EntityManager`.
++ Locking Support
Doctrine 2 offers support for Pessimistic- and Optimistic-locking strategies natively. This allows to take
very fine-grained control over what kind of locking is required for your Entities in your application.
+++ Optimistic Locking
Database transactions are fine for concurrency control during a single request. However, a database transaction
should not span across requests, the so-called "user think time". Therefore a long-running "business transaction"
that spans multiple requests needs to involve several database transactions. Thus, database transactions alone
can no longer control concurrency during such a long-running business transaction. Concurrency control becomes
the partial responsibility of the application itself.
Doctrine has integrated support for automatic optimistic locking via a version field. In this approach any entity
that should be protected against concurrent modifications during long-running business transactions gets a version
field that is either a simple number (mapping type: integer) or a timestamp (mapping type: datetime). When changes
to such an entity are persisted at the end of a long-running conversation the version of the entity is compared to
the version in the database and if they don't match, an `OptimisticLockException` is thrown, indicating that the
entity has been modified by someone else already.
You designate a version field in an entity as follows. In this example we'll use an integer.
[php]
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
// ...
}
Alternatively a datetime type can be used (which maps to an SQL timestamp or datetime):
[php]
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
// ...
}
Version numbers (not timestamps) should however be preferred as they can not potentially conflict in a highly concurrent
environment, unlike timestamps where this is a possibility, depending on the resolution of the timestamp on the particular
database platform.
When a version conflict is encountered during `EntityManager#flush()`, an `OptimisticLockException` is thrown
and the active transaction rolled back (or marked for rollback). This exception can be caught and handled.
Potential responses to an OptimisticLockException are to present the conflict to the user or to
refresh or reload objects in a new transaction and then retrying the transaction.
With PHP promoting a share-nothing architecture, the time between showing an update form and actually modifying the entity can in the worst scenario be
as long as your applications session timeout. If changes happen to the entity in that time frame you want to know directly
when retrieving the entity that you will hit an optimistic locking exception:
You can always verify the version of an entity during a request either when calling `EntityManager#find()`:
[php]
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Or you can use `EntityManager#lock()` to find out:
[php]
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
++++ Important Implementation Notes
You can easily get the optimistic locking workflow wrong if you compare the wrong versions.
Say you have Alice and Bob accessing a hypothetical bank account:
* Alice reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request)
* Bob reads the headline of the blog post being "Foo", at optimistic lock version 1 (GET Request)
* Bob updates the headline to "Bar", upgrading the optimistic lock version to 2 (POST Request of a Form)
* Alice updates the headline to "Baz", ... (POST Request of a Form)
Now at the last stage of this scenario the blog post has to be read again from the database before
Alice's headline can be applied. At this point you will want to check if the blog post is still at version 1
(which it is not in this scenario).
Using optimistic locking correctly, you *have* to add the version as an additional hidden field
(or into the SESSION for more safety). Otherwise you cannot verify the version is still the one being originally read from
the database when Alice performed her GET request for the blog post. If this happens you might
see lost updates you wanted to prevent with Optimistic Locking.
See the example code, The form (GET Request):
[php]
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
And the change headline action (POST Request):
[php]
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
+++ Pessimistic Locking
Doctrine 2 supports Pessimistic Locking at the database level. No attempt is being made to implement pessimistic locking
inside Doctrine, rather vendor-specific and ANSI-SQL commands are used to acquire row-level locks. Every Entity can
be part of a pessimistic lock, there is no special metadata required to use this feature.
However for Pessimistic Locking to work you have to disable the Auto-Commit Mode of your Database and start a
transaction around your pessimistic lock use-case using the "Approach 2: Explicit Transaction Demarcation" described
above. Doctrine 2 will throw an Exception if you attempt to acquire an pessimistic lock and no transaction is running.
Doctrine 2 currently supports two pessimistic lock modes:
* Pessimistic Write (`Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE`), locks the underlying database rows for concurrent Read and Write Operations.
* Pessimistic Read (`Doctrine\DBAL\LockMode::PESSIMISTIC_READ`), locks other concurrent requests that attempt to update or lock rows in write mode.
You can use pessimistic locks in three different scenarios:
1. Using `EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`
2. Using `EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`
3. Using `Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)` or `Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`

View File

@ -1,417 +0,0 @@
Associations between entities are represented just like in regular object-oriented PHP, with references to other objects
or collections of objects. When it comes to persistence, it is important to understand three main things:
* The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side).
* If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself.
* Collection-valued persistent fields have to be instances of the `Doctrine\Common\Collections\Collection` interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details.
Changes to associations in your code are not synchronized to the database directly, but upon calling `EntityManager#flush()`.
To describe all the concepts of working with associations we introduce a specific set of example entities that show
all the different flavors of association management in Doctrine.
++ Association Example Entities
We will use a simple comment system with Users and Comments as entities to show examples of association management.
See the PHP docblocks of each association in the following example for information about its type and if its the owning or inverse side.
[php]
/** @Entity */
class User
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")}
* )
*/
private $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")}
* )
*/
private $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
*/
private $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
}
/** @Entity */
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
*/
private $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="authoredComments")
*/
private $author;
}
This two entities generate the following MySQL Schema (Foreign Key definitions omitted):
[sql]
CREATE TABLE User (
id VARCHAR(255) NOT NULL,
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, comment_id)
) ENGINE = InnoDB;
++ Establishing Associations
Establishing an association between two entities is straight-forward. Here are some examples for the unidirectional
relations of the `User`:
[php]
class User
{
// ...
public function getReadComments() {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
}
The interaction code would then look like in the following snippet (`$em` here is an instance of the EntityManager):
[php]
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
In the case of bi-directional associations you have to update the fields on both sides:
[php]
class User
{
// ..
public function getAuthoredComments() {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
return $this->favorites;
}
}
class Comment
{
// ...
public function getUserFavorites() {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
$this->author = $author;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist($newComment);
$em->flush();
Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle.
++ Removing Associations
Removing an association between two entities is similarly straight-forward. There are two strategies
to do so, by key and by element. Here are some examples:
[php]
// Remove by Elements
$user->getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->removeElement($ithComment);
$comment->setAuthor(null);
You need to call `$em->flush()` to make persist these changes in the database permanently.
Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently
simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does only
allows null values if `null` is set as default value. Otherwise setAddress(null) will fail for removing the association.
If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`.
This can also provide better encapsulation as it hides the internal meaning of not having an address.
When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array).
That is why the `remove` operation accepts an index/key. `removeElement` is a separate method
that has O(n) complexity using `array_search`, where n is the size of the map.
> **NOTE**
>
> Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary
> for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated.
> This knowledge can often be used to improve performance by avoiding the loading of the inverse collection.
You can also clear the contents of a whole collection using the `Collections::clear()` method. You
should be aware that using this method can lead to a straight and optimized database delete or update call
during the flush operation that is not aware of entities that have been re-added to the collection.
Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call
`$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue
two database calls.
++ Association Management Methods
It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained.
The following code shows updates to the previous User and Comment example that encapsulate much of
the association management code:
[php]
class User
{
//...
public function markCommentRead(Comment $comment) {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
$this->comments[] = $comment;
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
}
class Comment
{
// ..
public function addUserFavorite(User $user) {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
$this->userFavorites->removeElement($user);
}
}
You will notice that `addUserFavorite` and `removeUserFavorite` do not call `addFavorite` and `removeFavorite`,
thus the bidirectional association is strictly-speaking still incomplete. However if you would naively add the
`addFavorite` in `addUserFavorite`, you end up with an infinite loop, so more work is needed.
As you can see, proper bidirectional association management in plain OOP is a non-trivial task
and encapsulating all the details inside the classes can be challenging.
> **NOTE**
>
> If you want to make sure that your collections are perfectly encapsulated you should not return
> them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer
> for the entity cannot circumvent the logic you implement on your entity for association management. For example:
[php]
class User {
public function getReadComments() {
return $this->commentsRead->toArray();
}
}
This will however always initialize the collection, with all the performance penalties given the size. In
some scenarios of large collections it might even be a good idea to completely hide the read access behind
methods on the EntityRepository.
There is no single, best way for association management. It greatly depends on the requirements of your concrete
domain model as well as your preferences.
++ Synchronizing Bidirectional Collections
In the case of Many-To-Many associations you as the developer are responsible to keep the collections on the
owning and inverse side up in sync, when you apply changes to them. Doctrine can only guarantee a consistent
state for the hydration, not for your client code.
Using the User-Comment entities from above, a very simple example can show the possible caveats you can encounter:
[php]
$user->getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUserFavorites()->contains($user); // FALSE
There are to approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections, BUT never read from them in requests that changed
their state. In the next Request Doctrine hydrates the consistent collection state again.
2. Always keep the bidirectional collections in sync through association management methods. Reads of
the Collections directly after changes are consistent then.
++ Transitive persistence / Cascade Operations
Persisting, removing, detaching and merging individual entities can become pretty
cumbersome, especially when a larger object graph with collections is involved.
Therefore Doctrine 2 provides a mechanism for transitive persistence through
cascading of these operations. Each association to another entity or a collection
of entities can be configured to automatically cascade certain operations. By
default, no operations are cascaded.
The following cascade options exist:
* persist : Cascades persist operations to the associated entities.
* remove : Cascades remove operations to the associated entities.
* merge : Cascades merge operations to the associated entities.
* detach : Cascades detach operations to the associated entities.
* all : Cascades persist, remove, merge and detach operations to associated entities.
The following example is an extension to the User-Comment example of this chapter.
Suppose in our application a user is created whenever he writes his first comment.
In this case we would use the following code:
[php]
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment);
$em->flush();
Even if you *persist* a new User that contains our new Comment this code would fail
if you removed the call to `EntityManager#persist($myFirstComment)`. Doctrine 2 does
not cascade the persist operation to all nested entities that are new as well.
More complicated is the deletion of all a users comments when he is removed from the system:
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
Without the loop over all the authored comments Doctrine would use an UPDATE statement only
to set the foreign key to NULL and only the User would be deleted from the database
during the flush()-Operation.
To have Doctrine handle both cases automatically we can change the `User#commentsAuthored`
property to cascade both the "persist" and the "remove" operation.
[php]
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
Even though automatic cascading is convenient it should be used with care.
Do not blindly apply cascade=all to all associations as it will unnecessarily
degrade the performance of your application. For each cascade operation that gets
activated Doctrine also applies that operation to the association, be it
single or collection valued.
+++ Persistence by Reachability: Cascade Persist
There are additional semantics that apply to the Cascade Persist operation.
During each flush() operation Doctrine detects if there are new entities in any
collection and three possible cases can happen:
1. New entities in a collection marked as cascade persist will be directly persisted by Doctrine.
2. New entities in a collection not marked as cascade persist will produce an Exception and rollback the flush() operation.
3. Collections without new entities are skipped.
This concept is called Persistence by Reachability: New entities that are found on
already managed entities are automatically persisted as long as the association is defined
as cascade persist.

View File

@ -1,555 +0,0 @@
In this chapter we will help you understand the `EntityManager` and the `UnitOfWork`.
A Unit of Work is similar to an object-level transaction. A new Unit of Work is
implicitly started when an EntityManager is initially created or after
`EntityManager#flush()` has been invoked. A Unit of Work is committed
(and a new one started) by invoking `EntityManager#flush()`.
A Unit of Work can be manually closed by calling EntityManager#close(). Any
changes to objects within this Unit of Work that have not yet been persisted
are lost.
> **NOTE**
>
> It is very important to understand that only `EntityManager#flush()` ever causes
> write operations against the database to be executed. Any other methods such
> as `EntityManager#persist($entity)` or `EntityManager#remove($entity)` only
> notify the UnitOfWork to perform these operations during flush.
>
> Not calling `EntityManager#flush()` will lead to all changes during that request being lost.
++ Entities and the Identity Map
Entities are objects with identity. Their identity has a conceptual meaning inside your domain.
In a CMS application each article has a unique id. You can uniquely identify each article
by that id.
Take the following example, where you find an article with the headline "Hello World"
with the ID 1234:
[php]
$article = $entityManager->find('CMS\Article', 1234);
$article->setHeadline('Hello World dude!');
$article2 = $entityManager->find('CMS\Article', 1234);
echo $article2->getHeadline();
In this case the Article is accessed from the entity manager twice, but modified in between.
Doctrine 2 realizes this and will only ever give you access to one instance of the Article
with ID 1234, no matter how often do you retrieve it from the EntityManager and even no
matter what kind of Query method you are using (find, Repository Finder or DQL).
This is called "Identity Map" pattern, which means Doctrine keeps a map of each entity
and ids that have been retrieved per PHP request and keeps returning you the same instances.
In the previous example the echo prints "Hello World dude!" to the screen. You can
even verify that `$article` and `$article2` are indeed pointing to the same instance
by running the following code:
[php]
if ($article === $article2) {
echo "Yes we are the same!";
}
Sometimes you want to clear the identity map of an EntityManager to start over. We use
this regularly in our unit-tests to enforce loading objects from the database again
instead of serving them from the identity map. You can call `EntityManager#clear()`
to achieve this result.
++ Entity Object Graph Traversal
Although Doctrine allows for a complete separation of your domain model (Entity classes)
there will never be a situation where objects are "missing" when traversing associations.
You can walk all the associations inside your entity models as deep as you want.
Take the following example of a single `Article` entity fetched from newly opened EntityManager.
[php]
/** @Entity */
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string") */
private $headline;
/** @ManyToOne(targetEntity="User") */
private $author;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
public function __construct {
$this->comments = new ArrayCollection();
}
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
}
$article = $em->find('Article', 1);
This code only retrieves the `User` instance with id 1 executing a single SELECT statement
against the user table in the database. You can still access the associated properties author
and comments and the associated objects they contain.
This works by utilizing the lazy loading pattern. Instead of passing you back a real
Author instance and a collection of comments Doctrine will create proxy instances for you.
Only if you access these proxies for the first time they will go through the EntityManager
and load their state from the database.
This lazy-loading process happens behind the scenes, hidden from your code. See the following code:
[php]
$article = $em->find('Article', 1);
// accessing a method of the user instance triggers the lazy-load
echo "Author: " . $article->getAuthor()->getName() . "\n";
// Lazy Loading Proxies pass instanceof tests:
if ($article->getAuthor() instanceof User) {
// a User Proxy is a generated "UserProxy" class
}
// accessing the comments as an iterator triggers the lazy-load
// retrieving ALL the comments of this article from the database
// using a single SELECT statement
foreach ($article->getComments() AS $comment) {
echo $comment->getText() . "\n\n";
}
// Article::$comments passes instanceof tests for the Collection interface
// But it will NOT pass for the ArrayCollection interface
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
echo "This will always be true!";
}
A slice of the generated proxy classes code looks like the following piece of code. A real proxy
class override ALL public methods along the lines of the `getName()` method shown below:
[php]
class UserProxy extends User implements Proxy
{
private function _load()
{
// lazy loading code
}
public function getName()
{
$this->_load();
return parent::getName();
}
// .. other public methods of User
}
> **Warning**
>
> Traversing the object graph for parts that are lazy-loaded will easily trigger lots
> of SQL queries and will perform badly if used to heavily. Make sure to use DQL
> to fetch-join all the parts of the object-graph that you need as efficiently as possible.
++ Persisting entities
An entity can be made persistent by passing it to the `EntityManager#persist($entity)`
method. By applying the persist operation on some entity, that entity becomes MANAGED,
which means that its persistence is from now on managed by an EntityManager. As a
result the persistent state of such an entity will subsequently be properly
synchronized with the database when `EntityManager#flush()` is invoked.
> **CAUTION**
> Invoking the `persist` method on an entity does NOT cause an immediate SQL INSERT to be
> issued on the database. Doctrine applies a strategy called "transactional write-behind",
> which means that it will delay most SQL commands until `EntityManager#flush()` is
> invoked which will then issue all necessary SQL statements to synchronize your objects
> with the database in the most efficient way and a single, short transaction,
> taking care of maintaining referential integrity.
Example:
[php]
$user = new User;
$user->setName('Mr.Right');
$em->persist($user);
$em->flush();
> **CAUTION**
> Generated entity identifiers / primary keys are guaranteed to be available after the
> next successful flush operation that involves the entity in question.
> You can not rely on a generated identifier to be available directly after invoking `persist`.
> The inverse is also true. You can not rely on a generated identifier being not available
> after a failed flush operation.
The semantics of the persist operation, applied on an entity X, are as follows:
* If X is a new entity, it becomes managed. The entity X will be entered into the database as a result of the flush operation.
* If X is a preexisting managed entity, it is ignored by the persist operation. However, the persist operation is cascaded to entities referenced by X, if the relationships from X to these other entities are mapped with cascade=PERSIST or cascade=ALL (see "Transitive Persistence").
* If X is a removed entity, it becomes managed.
* If X is a detached entity, an exception will be thrown on flush.
++ Removing entities
An entity can be removed from persistent storage by passing it to the `EntityManager#remove($entity)` method. By applying the `remove` operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once `EntityManager#flush()` is invoked.
> **CAUTION**
> Just like `persist`, invoking `remove` on an entity does NOT cause an immediate SQL
> DELETE to be issued on the database. The entity will be deleted on the next invocation
> of `EntityManager#flush()` that involves that entity.
Example:
[php]
$em->remove($user);
$em->flush();
The semantics of the remove operation, applied to an entity X are as follows:
* If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
* If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
* If X is a detached entity, an InvalidArgumentException will be thrown.
* If X is a removed entity, it is ignored by the remove operation.
* A removed entity X will be removed from the database as a result of the flush operation.
After an entity has been removed its in-memory state is the same as before the removal, except for generated identifiers.
Removing an entity will also automatically delete any existing records in many-to-many
join tables that link this entity. The action taken depends on the value of the `@joinColumn`
mapping attribute "onDelete". Either Doctrine issues a dedicated `DELETE` statement
for records of each join table or it depends on the foreign key semantics of
onDelete="CASCADE".
Deleting an object with all its associated objects can be achieved in multiple
ways with very different performance impacts.
1. If an association is marked as `CASCADE=REMOVE` Doctrine 2 will fetch this
association. If its a Single association it will pass this entity to
´EntityManager#remove()`. If the association is a collection, Doctrine will loop over all
its elements and pass them to `EntityManager#remove()`. In both cases the
cascade remove semantics are applied recursively. For large object graphs
this removal strategy can be very costly.
2. Using a DQL `DELETE` statement allows you to delete multiple entities of a
type with a single command and without hydrating these entities. This
can be very efficient to delete large object graphs from the database.
3. Using foreign key semantics `onDelete="CASCADE"` can force the database
to remove all associated objects internally. This strategy is a bit
tricky to get right but can be very powerful and fast. You should be aware
however that using strategy 1 (`CASCADE=REMOVE`) completely by-passes
any foreign key `onDelete=CASCADE` option, because Doctrine will fetch and remove
all associated entities explicitly nevertheless.
++ Detaching entities
An entity is detached from an EntityManager and thus no longer managed by
invoking the `EntityManager#detach($entity)` method on it or by cascading
the detach operation to it. Changes made to the detached entity, if any
(including removal of the entity), will not be synchronized to the database
after the entity has been detached.
Doctrine will not hold on to any references to a detached entity.
Example:
[php]
$em->detach($entity);
The semantics of the detach operation, applied to an entity X are as follows:
* If X is a managed entity, the detach operation causes it to become detached. The detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X.
* If X is a new or detached entity, it is ignored by the detach operation.
* If X is a removed entity, the detach operation is cascaded to entities referenced by X, if the relationships from X to these other entities is mapped with cascade=DETACH or cascade=ALL (see "Transitive Persistence"). Entities which previously referenced X will continue to reference X.
There are several situations in which an entity is detached automatically without invoking the `detach` method:
* When `EntityManager#clear()` is invoked, all entities that are currently managed by the EntityManager instance become detached.
* When serializing an entity. The entity retrieved upon subsequent unserialization will be detached (This is the case for all entities that are serialized and stored in some cache, i.e. when using the Query Result Cache).
The `detach` operation is usually not as frequently needed and used as `persist` and `remove`.
++ Merging entities
Merging entities refers to the merging of (usually detached) entities into the
context of an EntityManager so that they become managed again. To merge the
state of an entity into an EntityManager use the `EntityManager#merge($entity)`
method. The state of the passed entity will be merged into a managed copy of
this entity and this copy will subsequently be returned.
Example:
[php]
$detachedEntity = unserialize($serializedEntity); // some detached entity
$entity = $em->merge($detachedEntity);
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
> **CAUTION**
> When you want to serialize/unserialize entities you have to make all entity properties
> protected, never private. The reason for this is, if you serialize a class that was a proxy
> instance before, the private variables won't be serialized and a PHP Notice is thrown.
The semantics of the merge operation, applied to an entity X, are as follows:
* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity.
* If X is a new entity instance, a new managed copy X' will be created and the state of X is copied onto this managed instance.
* If X is a removed entity instance, an InvalidArgumentException will be thrown.
* If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been mapped with the cascade element value MERGE or ALL (see "Transitive Persistence").
* For all entities Y referenced by relationships from X having the cascade element value
MERGE or ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
* If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y.
The `merge` operation will throw an `OptimisticLockException` if the entity
being merged uses optimistic locking through a version field and the versions
of the entity being merged and the managed copy don't match. This usually means
that the entity has been modified while being detached.
The `merge` operation is usually not as frequently needed and used as `persist`
and `remove`. The most common scenario for the `merge` operation is to reattach
entities to an EntityManager that come from some cache (and are therefore detached)
and you want to modify and persist such an entity.
> **CAUTION**
> If you need to perform multiple merges of entities that share certain subparts
> of their object-graphs and cascade merge, then you have to call `EntityManager#clear()` between the
> successive calls to `EntityManager#merge()`. Otherwise you might end up with
> multiple copies of the "same" object in the database, however with different ids.
> **NOTE**
> If you load some detached entities from a cache and you do not need to persist or
> delete them or otherwise make use of them without the need for persistence services
> there is no need to use `merge`. I.e. you can simply pass detached objects from a cache
> directly to the view.
++ Synchronization with the Database
The state of persistent entities is synchronized with the database on flush of an `EntityManager`
which commits the underlying `UnitOfWork`. The synchronization involves writing any updates to
persistent entities and their relationships to the database. Thereby bidirectional relationships
are persisted based on the references held by the owning side of the relationship as explained
in the Association Mapping chapter.
When `EntityManager#flush()` is called, Doctrine inspects all managed, new and removed entities
and will perform the following operations.
+++ Synchronizing New and Managed Entities
The flush operation applies to a managed entity with the following semantics:
* The entity itself is synchronized to the database using a SQL UPDATE statement, only if at least one persistent field has changed.
* No SQL updates are executed if the entity did not change.
The flush operation applies to a new entity with the following semantics:
* The entity itself is synchronized to the database using a SQL INSERT statement.
For all (initialized) relationships of the new or managed entity the following semantics apply to each
associated entity X:
* If X is new and persist operations are configured to cascade on the relationship,
X will be persisted.
* If X is new and no persist operations are configured to cascade on the relationship,
an exception will be thrown as this indicates a programming error.
* If X is removed and persist operations are configured to cascade on the relationship,
an exception will be thrown as this indicates a programming error (X would be re-persisted by the cascade).
* If X is detached and persist operations are configured to cascade on the relationship,
an exception will be thrown (This is semantically the same as passing X to persist()).
+++ Synchronizing Removed Entities
The flush operation applies to a removed entity by deleting its persistent state from the database.
No cascade options are relevant for removed entities on flush, the cascade remove option is already
executed during `EntityManager#remove($entity)`.
+++ The size of a Unit of Work
The size of a Unit of Work mainly refers to the number of managed entities at
a particular point in time.
+++ The cost of flushing
How costly a flush operation is, mainly depends on two factors:
* The size of the EntityManager's current UnitOfWork.
* The configured change tracking policies
You can get the size of a UnitOfWork as follows:
[php]
$uowSize = $em->getUnitOfWork()->size();
The size represents the number of managed entities in the Unit of Work. This
size affects the performance of flush() operations due to change tracking
(see "Change Tracking Policies") and, of course, memory consumption, so you
may want to check it from time to time during development.
> **CAUTION**
> Do not invoke `flush` after every change to an entity or every single invocation of
> persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the
> performance of your application. Instead, form units of work that operate on your objects
> and call `flush` when you are done. While serving a single HTTP request there should
> be usually no need for invoking `flush` more than 0-2 times.
+++ Direct access to a Unit of Work
You can get direct access to the Unit of Work by calling `EntityManager#getUnitOfWork()`.
This will return the UnitOfWork instance the EntityManager is currently using.
[php]
$uow = $em->getUnitOfWork();
> **NOTE**
> Directly manipulating a UnitOfWork is not recommended. When working directly with the
> UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read
> the API documentation.
+++ Entity State
As outlined in the architecture overview an entity can be in one of four possible states:
NEW, MANAGED, REMOVED, DETACHED. If you explicitly need to find out what the current state
of an entity is in the context of a certain `EntityManager` you can ask the underlying
`UnitOfWork`:
[php]
switch ($em->getUnitOfWork()->getEntityState($entity)) {
case UnitOfWork::MANAGED:
...
case UnitOfWork::REMOVED:
...
case UnitOfWork::DETACHED:
...
case UnitOfWork::NEW:
...
}
An entity is in MANAGED state if it is associated with an `EntityManager` and it is not REMOVED.
An entity is in REMOVED state after it has been passed to `EntityManager#remove()` until the
next flush operation of the same EntityManager. A REMOVED entity is still associated with an
`EntityManager` until the next flush operation.
An entity is in DETACHED state if it has persistent state and identity but is currently not
associated with an `EntityManager`.
An entity is in NEW state if has no persistent state and identity and is not associated with an
`EntityManager` (for example those just created via the "new" operator).
++ Querying
Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs.
+++ By Primary Key
The most basic way to query for a persistent object is by its identifier / primary key using the `EntityManager#find($entityName, $id)` method. Here is an example:
[php]
// $em instanceof EntityManager
$user = $em->find('MyProject\Domain\User', $id);
The return value is either the found entity instance or null if no instance could be found with the given identifier.
Essentially, `EntityManager#find()` is just a shortcut for the following:
[php]
// $em instanceof EntityManager
$user = $em->getRepository('MyProject\Domain\User')->find($id);
`EntityManager#getRepository($entityName)` returns a repository object which provides many ways to retrieve entities of the specified type. By default, the repository instance is of type `Doctrine\ORM\EntityRepository`. You can also use custom repository classes as shown later.
+++ By Simple Conditions
To query for one or more entities based on several conditions that form a logical conjunction, use the `findBy` and `findOneBy` methods on a repository as follows:
[php]
// $em instanceof EntityManager
// All users that are 20 years old
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
// All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
An EntityRepository also provides a mechanism for more concise calls through its use of `__call`. Thus, the following two examples are equivalent:
[php]
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
+++ By Eager Loading
Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application.
+++ By Lazy Loading
Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading.
+++ By DQL
The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations.
DQL is syntactically very similar to the familiar SQL but *it is not SQL*.
A DQL query is represented by an instance of the `Doctrine\ORM\Query` class. You create a query using `EntityManager#createQuery($dql)`. Here is a simple example:
[php]
// $em instanceof EntityManager
// All users with an age between 20 and 30 (inclusive).
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
$users = $q->getResult();
Note that this query contains no knowledge about the relational schema, only about the object model. DQL supports positional as well as named parameters, many functions, (fetch) joins, aggregates, subqueries and much more. Detailed information about DQL and its syntax as well as the Doctrine\ORM\Query class can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language). For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special `Doctrine\ORM\QueryBuilder` class. More information on constructing queries with a QueryBuilder can be found [in the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/query-builder).
+++ By Native Queries
As an alternative to DQL or as a fallback for special SQL statements native queries can be used.
Native queries are built by using a hand-crafted SQL query and a ResultSetMapping that describes
how the SQL result set should be transformed by Doctrine. More information about native queries
can be found in [the dedicated chapter](http://www.doctrine-project.org/documentation/manual/2_0/en/native-sql).
+++ Custom Repositories
By default the EntityManager returns a default implementation of `Doctrine\ORM\EntityRepository` when
you call `EntityManager#getRepository($entityClass)`. You can overwrite this behaviour by specifying
the class name of your own Entity Repository in the Annotation, XML or YAML metadata.
In large applications that require lots of specialized DQL queries using a custom repository is
one recommended way of grouping these queries in a central location.
[php]
namespace MyDomain\Model;
use Doctrine\ORM\EntityRepository;
/**
* @entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
}
}
You can access your repository now by calling:
[php]
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

View File

@ -1,509 +0,0 @@
The XML mapping driver enables you to provide the ORM metadata in form of XML documents.
The XML driver is backed by an XML Schema document that describes the structure of a mapping document. The most recent version of the XML Schema document is available online at [http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd](http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd). In order to point to the latest version of the document of a particular stable release branch, just append the release number, i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with XML mapping files is to use an IDE/editor that can provide code-completion based on such an XML Schema document. The following is an outline of a XML mapping document with the proper xmlns/xsi setup for the latest code in trunk.
[xml]
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
...
</doctrine-mapping>
The XML mapping document of a class is loaded on-demand the first time it is requested and subsequently stored in the metadata cache. In order to work, this requires certain conventions:
* Each entity/mapped superclass must get its own dedicated XML mapping document.
* The name of the mapping document must consist of the fully qualified name of the class, where namespace
separators are replaced by dots (.). For example an Entity with the fully qualified class-name "MyProject\Entities\User"
would require a mapping file "MyProject.Entities.User.dcm.xml" unless the extension is changed.
* All mapping documents should get the extension ".dcm.xml" to identify it as a Doctrine mapping file. This is more of
a convention and you are not forced to do this. You can change the file extension easily enough.
-
[php]
$driver->setFileExtension('.xml');
It is recommended to put all XML mapping documents in a single folder but you can spread the documents over several folders if you want to. In order to tell the XmlDriver where to look for your mapping documents, supply an array of paths as the first argument of the constructor, like this:
[php]
$config = new \Doctrine\ORM\Configuration();
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
$config->setMetadataDriverImpl($driver);
++ Example
As a quick start, here is a small example document that makes use of several common elements:
[xml]
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
</indexes>
<unique-constraints>
<unique-constraint columns="name,user_email" name="search_idx" />
</unique-constraints>
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersistToo"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
</id>
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
<one-to-one field="address" target-entity="Address" inversed-by="user">
<cascade><cascade-remove /></cascade>
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
</one-to-one>
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user">
<cascade>
<cascade-persist/>
</cascade>
<order-by>
<order-by-field name="number" direction="ASC" />
</order-by>
</one-to-many>
<many-to-many field="groups" target-entity="Group">
<cascade>
<cascade-all/>
</cascade>
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id" nullable="false" unique="false" />
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id" column-definition="INT NULL" />
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
</doctrine-mapping>
Be aware that class-names specified in the XML files should be fully qualified.
++ XML-Element Reference
The XML-Element reference explains all the tags and attributes that the Doctrine Mapping XSD Schema defines.
You should read the Basic-, Association- and Inheritance Mapping chapters to understand what each of this
definitions means in detail.
+++ Defining an Entity
Each XML Mapping File contains the definition of one entity, specified as the `<entity />` element
as a direct child of the `<doctrine-mapping />` element:
[xml]
<doctrine-mapping>
<entity name="MyProject\User" table="cms_users" repository-class="MyProject\UserRepository">
<!-- definition here -->
</entity>
</doctrine-mapping>
Required attributes:
* name - The fully qualified class-name of the entity.
Optional attributes:
* table - The Table-Name to be used for this entity. Otherwise the Unqualified Class-Name is used by default.
* repository-class - The fully qualified class-name of an alternative `Doctrine\ORM\EntityRepository` implementation to be used with this entity.
* inheritance-type - The type of inheritance, defaults to none. A more detailed description follows in the *Defining Inheritance Mappings* section.
+++ Defining Fields
Each entity class can contain zero to infinite fields that are managed by Doctrine. You can define
them using the `<field />` element as a children to the `<entity />` element. The field element is only
used for primitive types that are not the ID of the entity. For the ID mapping you have to use the `<id />` element.
[xml]
<entity name="MyProject\User">
<field name="name" type="string" length="50" />
<field name="username" type="string" unique="true" />
<field name="age" type="integer" nullable="true" />
<field name="isActive" column="is_active" type="boolean" />
<field name="weight" type="decimal" scale="5" precision="2" />
</entity>
Required attributes:
* name - The name of the Property/Field on the given Entity PHP class.
Optional attributes:
* type - The `Doctrine\DBAL\Types\Type` name, defaults to "string"
* column - Name of the column in the database, defaults to the field name.
* length - The length of the given type, for use with strings only.
* unique - Should this field contain a unique value across the table? Defaults to false.
* nullable - Should this field allow NULL as a value? Defaults to false.
* version - Should this field be used for optimistic locking? Only works on fields with type integer or datetime.
* scale - Scale of a decimal type.
* precision - Precision of a decimal type.
* column-definition - Optional alternative SQL representation for this column. This definition begin after the
field-name and has to specify the complete column definition. Using this feature will turn this field dirty
for Schema-Tool update commands at all times.
+++ Defining Identity and Generator Strategies
An entity has to have at least one `<id />` element. For composite keys you can specify more than one id-element,
however surrogate keys are recommended for use with Doctrine 2. The Id field allows to define properties of
the identifier and allows a subset of the `<field />` element attributes:
[xml]
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id" />
</entity>
Required attributes:
* name - The name of the Property/Field on the given Entity PHP class.
* type - The `Doctrine\DBAL\Types\Type` name, preferably "string" or "integer".
Optional attributes:
* column - Name of the column in the database, defaults to the field name.
Using the simplified definition above Doctrine will use no identifier strategy for this entity. That means
you have to manually set the identifier before calling `EntityManager#persist($entity)`. This is the
so called `ASSIGNED` strategy.
If you want to switch the identifier generation strategy you have to nest a `<generator />` element inside
the id-element. This of course only works for surrogate keys. For composite keys you always have to use
the `ASSIGNED` strategy.
[xml]
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id">
<generator strategy="AUTO" />
</id>
</entity>
The following values are allowed for the `<generator />` strategy attribute:
* AUTO - Automatic detection of the identifier strategy based on the preferred solution of the database vendor.
* IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs available to Doctrine AFTER the INSERT statement has been executed.
* SEQUENCE - Use of a database sequence to retrieve the entity-ids. This is possible before the INSERT statement is executed.
If you are using the SEQUENCE strategy you can define an additional element to describe the sequence:
[xml]
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id">
<generator strategy="SEQUENCE" />
<sequence-generator sequence-name="user_seq" allocation-size="5" initial-value="1" />
</id>
</entity>
Required attributes for `<sequence-generator />`:
* sequence-name - The name of the sequence
Optional attributes for `<sequence-generator />`:
* allocation-size - By how much steps should the sequence be incremented when a value is retrieved. Defaults to 1
* initial-value - What should the initial value of the sequence be.
> **NOTE**
>
> If you want to implement a cross-vendor compatible application you have to specify <generator strategy="AUTO" /> and
> additionally define the <sequence-generator /> element, if Doctrine chooses the sequence strategy for a platform.
+++ Defining a Mapped Superclass
Sometimes you want to define a class that multiple entities inherit from, which itself is not an entity however.
The chapter on *Inheritance Mapping* describes a Mapped Superclass in detail. You can define it in XML using
the `<mapped-superclass />` tag.
[xml]
<doctrine-mapping>
<mapped-superclass name="MyProject\BaseClass">
<field name="created" type="datetime" />
<field name="updated" type="datetime" />
</mapped-superclass>
</doctrine-mapping>
Required attributes:
* name - Class name of the mapped superclass.
You can nest any number of `<field />` and unidirectional `<many-to-one />` or `<one-to-one />` associations inside
a mapped superclass.
+++ Defining Inheritance Mappings
There are currently two inheritance persistence strategies that you can choose from when defining entities that
inherit from each other. Single Table inheritance saves the fields of the complete inheritance hierarchy in a single table,
joined table inheritance creates a table for each entity combining the fields using join conditions.
You can specify the inheritance type in the `<entity />` element and then use the `<discriminator-column />` and
`<discriminator-mapping />` attributes.
[xml]
<entity name="MyProject\Animal" inheritance-type="JOINED">
<discriminator-column name="discr" type="string" />
<discriminator-map>
<discriminator-mapping value="cat" class="MyProject\Cat" />
<discriminator-mapping value="dog" class="MyProject\Dog" />
<discriminator-mapping value="mouse" class="MyProject\Mouse" />
</discriminator-map>
</entity>
The allowed values for inheritance-type attribute are `JOINED` or `SINGLE_TABLE`.
> **NOTE**
>
> All inheritance related definitions have to be defined on the root entity of the hierarchy.
+++ Defining Lifecycle Callbacks
You can define the lifecycle callback methods on your entities using the `<lifecycle-callbacks />` element:
[xml]
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="onPrePersist" />
</lifecycle-callbacks>
</entity>
+++ Defining One-To-One Relations
You can define One-To-One Relations/Associations using the `<one-to-one />` element. The required
and optional attributes depend on the associations being on the inverse or owning side.
For the inverse side the mapping is as simple as:
[xml]
<entity class="MyProject\User">
<one-to-one field="address" target-entity="Address" mapped-by="user" />
</entity>
Required attributes for inverse One-To-One:
* field - Name of the property/field on the entity's PHP class.
* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash!
* mapped-by - Name of the field on the owning side (here Address entity) that contains the owning side association.
For the owning side this mapping would look like:
[xml]
<entity class="MyProject\Address">
<one-to-one field="user" target-entity="User" inversed-by="address" />
</entity>
Required attributes for owning One-to-One:
* field - Name of the property/field on the entity's PHP class.
* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash!
Optional attributes for owning One-to-One:
* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference.
* orphan-removal - If true, the inverse side entity is always deleted when the owning side entity is. Defaults to false.
* fetch - Either LAZY or FETCH, defaults to LAZY. This attribute makes only sense on the owning side, the inverse side *ALWAYS* has to use the `FETCH` strategy.
The definition for the owning side relies on a bunch of mapping defaults for the join column names.
Without the nested `<join-column />` element Doctrine assumes to foreign key to be called `user_id` on the Address
Entities table. This is because the `MyProject\Address` entity is the owning side of this association, which means
it contains the foreign key.
The completed explicitly defined mapping is:
[xml]
<entity class="MyProject\Address">
<one-to-one field="user" target-entity="User" inversed-by="address">
<join-column name="user_id" referenced-column-name="id" />
</one-to-one>
</entity>
+++ Defining Many-To-One Associations
The many-to-one association is *ALWAYS* the owning side of any bidirectional association. This simplifies the mapping
compared to the one-to-one case. The minimal mapping for this association looks like:
[xml]
<entity class="MyProject\Article">
<many-to-one field="author" target-entity="User" />
</entity>
Required attributes:
* field - Name of the property/field on the entity's PHP class.
* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference.
* orphan-removal - If true the entity on the inverse side is always deleted when the owning side entity is and it is not connected to any other owning side entity anymore. Defaults to false.
* fetch - Either LAZY or FETCH, defaults to LAZY.
This definition relies on a bunch of mapping defaults with regards to the naming of the join-column/foreign key. The
explicitly defined mapping includes a `<join-column />` tag nested inside the many-to-one association tag:
[xml]
<entity class="MyProject\Article">
<many-to-one field="author" target-entity="User">
<join-column name="author_id" referenced-column-name="id" />
</many-to-one>
</entity>
The join-column attribute `name` specifies the column name of the foreign key and
the `referenced-column-name` attribute specifies the name of the primary key column
on the User entity.
+++ Defining One-To-Many Associations
The one-to-many association is *ALWAYS* the inverse side of any association. There exists no such thing as a
uni-directional one-to-many association, which means this association only ever exists for bi-directional associations.
[xml]
<entity class="MyProject\User">
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user" />
</entity>
Required attributes:
* field - Name of the property/field on the entity's PHP class.
* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash!
* mapped-by - Name of the field on the owning side (here Phonenumber entity) that contains the owning side association.
Optional attributes:
* fetch - Either LAZY or FETCH, defaults to LAZY.
+++ Defining Many-To-Many Associations
From all the associations the many-to-many has the most complex definition. When you rely on the mapping defaults
you can omit many definitions and rely on their implicit values.
[xml]
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group" />
</entity>
Required attributes:
* field - Name of the property/field on the entity's PHP class.
* target-entity - Name of the entity associated entity class. If this is not qualified the namespace of the current class is prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
* mapped-by - Name of the field on the owning side that contains the owning side association if the defined many-to-many association is on the inverse side.
* inversed-by - If the association is bidirectional the inversed-by attribute has to be specified with the name of the field on the inverse entity that contains the back-reference.
* fetch - Either LAZY or FETCH, defaults to LAZY.
The mapping defaults would lead to a join-table with the name "User_Group" being created that contains two columns
"user_id" and "group_id". The explicit definition of this mapping would be:
[xml]
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id"/>
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id"/>
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
Here both the `<join-columns>` and `<inverse-join-columns>` tags are necessary to tell Doctrine for which side the
specified join-columns apply. These are nested inside a `<join-table />` attribute which allows to specify
the table name of the many-to-many join-table.
+++ Cascade Element
Doctrine allows cascading of several UnitOfWork operations to related entities. You can specify the cascade
operations in the `<cascade />` element inside any of the association mapping tags.
[xml]
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<cascade>
<cascade-all/>
</cascade>
</many-to-many>
</entity>
Besides `<cascade-all />` the following operations can be specified by their respective tags:
* `<cascade-persist />`
* `<cascade-merge />`
* `<cascade-remove />`
* `<cascade-refresh />`
+++ Join Column Element
In any explicitly defined association mapping you will need the `<join-column />` tag. It defines how the
foreign key and primary key names are called that are used for joining two entities.
Required attributes:
* name - The column name of the foreign key.
* referenced-column-name - The column name of the associated entities primary key
Optional attributes:
* unique - If the join column should contain a UNIQUE constraint. This makes sense for Many-To-Many join-columns only to simulate a one-to-many unidirectional using a join-table.
* nullable - should the join column be nullable, defaults to true.
* on-delete - Foreign Key Cascade action to perform when entity is deleted, defaults to NO ACTION/RESTRICT but can be set to "CASCADE".
+++ Defining Order of To-Many Associations
You can require one-to-many or many-to-many associations to be retrieved using an additional `ORDER BY`.
[xml]
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<order-by>
<order-by-field name="name" direction="ASC" />
</order-by>
</many-to-many>
</entity>
+++ Defining Indexes or Unique Constraints
To define additional indexes or unique constraints on the entities table you can use the
`<indexes />` and `<unique-constraints />` elements:
[xml]
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
</indexes>
<unique-constraints>
<unique-constraint columns="name,user_email" name="search_idx" />
</unique-constraints>
</entity>
You have to specify the column and not the entity-class field names in the index and unique-constraint
definitions.