doctrine/dbal
Doctrine DBAL is a powerful database abstraction layer for PHP, providing a consistent API across drivers plus rich schema introspection and management tools. Ideal for building portable SQL queries, migrations, and database tooling beyond PDO.
Install via Composer:
composer require doctrine/dbal
First Use Case: Database Connection
use Doctrine\DBAL\DriverManager;
// Basic connection configuration
$connectionParams = [
'dbname' => env('DB_DATABASE'),
'user' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'host' => env('DB_HOST'),
'driver' => 'pdo_mysql', // or 'pdo_pgsql', 'pdo_sqlite', etc.
];
$conn = DriverManager::getConnection($connectionParams);
// Execute a simple query
$stmt = $conn->executeQuery('SELECT * FROM users LIMIT 10');
$users = $stmt->fetchAllAssociative();
Where to Look First:
DriverManager – The entry point for creating connections.Connection Interface – Core methods like executeQuery(), executeStatement(), and schema management.QueryBuilder – For building complex queries programmatically.// Fetch all rows
$stmt = $conn->executeQuery('SELECT * FROM products');
$products = $stmt->fetchAllAssociative();
// Fetch a single row
$product = $conn->fetchAssociative('SELECT * FROM products WHERE id = ?', [1]);
$stmt = $conn->prepare('INSERT INTO orders (user_id, amount) VALUES (?, ?)');
$stmt->bindValue(1, $userId);
$stmt->bindValue(2, $amount);
$stmt->execute();
$conn->beginTransaction();
try {
$conn->executeStatement('UPDATE accounts SET balance = balance - 100 WHERE id = ?', [$userId]);
$conn->executeStatement('UPDATE accounts SET balance = balance + 100 WHERE id = ?', [$vendorId]);
$conn->commit();
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
$schemaManager = $conn->createSchemaManager();
$tables = $schemaManager->listTables(); // Array of Table objects
$table = $schemaManager->describeTable('users'); // Table metadata
$schema = new \Doctrine\DBAL\Schema\Schema();
$table = $schema->createTable('users');
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('name', 'string', ['length' => 255]);
$table->setPrimaryKey(['id']);
$schemaManager->createTable($table); // Creates table if it doesn't exist
$schemaManager->dropTable('users'); // Drops table
Use with Doctrine Migrations Bundle for Laravel:
composer require doctrine/doctrine-migrations-bundle
php artisan doctrine:migrations:diff
php artisan doctrine:migrations:migrate
$queryBuilder = $conn->createQueryBuilder();
$queryBuilder
->select('u.id', 'u.name', 'o.amount')
->from('users', 'u')
->join('u', 'orders', 'o', 'u.id = o.user_id')
->where('o.created_at > :date')
->setParameter('date', new \DateTime('-1 month'))
->orderBy('o.amount', 'DESC')
->limit(10);
$stmt = $queryBuilder->execute();
$results = $stmt->fetchAllAssociative();
Use DBAL for raw queries when Eloquent is overkill:
use Doctrine\DBAL\Connection;
// In a Laravel service or repository
public function getRawUserData(Connection $conn, int $id)
{
return $conn->fetchAssociative('SELECT * FROM users WHERE id = ?', [$id]);
}
Register DBAL Connection in Laravel:
// config/database.php
'dbal' => [
'driver' => 'pdo_mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
// In a service provider
public function register()
{
$this->app->bind(\Doctrine\DBAL\Connection::class, function ($app) {
$config = $app['config']['database.dbal'];
return \Doctrine\DBAL\DriverManager::getConnection($config);
});
}
$conn->close();
pdo_pgsql with pgbouncer).fetchAll() for large datasets – Use iterateAssociative() or chunked fetching:
$stmt->iterateAssociative(function ($row) {
// Process row-by-row
});
executeStatement() for non-select queries (e.g., INSERT, UPDATE) to avoid fetching results.$conn->beginTransaction();
try {
$schemaManager->createTable($table);
$conn->commit();
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
$conn->executeStatement('SET FOREIGN_KEY_CHECKS = 0');
// ... bulk operations ...
$conn->executeStatement('SET FOREIGN_KEY_CHECKS = 1');
$conn->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
$table->addColumn('`order`', 'string'); // Avoids SQL syntax errors
DBAL for multi-database queries: If your app uses multiple databases, DBAL provides a unified interface:
$conn1 = DriverManager::getConnection(['url' => env('DB_URL_PRIMARY')]);
$conn2 = DriverManager::getConnection(['url' => env('DB_URL_SECONDARY')]);
\Doctrine\DBAL\Driver for unsupported databases.\Doctrine\DBAL\Event\Listeners for query logging, profiling, or rewriting.\Doctrine\DBAL\Platforms\AbstractPlatform for database-specific SQL dialect tweaks.| Error | Cause | Solution |
|---|---|---|
PDOException: SQLSTATE[HY000] |
Invalid connection params | Verify host, port, username, password. |
InvalidParameterException |
Missing required column | Check addColumn() parameters (e.g., length for strings). |
ForeignKeyConstraintViolationException |
Referential integrity | Ensure parent records exist before inserting children. |
SyntaxErrorException |
Reserved keyword in query | Use backticks: `table` or `column`. |
$
How can I help you explore Laravel packages today?