1
0
mirror of synced 2025-01-18 22:41:43 +03:00

Merge branch 'master' of github.com:doctrine/orm-documentation

This commit is contained in:
Benjamin Eberlei 2010-05-12 23:52:38 +02:00
commit 150bca0498
9 changed files with 572 additions and 164 deletions

View File

@ -0,0 +1,208 @@
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
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:
* 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 paneltype? 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 entites. 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
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 frontend 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 frontend 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.
*
* @author Christian Heinrich
* @version $Id: BlockStrategyInterface.php 657 2010-03-16 00:08:50Z cheinrich $
*/
interface BlockStrategyInterface {
/**
* This could configure your entity
*/
public function setConfig(Config\EntityConfig $config);
/**
* Returns the config this strategy is configured with.
* @return Core\Model\Config\EntityConfig
*/
public function getConfig();
/**
* 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.
*
* @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.
*
* Most of the time, this method will return / output a simple form which in turn
* calls some controllers.
*
* @return string
*/
public function renderBackend();
/**
* Returns all possible types of panels this block can be stacked onto
*
* @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:
[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
* @var integer
*/
private $id;
// Add code for relation to the parent panel, configuration objects, ....
/**
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
*
* @var string
*/
protected $strategyClassName;
/**
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2
* @var BlockStrategyInterface
*/
protected $strategyInstance;
/**
* Returns the strategy that is used for this blockitem.
*
* The strategy itself defines how this block can be rendered etc.
*
* @return string
*/
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
* @return BlockStrategyInterface
*/
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!
*
* @param BlockStrategyInterface $strategy
*/
public function setStrategy(BlockStrategyInterface $strategy) {
$this->strategyInstance = $strategy;
$this->strategyClassName = get_class($strategy);
$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!
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]
use \Doctrine\ORM,
\Doctrine\Common;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*
* @author Christian Heinrich
*/
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();
$strategyInstance = new $strategy();
if (null !== $blockItem->getConfig()) {
$strategyInstance->setConfig($blockItem->getConfig());
}
$strategyInstance->setView($this->view);
$blockItem->setStrategy($strategyInstance);
}
}
}
In this example, even some variables are set - like a view object or a specific configuration object.

View File

@ -215,24 +215,30 @@ A common alternative strategy is to use a generated value as the identifier. To
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. For MySql, for example, Doctrine would use the `IDENTITY` strategy which means a typical AUTO_INCREMENT column. For PostgreSql it would choose to use the `SEQUENCE` strategy which would result in using a database sequence.
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.
+++ Id Generation Strategies
+++ Identifier Generation Strategies
The previous example showed how to use the default Id generation strategy without knowing the underlying database with the AUTO-detection strategy.
It is also possible to specifiy the Id Generation strategy more explicitly, which allows to make use of some additional features.
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 specifiy 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. This strategy provides full portability.
* `NONE`: Tells Doctrine that you generated the entities primary key value in userland before `EntityManager#persist()` is called.
* `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 usually generate a value on insertion of a row (i.e. MySql AUTO_INCREMENT). This strategy does currently not provide full portability. IDENTITY is supported by MySql, Sqlite and MsSql.
* `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 be used in conjunction with Oracle or Postgres Platforms and allows some additional configuration options besides
The Sequence Generator can currently be used in conjunction with Oracle or Postgres and allows some additional configuration options besides
specifiying the sequence's name:
[php]
@ -247,17 +253,25 @@ specifiying the sequence's name:
The initial value specifies at which value the sequence should start.
Allocation Size is a powerful feature to optimize INSERT performance of Doctrine. The allocation size specifies
by how much values the sequence is incremented whenever the next value is retrieved. If this is larger than one
Doctrine 2 can generate Id values for the allocationSizes amount of entities. In the above example with
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.
> **CAUTION**
*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.
> Allocation Size is detected by SchemaTool and transformed into an "INCREMENT BY <allocationSize>" clause
> in the CREATE SEQUENCE statement. For a database schema created by you (and not SchemaTool) you have to
> make sure that the allocationSize configuration option is never larger than the actual sequences INCREMENT BY value.
+++ Composite Keys

