doctrine/dbal
Doctrine DBAL is a powerful PHP database abstraction layer offering portable connections, a fluent query builder, schema introspection, and schema management tools. It supports multiple database platforms and underpins many Doctrine-based applications.
Install via Composer:
composer require doctrine/dbal
First Use Case: Database Connection Leverage DBAL alongside Laravel's built-in Eloquent or Query Builder for raw SQL operations:
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
// Configure connection (similar to Laravel's .env)
$connectionParams = [
'dbname' => env('DB_DATABASE'),
'user' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'host' => env('DB_HOST'),
'driver' => 'pdo_mysql',
];
$conn = DriverManager::getConnection($connectionParams);
// Execute a query
$stmt = $conn->executeQuery('SELECT * FROM users');
$users = $stmt->fetchAllAssociative();
Key Starting Points:
Connection Class – Core interface for queries, transactions, and schema operations.QueryBuilder – Fluent SQL builder (alternative to Laravel’s Query Builder).SchemaManager – Introspect and manage database schemas.Basic CRUD with DBAL:
// Insert
$conn->insert('users', ['name' => 'John', 'email' => 'john@example.com']);
// Update
$conn->update('users', ['name' => 'Jane'], ['id' => 1]);
// Delete
$conn->delete('users', ['id' => 1]);
// Fetch single row
$user = $conn->fetchAssociative('SELECT * FROM users WHERE id = ?', [1]);
Bulk Operations:
// Batch insert
$conn->executeStatement(
'INSERT INTO users (name, email) VALUES (:name, :email)',
['name' => 'Alice', 'email' => 'alice@example.com']
);
// Transaction
$conn->beginTransaction();
try {
$conn->executeQuery('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
$conn->executeQuery('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
$conn->commit();
} catch (\Exception $e) {
$conn->rollBack();
throw $e;
}
Introspection (Inspect Database Structure):
$schemaManager = $conn->createSchemaManager();
$tables = $schemaManager->listTables(); // Array of Table objects
$columns = $schemaManager->listTableColumns('users'); // Column metadata
Schema Updates (Migrations):
$schema = new \Doctrine\DBAL\Schema\Schema();
$table = $schema->createTable('posts');
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('title', 'string', ['length' => 255]);
$table->setPrimaryKey(['id']);
$sm = $conn->createSchemaManager();
$sm->createSchema($schema); // Apply schema
Laravel Integration Tip:
Use DBAL’s SchemaManager in migrations or seeder classes:
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\DriverManager;
public function up()
{
$conn = DriverManager::getConnection(config('database.connections.mysql'));
$schema = new Schema();
// Define schema changes...
$sm = $conn->createSchemaManager();
$sm->createSchema($schema);
}
Fluent SQL Construction:
$queryBuilder = $conn->createQueryBuilder();
$queryBuilder
->select('u.*')
->from('users', 'u')
->where('u.active = :active')
->andWhere('u.created_at > :date')
->setParameter('active', 1)
->setParameter('date', '2023-01-01')
->orderBy('u.name', 'ASC');
$results = $queryBuilder->execute()->fetchAllAssociative();
Subqueries and CTEs (Common Table Expressions):
// Subquery
$subQuery = $conn->createQueryBuilder()
->select('id')
->from('orders')
->where('status = :status')
->setParameter('status', 'completed');
$mainQuery = $conn->createQueryBuilder();
$mainQuery
->select('u.*')
->from('users', 'u')
->where('u.id IN (' . $subQuery->getSQL() . ')')
->setParameters($subQuery->getParameters());
Laravel Synergy:
Use DBAL’s QueryBuilder for raw SQL where Eloquent’s query builder falls short (e.g., window functions, complex joins).
Database-Specific Types:
// JSON column handling
$conn->executeQuery(
'INSERT INTO posts (content) VALUES (:content)',
['content' => json_encode(['title' => 'Hello'])]
);
// Enum/Set types
$conn->executeQuery(
'INSERT INTO users (role) VALUES (:role)',
['role' => 'admin'] // DBAL auto-converts based on platform
);
Platform-Specific Logic:
$platform = $conn->getDatabasePlatform();
if ($platform->getName() === 'mysql') {
// MySQL-specific syntax
$conn->executeQuery('SET NAMES utf8mb4');
}
// Reuse connections (avoid creating new ones per request)
$conn = app(Connection::class); // If using Laravel's DBAL binding
DriverManager::getConnection() for one-off operations, but cache connections in Laravel’s service container for performance.try {
$conn->beginTransaction();
// Operations...
$conn->commit();
} catch (\Doctrine\DBAL\Exception $e) {
$conn->rollBack();
throw new \RuntimeException('Transaction failed: ' . $e->getMessage());
}
Connection::getWrappedConnection()->isTransactionActive() to check state.SchemaManager::introspectTable() and inspect Column objects:
$column = $schemaManager->listTableColumns('users')['email'];
if ($column->getDefault() instanceof \Doctrine\DBAL\Types\DefaultExpression) {
echo 'Default is an expression: ' . $column->getDefault()->getExpression();
}
NOW()), use DefaultExpression explicitly:
$table->addColumn('created_at', 'datetime', [
'default' => new \Doctrine\DBAL\Types\DefaultExpression('CURRENT_TIMESTAMP')
]);
QueryBuilder::createNamedParameter() for clarity:
$qb->where('u.id = :id')
->setParameter('id', $userId, \PDO::PARAM_INT);
WHERE clause incrementally:
$qb = $conn->createQueryBuilder();
$qb->select('*')->from('users');
if ($activeOnly) {
$qb->andWhere('active = :active')->setParameter('active', 1);
}
executeStatement() for non-returning queries:
$conn->executeStatement(
'INSERT INTO logs (message) VALUES (:msg)',
['msg' => 'Event triggered'],
['msg' => \PDO::PARAM_STR]
);
iterateAssociative():
$stmt = $conn->executeQuery('SELECT * FROM large_table');
foreach ($stmt->iterateAssociative() as $row) {
// Process row-by-row (memory efficient)
}
$conn->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
How can I help you explore Laravel packages today?