doctrine/dbal
Doctrine DBAL is a powerful PHP database abstraction layer for working with multiple database platforms. Provides connections, query building, and rich schema introspection and management tools for migrations and database tooling.
Installation:
composer require doctrine/dbal
Laravel already includes Doctrine DBAL as a dependency via illuminate/database, so no additional installation is needed if using Laravel's built-in database layer.
First Use Case: Connect to a database and run a raw query:
use Doctrine\DBAL\DriverManager;
$connectionParams = [
'dbname' => 'your_database',
'user' => 'your_username',
'password' => 'your_password',
'host' => 'localhost',
'driver' => 'pdo_mysql',
];
$conn = DriverManager::getConnection($connectionParams);
$stmt = $conn->executeQuery('SELECT * FROM users');
$result = $stmt->fetchAllAssociative();
Where to Look First:
config/database.php for connection configurations.vendor/doctrine/dbal/src/Doctrine/DBAL/ for core classes.use Doctrine\DBAL\Query\QueryBuilder;
$queryBuilder = $conn->createQueryBuilder();
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->where('u.active = :active')
->setParameter('active', 1)
->orderBy('u.name', 'ASC');
$result = $queryBuilder->execute()->fetchAllAssociative();
Inspect database schema dynamically:
$schemaManager = $conn->createSchemaManager();
$tables = $schemaManager->listTables();
$columns = $schemaManager->listTableColumns('users');
$conn->beginTransaction();
try {
$conn->executeStatement('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
$conn->executeStatement('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
$conn->commit();
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
Create or modify tables programmatically:
$schemaManager = $conn->createSchemaManager();
$table = $schemaManager->createTable('new_table');
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('name', 'string', ['length' => 255]);
$table->setPrimaryKey(['id']);
$schemaManager->createTable($table);
Use DBAL for raw queries alongside Eloquent:
$users = DB::connection('mysql')->select('SELECT * FROM users WHERE active = ?', [1]);
// Or via Laravel's DB facade with DBAL under the hood
Laravel's DB facade uses Doctrine DBAL internally. Extend it for custom logic:
use Doctrine\DBAL\Connection;
use Illuminate\Support\Facades\DB;
$conn = DB::getDoctrineConnection(); // Get underlying DBAL connection
Extend Doctrine\DBAL\Platforms\AbstractPlatform for database-specific logic:
use Doctrine\DBAL\Platforms\MySqlPlatform;
class CustomMySqlPlatform extends MySqlPlatform {
public function getListTableColumnsSQL(string $table) {
return 'SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?';
}
}
Attach listeners for query logging or performance monitoring:
$conn->getEventManager()->addListener(
\Doctrine\DBAL\Connection::EVENT_QUERY,
function ($eventArgs) {
\Log::debug('Query: ' . $eventArgs->getSql());
}
);
try-catch blocks and ensure connections are properly closed:
$conn = DriverManager::getConnection($connectionParams);
try {
$conn->beginTransaction();
// ...
} catch (\Exception $e) {
$conn->rollBack();
} finally {
$conn->close();
}
setParameter or ? placeholders:
// Bad
$conn->executeQuery("SELECT * FROM users WHERE id = {$id}");
// Good
$stmt = $conn->executeQuery("SELECT * FROM users WHERE id = ?", [$id]);
SchemaManager::createTable() carefully.LIMIT vs. FETCH FIRST).$platform = $conn->getDatabasePlatform();
if ($platform->supportsLimitOffset()) {
$queryBuilder->setMaxResults(10)->setFirstResult(20);
}
try {
$conn->beginTransaction();
// ...
} catch (\Doctrine\DBAL\Exception $e) {
if ($conn->isTransactionActive()) {
$conn->rollBack();
}
throw $e;
}
$conn->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
getLastInsertId()Debug auto-increment IDs:
$conn->executeStatement('INSERT INTO users (name) VALUES (?)', ['John']);
$lastId = $conn->lastInsertId();
Compare schemas between environments:
$schemaManager1 = $conn1->createSchemaManager();
$schemaManager2 = $conn2->createSchemaManager();
$diff = $schemaManager1->createSchemaDiff($schemaManager2->createSchema());
Test transactions with isolation levels:
$conn->beginTransaction(Connection::TRANSACTION_READ_COMMITTED);
Register custom database types:
use Doctrine\DBAL\Types\Type;
Type::addType('json', \Doctrine\DBAL\Types\JsonType::class);
Extend functionality via events:
$eventManager = $conn->getEventManager();
$eventManager->addEventSubscriber(new class implements \Doctrine\DBAL\Event\EventSubscriber {
public function getSubscribedEvents() {
return [
\Doctrine\DBAL\Connection::EVENT_POST_QUERY => 'onPostQuery',
];
}
public function onPostQuery(\Doctrine\DBAL\Event\ConnectionEventArgs $eventArgs) {
// Custom logic after query execution
}
});
Override platform behavior:
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('json', 'json');
Create custom connection wrappers for retry logic or connection pooling:
class RetryConnection implements \Doctrine\DBAL\Connection {
private $conn;
public function __construct(\Doctrine\DBAL\Connection $conn) {
$this->conn = $conn;
}
public function executeQuery($sql, array $params = [], array $types = []) {
return $this->retryWithBackoff(function() use ($sql, $params, $types) {
return $this->conn->executeQuery($sql, $params, $types);
});
}
// Delegate other methods to $this->conn
}
DB Facade with DBAL$results = DB::connection('mysql')->select('SELECT * FROM users WHERE active = :
How can I help you explore Laravel packages today?