View File

@ -120,7 +120,7 @@ The annotation driver can be configured with a factory method on the `Doctrine\O
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is requied for the annotation driver, because otherwise
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.
+++ Metadata Cache (***RECOMMENDED***)

View File

@ -1,13 +1,40 @@
The Doctrine 2 database layer can be used independently of the object-relational mapping. It offers a leightweight abstraction layer around a PDO like API and allows optional access to lots of convenience functionality aswell as the ability to generate platform independent DQL and DDL statements.
The Doctrine database abstraction & access layer (DBAL) offers a leightweight and thin runtime layer around a PDO-like API and a lot of additional, horizontal features like database schema introspection and manipulation through an OO API.
++ Configuration
The fact that the Doctrine DBAL abstracts the concrete PDO API away through the use of interfaces that closely resemble the existing PDO API
makes it possible to implement custom drivers that may use existing native or self-made APIs. For example, the DBAL ships with a driver for Oracle databases
that uses the oci8 extension under the hood.
You can create a Doctrine Connection by using the `Doctrine\DBAL\DriverManager` class.
++ Architecture
As already said, the DBAL is a thin layer on top of PDO. PDO itself is mainly defined in terms of 2 classes:
`PDO` and `PDOStatement`. The equivalent classes in the DBAL are `Doctrine\DBAL\Connection` and
`Doctrine\DBAL\Statement`. A `Doctrine\DBAL\Connection` wraps a `Doctrine\DBAL\Driver\Connection`
and a `Doctrine\DBAL\Statement` wraps a `Doctrine\DBAL\Driver\Statement`.
`Doctrine\DBAL\Driver\Connection` and `Doctrine\DBAL\Driver\Statement` are just interfaces.
These interfaces are implemented by concrete drivers. For all PDO based drivers, `PDO` and
`PDOStatement` are the implementations of these interfaces. Thus, for PDO-based drivers, a
`Doctrine\DBAL\Connection` wraps a `PDO` instance and a `Doctrine\DBAL\Statement` wraps a
`PDOStatement` instance. Even more, a `Doctrine\DBAL\Connection` *is a* `Doctrine\DBAL\Driver\Connection`
and a `Doctrine\DBAL\Statement` *is a* `Doctrine\DBAL\Driver\Statement`.
What does a `Doctrine\DBAL\Connection` or a `Doctrine\DBAL\Statement` add to the underlying
driver implementations? The enhancements include SQL logging, events and control over the
transaction isolation level in a portable manner, among others.
A DBAL driver is defined to the outside in terms of 3 interfaces: `Doctrine\DBAL\Driver`,
`Doctrine\DBAL\Driver\Connection` and `Doctrine\DBAL\Driver\Statement`.
The latter two resemble (a subset of) the corresponding PDO API.
A concrete driver implementation must provide implementation classes for these 3 interfaces.
++ Getting a Connection
You can get a DBAL Connection through the `Doctrine\DBAL\DriverManager` class.
[php]
$config = new \Doctrine\DBAL\Configuration();
//..
$connectionParams = array(
'dbname' => 'mydb',
'user' => 'user',
@ -17,45 +44,89 @@ You can create a Doctrine Connection by using the `Doctrine\DBAL\DriverManager`
);
$conn = DriverManager::getConnection($connectionParams);
The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around any configured database driver, for example the PDO Mysql driver in the previous example.
The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around the underlying driver connection (which is often a PDO instance).
+++ Connection Options
The following sections describe the available connection parameters in detail.
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 following option keys can be specified
to create your connection:
++++ Driver Management Options:
+++ Driver
* driver - Allows to specify the default drivers shipped with Doctrine 2, 'pdo_mysql', 'pdo_sqlite', 'pdo_pgsql, and 'oci'.
* driverClass - If no 'driver' is specified this allows usage of a userland implementation of Doctrine\DBAL\Driver.
* pdo - If PDO is already instantiated for Mysql, SqLite or PgSQL this key can be used to pass this instance into Doctrine.
* wrapperClass - By default Doctrine\DBAL\Connection is wrapped around each driver, however this option allows to specify a userland sub-class.
The driver specifies the actual implementations of the DBAL interfaces to use. It can be configured in one of three ways:
++++ Driver Configuration Options:
* `driver`: The built-in driver implementation to use. The following drivers are currently available:
* `pdo_mysql`: A MySQL driver that uses the pdo_mysql PDO extension.
* `pdo_sqlite`: An SQLite driver that uses the pdo_sqlite PDO extension.
* `pdo_pgsql`: A PostgreSQL driver that uses the pdo_pgsql PDO extension.
* `pdo_oci`: An Oracle driver that uses the pdo_oci PDO extension. **Note that this driver caused problems in our tests. Prefer the oci8 driver if possible.**
* `oci8`:` An Oracle driver that uses the oci8 PHP extension.
* `driverClass`: Specifies a custom driver implementation if no 'driver' is specified. This allows the use of custom drivers that are not part of the Doctrine DBAL itself.
* `pdo`: Specifies an existing PDO instance to use.
Common Configuration Options across all database drivers:
* platform - An instance of `Doctrine\DBAL\Platforms\AbstractPlatform`. This is only required for userland implementations, each driver shipped with Doctrine 2 has a default platform.
* user - Username required to connect to the database.
* password - Password required to connect to the database.
* driverOptions - Array of options passed to the driver instance on calling to Driver::connect.
+++ Wrapper Class
Driver Configuration Options are different for each Database Driver, here are some of the db specific ones:
By default a `Doctrine\DBAL\Connection` is wrapped around a driver `Connection`.
The `wrapperClass` option allows to specify a custom wrapper implementation to use, however,
custom wrapper class must be a subclass of `Doctrine\DBAL\Connection`.
* host - Database host (Mysql, Pgsql, Oracle)
* port - Database port (Mysql, Pgsql, Oracle)
* dbname - Name of the database/schema to connect to. (Mysql, Pgsql, Oracle)
* unix_socket - Name of the socket used to connect to the database. (Mysql)
* charset - The charset used when connecting to the database. (Oracle)
* path - The filesystem path to the database (Sqlite)
* memory - True if the sqlite database should be in memory. (Sqlite)
+++ DBAL Events
+++ Connection Details
Both `Doctrine\DBAL\DriverManager` and `Doctrine\DBAL\Connection` accept an instance of `Doctrine\Common\EventManager`.
The EventManager has a couple of events inside the DBAL layer that are triggered for the user to listen to:
The connection details identify the database to connect to as well as the credentials to use.
The connection details can differ depending on the used driver. The following sections describe
the options recognized by each built-in driver.
> *NOTE*
> When using an existing PDO instance through the `pdo` option, specifying connection details is obviously not necessary.
++++ pdo_sqlite
* `user` (string): Username to use when connecting to the database.
* `password` (string): Password to use when connecting to the database.
* `path` (string): The filesystem path to the database file. Mutually exclusive with `memory`. `path` takes precedence.
* `memory` (boolean): True if the SQLite database should be in-memory (non-persistent). Mutually exclusive with `path`. `path` takes precedence.
++++ pdo_mysql
* `user` (string): Username to use when connecting to the database.
* `password` (string): Password to use when connecting to the database.
* `host` (string): Hostname of the database to connect to.
* `port` (integer): Port of the database to connect to.
* `dbname` (string): Name of the database/schema to connect to.
* `unix_socket` (string): Name of the socket used to connect to the database.
++++ pdo_pgsql
* `user` (string): Username to use when connecting to the database.
* `password` (string): Password to use when connecting to the database.
* `host` (string): Hostname of the database to connect to.
* `port` (integer): Port of the database to connect to.
* `dbname` (string): Name of the database/schema to connect to.
++++ pdo_oci / oci8
* `user` (string): Username to use when connecting to the database.
* `password` (string): Password to use when connecting to the database.
* `host` (string): Hostname of the database to connect to.
* `port` (integer): Port of the database to connect to.
* `dbname` (string): Name of the database/schema to connect to.
* `charset` (string): The charset used when connecting to the database.
+++ Custom Platform
Each built-in driver uses a default implementation of `Doctrine\DBAL\Platforms\AbstractPlatform`.
If you wish to use a customized or custom implementation, you can pass a precreated instance
in the `platform` option.
+++ Custom Driver Options
The `driverOptions` option allows to pass arbitrary options through to the driver.
This is equivalent to the 4th argument of the [PDO constructor](http://php.net/manual/en/pdo.construct.php).
+++ Events
++++ PostConnect Event
@ -77,6 +148,7 @@ You can register events by subscribing them to the `EventManager` instance passe
$conn = DriverManager::getConnection($connectionParams, null, $evm);
++ DBAL API
+++ DBAL Architecture
@ -91,7 +163,7 @@ The DBAL is seperated into several different packages that perfectly seperate re
+++ Data Retrieval and Manipulation
The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods:
The following methods exist for executing queries against your configured database, three very generic methods and some advanced retrievial methods:
* `prepare($sql)` - Prepare a given sql statement and return the `\Doctrine\DBAL\Driver\Statement` instance.
* `executeUpdate($sql, array $params)` - Executes a prepared statement with the given sql and parameters and returns the affected rows count.
@ -116,44 +188,105 @@ By default the Doctrine DBAL does no escaping. Escaping is a very tricky busines
+++ Transactions
Doctrine handles transactions with a PDO like API, having methods for `beginTransaction()`, `commit()` and `rollBack()`. For consistency across different drivers Doctrine also handles the nesting of transactions internally. You can call `beginTransaction()` more than once, and only a matching amount of calls to `commit()` triggers the commit to the database. The Doctrine connectionalso has a method to set the transaction isolation level of the connection as supported by the underlying database.
A `Doctrine\DBAL\Connection` provides a PDO-like API for transaction management, with the
methods `Connection#beginTransaction()`, `Connection#commit()` and `Connection#rollback()`.
Transaction demarcation with the Doctrine DBAL looks as follows:
[php]
class Connection
{
/**
* Constant for transaction isolation level READ UNCOMMITTED.
*/
const TRANSACTION_READ_UNCOMMITTED = 1;
/**
* Constant for transaction isolation level READ COMMITTED.
*/
const TRANSACTION_READ_COMMITTED = 2;
/**
* Constant for transaction isolation level REPEATABLE READ.
*/
const TRANSACTION_REPEATABLE_READ = 3;
/**
* Constant for transaction isolation level SERIALIZABLE.
*/
const TRANSACTION_SERIALIZABLE = 4;
}
A transaction with Doctrine DBAL might then look like:
[php]
$conn->setTransactionIsolationLevel(Connection::TRANSACTION_SERIALIZABLE);
$conn->beginTransaction();
try{
$conn->beginTransaction();
// do stuff
$conn->commit();
} catch(\Exception $e) {
$conn->rollback();
//handle or rethrow
}
The `Doctrine\DBAL\Connection` also has methods control the transaction isolation level as supported by the underlying database.
`Connection#setTransactionIsolation($level)` and Connection#getTransactionIsolation() can be used for that purpose.
The possible isolation levels are represented by the following constants:
[php]
Connection::TRANSACTION_READ_UNCOMMITTED
Connection::TRANSACTION_READ_COMMITTED
Connection::TRANSACTION_REPEATABLE_READ
Connection::TRANSACTION_SERIALIZABLE
The default transaction isolation level of a `Doctrine\DBAL\Connection` is chosen by the underlying
platform but it is always at least READ_COMMITTED.
+++ Transaction Nesting
A `Doctrine\DBAL\Connection` also adds support for nesting transactions, or rather propagating transaction control up the call stack.
For that purpose, the `Connection` class keeps an internal counter that represents the nesting level and is increased/decreased as
`beginTransaction()`, `commit()` and `rollback()` are invoked. `beginTransaction()` increases the nesting level whilst `commit()``
and `rollback()` decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1,
`beginTransaction()` is invoked on the underlying driver connection and whenever the nesting level transitions from 1 to 0,
`commit()` or `rollback()` is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()`
or `Connection#rollback()`.
What this means is that transaction control is basically passed to code higher up in the call stack and
the inner transaction block is ignored, with one important exception that is described further below.
Do not confuse this with "real" nested transactions or savepoints. These are not supported by Doctrine.
There is always only a single, real database transaction.
To visualize what this means in practice, consider the following example:
[php]
// $conn instanceof Doctrine\DBAL\Connection
$conn->beginTransaction(); // 0 => 1, "real" transaction started
try {
...
// nested transaction block, this might be in some other API/library code that is
// unaware of the outer transaction.
$conn->beginTransaction(); // 1 => 2
try {
...
$conn->commit(); // 2 => 1
} catch (Exception $e) {
$conn->rollback(); // 2 => 1, transaction marked for rollback only
throw $e;
}
...
$conn->commit(); // 1 => 0, "real" transaction committed
} catch (Exception $e) {
$conn->rollback(); // 1 => 0, "real" transaction rollback
throw $e;
}
However, **a rollback in a nested transaction block will always mark the current transaction so that the only possible outcome of the transaction is to be rolled back**.
That means in the above example, the rollback in the inner transaction block marks the whole transaction for rollback only.
Even if the nested transaction block would not rethrow the exception, the transaction is marked for rollback only and the commit of
the outer transaction would trigger an exception, leading to the final rollback.
This also means that you can not successfully commit some changes in an outer transaction if an inner transaction block fails and issues a rollback,
even if this would be the desired behavior (i.e. because the nested operation is "optional" for the purpose of the outer transaction block).
To achieve that, you need to restructure your application logic so as to avoid nesting transaction blocks. If this is not possible
because the nested transaction blocks are in a third-party API you're out of luck.
All that is guaruanteed to the inner transaction is that it still happens atomically, all or nothing, the transaction just gets a wider scope
and the control is handed to the outer scope.
> *CAUTION*
> The transaction nesting described here is a debated feature that has it's critics. Form your own opinion.
> We recommend avoiding nesting transaction blocks when possible, and most of the time, it is possible.
> Transaction control should mostly be left to a service layer and not be handled in data access objects or similar.
-
> **CAUTION**
> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the
> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in
> use bybasses the transparent transaction nesting that is provided by
> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors
> with broken transaction boundaries that may be hard to debug.
++ Schema Representation

View File

@ -469,7 +469,7 @@ The following methods exist on the `AbstractQuery` which both `Query` and `Nativ
++++ Parameters
Prepared Statements that use numerical or named wildcards require additional parameters to be executable
agains the database. To pass parameters to the query the following methods can be used:
against the database. To pass parameters to the query the following methods can be used:
* `AbstractQuery::setParameter($param, $value)` - Set the numerical or named wildcard to the given value.
* `AbstractQuery::setParameters(array $params)` - Set an array of parameter key-value pairs.
@ -516,7 +516,7 @@ are to be used in userland:
* Query::HINT_FORCE_PARTIAL_LOAD - Allows to hydrate objects although not all their columns are fetched. This query
hint can be used to handle memory consumption problems with large result-sets that contain char or binary data.
Doctrine has no way of implicitly reloaded this data. Partially loaded objects have to be passed to
Doctrine has no way of implicitly reloading this data. Partially loaded objects have to be passed to
`EntityManager::refresh()` if they are to be reloaded fully from the database.
* Query::HINT_REFRESH - This query is used internally by `EntityManager::refresh()` and can be used in userland aswell.
If you specify this hint and a query returns the data for an entity that is already managed by the UnitOfWork, the

View File

@ -146,7 +146,7 @@ The result would look like this:
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 an example assumes User has a unidirectional or bidirectional one-to-one association to a CmsAddress,
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]

View File

@ -72,6 +72,56 @@ This method is the responsable to build every piece of DQL. It takes 3 parameter
->add('where', 'u.id = ?1')
->add('orderBy', 'u.name ASC');
++++ 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. Binding parameters can simply be achieved as follows:
[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
$qb->add('select', 'u')
->add('from', 'User u')
->add('where', 'u.id = ?1')
->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:
[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
$qb->add('select', 'u')
->add('from', 'User u')
->add('where', 'u.id = :identifier')
->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.
If you've got several parameters to bind to your query, you can also use setParameters() instead of setParameter() with the following syntax:
[php]
// $qb instanceof QueryBuilder
// Query here...
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2', 'whatever' => 'your value for :whatever'));
Getting already bound parameters is easy - simply use the abovementioned syntax with "getParameter()" or "getParameters()":
[php]
// $qb instanceof QueryBuilder
// See example above
$params = qb->getParameters(array(1, 2, 'whatever'));
// Equivalent to
$param = array($qb->getParameter(1), $qb->getParameter(2), $qb->getParameter('whatever'));
Note: If you try to get a parameter that was not bound yet, getParameter() simply returns NULL.
++++ Expr\* classes
When you call `add()` with string, it internally evaluates to an instance of `Doctrine\ORM\Query\Expr\Expr\*` class.
@ -197,6 +247,8 @@ Here it is a complete list of supported helper methods available:
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)
public function in($x, $y); // Returns Expr\Func instance
// Example - $qb->expr()->notIn('u.id', '2')
@ -353,4 +405,4 @@ Here is a complete list of helper methods in `QueryBuilder`:
// Example - $qb->addOrderBy('u.firstName')
public function addOrderBy($sort, $order = null); // Default $order = 'ASC'
}
}

View File

@ -1,46 +1,17 @@
++ 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 have a negative effect on 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 that is immediately committed. Without any explicit transaction demarcation from your side, this quickly results in poor performance because transactions are not cheap and many small transactions degrade the performance of your application.
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, small transaction. This is a strategy called "transactional write-behind" that is frequently used in ORM solutions to increase efficiency.
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 you to take over and control transaction demarcation yourself, thereby "widening" the transaction boundaries. This is possible due to transparent nesting of transactions that is described in the following section.
However, Doctrine 2 also allows (and ecourages) you to take over and control transaction demarcation yourself.
++ Transaction Nesting
These are two ways to deal with transactions when using the Doctrine ORM and are now described in more detail.
Each `Doctrine\DBAL\Driver\Connection` instance is wrapped in a `Doctrine\DBAL\Connection` that adds support for transparent nesting of transactions. For that purpose, the Connection class keeps an internal counter that represents the nesting level and is increased/decreased as beginTransaction(), commit() and rollback() are invoked. beginTransaction() increases the nesting level whilst commit() and rollback() decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, beginTransaction() is invoked on the underlying driver and whenever the nesting level transitions from 1 to 0, commit() or rollback() is invoked on the underlying driver, depending on whether the transition was caused by `Connection#commit()` or `Connection#rollback()`.
+++ Approach 1: Implicitly
Lets visualize what that means in practice. It means that the first call to `Doctrine\DBAL\Connection#beginTransaction()` will increase the nesting level from 0 to 1 and invoke beginTransaction() on the underlying driver, effectively starting a "real" transaction by suspending auto-commit mode. Any subsequent, nested calls to `Doctrine\DBAL\Connection#beginTransaction()` would only increase the nesting level.
Here is an example to help visualize how this works:
[php]
// $conn instanceof Doctrine\DBAL\Connection
try {
$conn->beginTransaction(); // 0 => 1, "real" transaction started
...
try {
$conn->beginTransaction(); // 1 => 2
...
$conn->commit(); // 2 => 1
} catch (Exception $e) {
$conn->rollback(); // 2 => 1
throw $e;
}
...
$conn->commit(); // 1 => 0, "real" transaction committed
} catch (Exception $e) {
$conn->rollback(); // 1 => 0, "real" transaction rollback
throw $e;
}
What is the benefit of this? It allows reliable and transparent widening of transaction boundaries. Given the following code snippet, without any explicit transaction demarcation:
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
@ -49,50 +20,58 @@ What is the benefit of this? It allows reliable and transparent widening of tran
$em->persist($user);
$em->flush();
Inside `EntityManager#flush()` something like this happens:
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.
[php]
try {
$conn->beginTransaction(); // suspend auto-commit
... commit all changes to the database ...
$conn->commit();
} catch (Exception $e) {
$conn->rollback();
throw $e;
}
Since we do not do any custom transaction demarcation in the first snippet, `EntityManager#flush()` will begin and commit/rollback a "real" transaction. Now, if we want to widen the transaction boundaries, say, because we want to include some manual work with a `Doctrine\DBAL\Connection` in the same transaction, we can simply do this:
+++ 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
$conn = $em->getConnection();
$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
$conn->beginTransaction(); // suspend auto-commit
// Direct use of the Connection
$conn->insert(...);
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
$conn->commit();
$em->getConnection()->commit();
} catch (Exception $e) {
$conn->rollback();
// handle or rethrow
$em->getConnection()->rollback();
$em->close();
throw $e;
}
Now, our own code controls the "real" transaction and the transaction demarcation that happens inside `EntityManager#flush()` will merely affect the nesting level. When flush() returns, either by throwing an exception or regularly, the nesting level is the same as before the invocation of flush(), in this case 1, and thus our own $conn->commit() / $conn->rollback() affect the "real" transaction as expected, since we were the ones who started the transaction.
> **CAUTION**
> Directly invoking `PDO#beginTransaction()`, `PDO#commit()` or `PDO#rollback()` or the
> corresponding methods on the particular `Doctrine\DBAL\Driver\Connection` instance in
> use bybasses the transparent transaction nesting that is provided by
> `Doctrine\DBAL\Connection` and can therefore corrupt the nesting level, causing errors
> with broken transaction boundaries that may be hard to debug.
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.
++ 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. Note that when catching `Exception` you should generally rethrow 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 rethrow, 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 occured you should do that with a new `EntityManager`.
++ Optimistic Locking
@ -111,8 +90,7 @@ You designate a version field in an entity as follows. In this example we'll use
// ...
}
You could also just as easily use a datetime column and instead of incrementing an integer, a timestamp will be kept up to date.
Alternatively a datetime type can be used (which maps to an SQL timestamp or datetime):
[php]
class User
@ -121,4 +99,18 @@ You could also just as easily use a datetime column and instead of incrementing
/** @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.

View File

@ -35,9 +35,9 @@ 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
> 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 no need for invoking `flush` more than 0-2 times.
> be usually no need for invoking `flush` more than 0-2 times.
+++ Direct access to a Unit of Work
@ -49,7 +49,7 @@ This will return the UnitOfWork instance the EntityManager is currently using.
> **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
> UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read
> the API documentation.
++ Persisting entities
@ -78,15 +78,20 @@ Example:
> **CAUTION**
> Generated entity identifiers / primary keys are guaranteed to be available after the
> next invocation of `EntityManager#flush()` that involves the entity in question.
> YOU CAN NOT RELY ON A GENERATED IDENTIFIER TO BE AVAILABLE AFTER INVOKING `persist`!
> next successful flush operation that involves the entity in question.
> You can not reply 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 at or before transaction commit or as a result of the flush operation.
* 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 InvalidArgumentException will be thrown.
* If X is a detached entity, the behavior is undefined.
> **CAUTION**
> Do not pass detached entities to the persist operation.
++ Removing entities
@ -110,7 +115,7 @@ The semantics of the remove operation, applied to an entity X are as follows:
* 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 at or before transaction commit or as a result of the flush operation.
* A removed entity X will be removed from the database as a result of the flush operation.
++ Detaching entities
@ -161,7 +166,7 @@ Example:
> **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.
> 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:
@ -245,6 +250,10 @@ to do so, by key and by element. Here are some examples:
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 not allow null values and 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, where n is the size of the map.
Since Doctrine always only looks at the owning side of a bidirectional association, it is essentially not necessary 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.