1
0
mirror of synced 2024-12-15 23:56:02 +03:00
doctrine2/manual/en/dbal.txt
2010-05-11 15:26:35 +02:00

379 lines
21 KiB
Plaintext

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.
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.
++ 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',
'password' => 'secret',
'host' => 'locahlost',
'driver' => 'pdo_mysql',
);
$conn = DriverManager::getConnection($connectionParams);
The `DriverManager` returns an instance of `Doctrine\DBAL\Connection` which is a wrapper around the underlying driver connection (which is often a PDO instance).
The following sections describe the available connection parameters in detail.
+++ Driver
The driver specifies the actual implementations of the DBAL interfaces to use. It can be configured in one of three ways:
* `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.
+++ Wrapper Class
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`.
+++ Connection Details
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
`Doctrine\DBAL\Events::postConnect` is triggered right after the connection to the database is established. It allows
to specify any relevant connection specific options and gives access to the `Doctrine\DBAL\Connection` instance
that is responsible for the connection management via an instance of `Doctrine\DBAL\Event\ConnectionEventArgs`
event arguments instance.
Doctrine is already shipped with two implementations for the "PostConnect" event:
* `Doctrine\DBAL\Event\Listeners\OracleSessionInit` allows to specify any number of Oracle Session related enviroment variables that are set right after the connection is established.
* `Doctrine\DBAL\Event\Listeners\MysqlSessionInit` allows to specify the Charset and Collation of the Client Connection if these options are not configured correctly on the MySQL server side.
You can register events by subscribing them to the `EventManager` instance passed to the Connection factory:
[php]
$evm = new EventManager(),
$evm->addEventSubscriber(new MysqlSessionInit('UTF-8'));
$conn = DriverManager::getConnection($connectionParams, null, $evm);
++ DBAL API
+++ DBAL Architecture
The DBAL is seperated into several different packages that perfectly seperate responsibilities of the different RDBMS layers.
* **Drivers** abstract a PHP specific database API by enforcing two interfaces `\Doctrine\DBAL\Driver\Driver` and `\Doctrine\DBAL\Driver\Statement` which require exactly the same methods as PDO.
* **Platforms** abstract the generation of queries and which database features a platform supports. The `\Doctrine\DBAL\Platforms\AbstractPlatform` defines the common denominator of what a database platform has to publish to the userland, to be fully supportable by Doctrine. This includes the SchemaTool, Transaction Isolation and many other features. The Database platform for MySQL for example can be used by all 3 mysql extensions, PDO, Mysqli and ext/mysql.
* **Logging** holds the interface and some implementations for debugging of Doctrine SQL query execution during a request.
* **Schema** offers an API for each database platform to execute DDL statements against your platform or retrieve metadata about it. It also holds the Schema Abstraction Layer which is used by the different Schema Management facilities of Doctrine DBAL and ORM.
* **Types** offers an abstraction layer for the converting and generation of types between Databases and PHP.
+++ Data Retrieval and Manipulation
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.
* `execute($sql, array $params)` - Creates a prepared statement for the given sql and passes the parameters to the execute method, then returning the statement.
* `fetchAll($sql, array $params)` - Execute the query and fetch all results into an array.
* `fetchArray($sql, array $params)` - Numeric index retrieval of first result row of the given query.
* `fetchBoth($sql, array $params)` - Both numeric and assoc column name retrieval of the first result row.
* `fetchColumn($sql, array $params, $colnum)` - Retrieve only the given column of the first result row.
* `fetchRow($sql, array $params)` - Retrieve assoc row of the first result row.
* `select($sql, $limit, $offset)` - Modify the given query with a limit clause.
There are also convenience methods for data manipulation queries:
* `delete($tableName, array $identifier)` - Delete all rows of a table matching the given identifier, where keys are column names.
* `insert($tableName, array $data)` - Insert a row into the given table name using the key value pairs of data.
* `update($tableName, array $data, array $identifier)` - Update all rows for the matching key value identifiers with the given data.
By default the Doctrine DBAL does no escaping. Escaping is a very tricky business to do automagically, therefore there is none by default. The ORM internally escapes all your values, because it has lots of metadata available about the current context. When you use the Doctrine DBAL as standalone, you have to take care of this yourself. The following methods help you with it:
* `quote($input, $type=null)` - Quote a value
* `quoteIdentifier($identifier)`- Quote an identifier according to the platform details.
+++ Transactions
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]
$conn->beginTransaction();
try{
// 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
Doctrine has a very powerful abstraction of database schemas. It offers an object-oriented representation of a database schema with support for all the details of Tables, Sequences, Indexes and Foreign Keys. These Schema instances generate a representation that is equal for all the supported platforms. Internally this functionality is used by the ORM Schema Tool to offer you create, drop and update database schema methods from your Doctrine ORM Metadata model. Up to very specific functionality of your database system this allows you to generate SQL code that makes your Domain model work.
You will be pleased to hear, that Schema representation is completly decoupled from the Doctrine ORM though, that is you can also use it in any other project to implement database migrations or for SQL schema generation for any metadata model that your application has. You can easily generate a Schema, as a simple example shows:
[php]
$schema = new \Doctrine\DBAL\Schema\Schema();
$myTable = $schema->createTable("my_table");
$myTable->addColumn("id", "integer", array("unsigned" => true));
$myTable->addColumn("username", "string", array("length" => 32));
$myTable->setPrimaryKey(array("id"));
$myTable->addUniqueIndex(array("username"));
$schema->createSequence("my_table_seq");
$myForeign = $schema->createTable("my_foreign");
$myForeign->addColumn("id", "integer");
$myForeign->addColumn("user_id", "integer");
$myForeign->addForeignKeyConstraint($myTable, array("user_id"), array("id"), array("onUpdate" => "CASCADE"));
$queries = $schema->toSql($myPlatform); // get queries to create this schema.
$dropSchema = $schema->toDropSql($myPlatform); // get queries to safely delete this schema.
Now if you want to compare this schema with another schema, you can use the `Comparator` class to get instances of `SchemaDiff`, `TableDiff` and `ColumnDiff`, aswell as information about other foreign key, sequence and index changes.
[php]
$comparator = new \Doctrine\DBAL\Schema\Comparator();
$schemaDiff = $comparator->compare($fromSchema, $toSchema);
$queries = $schemaDiff->toSql($myPlatform); // queries to get from one to another schema.
$saveQueries = $schemaDiff->toSaveSql($myPlatform);
The Save Diff mode is a specific mode that prevents the deletion of tables and sequences that might occour when making a diff of your schema. This is often necessary when your target schema is not complete but only describes a subset of your application.
All methods that generate SQL queries for you make much effort to get the order of generation correct, so that no problems will ever occour with missing links of foreign keys.
++ Platforms
Platforms abstract query generation and specifics of the RDBMS featuresets. In most cases you don't need to interact with this package alot, but there might be certain cases when you are programming database independent where you want to access the platform to generate queries for you.
The platform can be accessed from any `Doctrine\DBAL\Connection` instance by calling the `getDatabasePlatform()` method.
You can use your own platform by specifying the 'platform' key with an instance of your own platform:
[php]
$myPlatform = new MyPlatform();
$options = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite',
'platform' => $myPlatform
);
This way you can optimize your schema or generated SQL code with features that might not be portable for instance,
however are required for your special needs.
++ Schema Manager
A Schema Manager instance helps you with the abstraction of the generation of SQL assets such as Tables, Sequences, Foreign Keys and Indexes. You can use any of the Schema Asset classes `Table`, `Sequence`, `ForeignKeyConstraint` and `Index` for use with the methods of the style `dropAndCreate(AssetName)($asset)`, `drop(AssetName)($asset)` and `create(AssetName)($asset)`.
You also have methods to retrieve instances of those types from the current database you are connected to. These methods are:
* `listDatabases()`
* `listSequences()`
* `listTableColumns($tableName)`
* `listTableDetails($tableName)`
* `listTableForeignKeys($tableName)`
* `listTableIndexes($tableName)`
* `listTables()`
* `listUsers()`
* `listViews()`
For a complete representation of the current database you can use the `createSchema()` method which returns an instance of Schema, which you can use in conjunction with the SchemaTool or Schema Comparator.
++ Supporting other Databases
To support a database which is not currently shipped with Doctrine you have to implement the following interfaces and abstract classes:
* `\Doctrine\DBAL\Driver\Driver`
* `\Doctrine\DBAL\Driver\Statement`
* `\Doctrine\DBAL\Platforms\AbstractPlatform`
* `\Doctrine\DBAL\Schema\AbstractSchemaManager`
For an already supported platform but unsupported driver you only need to implement the first two interfaces, since the SQL Generation and Schema Management is already supported by the respective platform and schema instances. You can also make use of several Abstract Unittests in the `\Doctrine\Tests\DBAL` package to check if your platform behaves like all the others which is necessary for SchemaTool support, namely:
* `\Doctrine\Tests\DBAL\Platforms\AbstractPlatformTestCase`
* `\Doctrine\Tests\DBAL\Functional\Schema\AbstractSchemaManagerTestCase`
We would be very happy if any support for new databases would be contributed back to Doctrine to make it an even better product.