Merge branch 'master' of github.com:doctrine/orm-documentation
This commit is contained in:
commit
dd6eafbb6c
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user