laminas/laminas-db
Database abstraction and SQL builder for PHP. Provides adapters, connection management, query/statement execution, metadata and schema tools, result sets, and a fluent API for composing SQL across multiple database platforms.
Installation:
composer require laminas/laminas-db
Note: Laravel developers typically use Eloquent, but laminas-db can be integrated for complex queries or legacy systems.
Basic Adapter Configuration:
use Laminas\Db\Adapter\Adapter;
use Laminas\Db\Adapter\Driver\Pdo\Connection;
$connection = new Connection(
new \PDO('mysql:host=localhost;dbname=test', 'user', 'pass')
);
$adapter = new Adapter($connection);
First Query:
$resultSet = $adapter->query('SELECT * FROM users WHERE id = ?', [1]);
$row = $resultSet->current();
Key Classes to Explore:
Adapter: Core interface for database operations.Sql: Build SQL queries programmatically.TableGateway: Higher-level abstraction for table operations.ResultSet: Handle query results.Sqluse Laminas\Db\Sql\Sql;
use Laminas\Db\Sql\Predicate\Predicate;
$sql = new Sql($adapter);
$select = $sql->select()->from('users');
$select->where->equalTo('active', 1);
$select->limit(10);
$statement = $sql->prepareStatementForSqlObject($select);
$result = $adapter->query($statement->getSql(), $statement->getBindValues());
Workflow:
where, order, join) for fluent query building.Predicate for complex conditions (e.g., Predicate\Expression for raw SQL snippets).use Laminas\Db\TableGateway\TableGateway;
$tableGateway = new TableGateway('users', $adapter);
$user = $tableGateway->select(['id' => 1])->current();
// Insert
$tableGateway->insert(['name' => 'John', 'email' => 'john@example.com']);
// Update
$tableGateway->update(['name' => 'Jane'], ['id' => 1]);
// Delete
$tableGateway->delete(['id' => 1]);
Integration Tip:
TableGateway for models requiring direct table access (e.g., legacy systems or complex joins).ServiceProvider to bind TableGateway instances to IoC container:
$this->app->bind('usersTable', function ($app) {
return new TableGateway('users', $app->make(Adapter::class));
});
$resultSet = $adapter->query('SELECT * FROM users');
foreach ($resultSet as $row) {
// $row is a Laminas\Db\ResultSet\ResultSet instance
echo $row->name;
}
// Hydrate to array/object
$users = $resultSet->toArray();
$firstUser = $resultSet->current();
Tip:
ResultSet::toArray() or ResultSet::toObject() for Laravel collections:
$collection = collect($resultSet->toArray());
$adapter->beginTransaction();
try {
$adapter->query('UPDATE accounts SET balance = balance - ? WHERE id = ?', [100, 1]);
$adapter->query('UPDATE accounts SET balance = balance + ? WHERE id = ?', [100, 2]);
$adapter->commit();
} catch (\Exception $e) {
$adapter->rollBack();
throw $e;
}
Laravel Integration: Wrap in a Laravel transaction helper:
\DB::transaction(function () use ($adapter) {
// Use $adapter for queries
});
$schema = $adapter->getSchema();
$schema->createTable('posts', function ($table) {
$table->addColumn('id', 'INTEGER', ['PRIMARY_KEY' => true]);
$table->addColumn('title', 'VARCHAR', ['LENGTH' => 255]);
});
Use Case:
// Bad: Connection not closed
$adapter->query('SELECT 1');
// Good: Use try-catch or ensure cleanup
$adapter->getDriver()->getConnection()->close();
DB facade manages connections automatically. For laminas-db, manually close connections in long-running scripts.oci8 adapters).
Fix: Update to laminas-db 2.17.0+ (includes PR #282).declare(strict_types=1); and enable report_deprecated in php.ini to catch issues early.rewind() calls on ResultSet can cause data loss (fixed in 2.16.3).
Workaround: Avoid rewinding or clone the ResultSet if needed:
$clone = clone $resultSet;
$clone->rewind();
Predicate#expression() may bind null values unexpectedly (fixed in 2.15.1).
Tip: Explicitly check for null in queries:
$select->where->equalTo('column', $value ?? 'default');
ResultSet::bufferMode(ResultSet::BUFFER_MODE_UNKNOWN) to stream results:
$resultSet->bufferMode(ResultSet::BUFFER_MODE_UNKNOWN);
foreach ($resultSet as $row) {
// Process row-by-row
}
laminas-db alongside Eloquent for complex queries:
$query = $adapter->query('SELECT * FROM users WHERE ...');
$eloquentResults = User::hydrate($query->toArray());
Adapter to Laravel’s container:
$this->app->singleton(Adapter::class, function ($app) {
return new Adapter(
new Connection(new \PDO('mysql:host=...', 'user', 'pass'))
);
});
laminas-db schema:
$schema = $adapter->getSchema();
$sql = $schema->getCreateTableSql('users');
// Parse $sql to create a Laravel migration.
$adapter->getEventManager()->attach(
'prepareStatement',
function ($e) {
error_log($e->getStatement()->getSql());
}
);
Laminas\Db\Sql\Platform\Feature\PlatformFeature\SqlGenerationFeature to inspect generated SQL.Laminas\Db\Adapter\Adapter for vendor-specific logic (e.g., Snowflake).Laminas\Db\Adapter\AdapterEvents (e.g., prepareStatement) for query modification:
$events = $adapter->getEventManager();
$events->attach('prepareStatement', function ($e) {
$e->getStatement()->setSql(str_replace('SELECT', 'SELECT /* custom */', $e->getStatement()->getSql()));
});
getSqlStringForSqlObject() (deprecated in 2.16.0).
Use: $statement->getSql() instead.laminas-db is in security-only mode. Plan for migration if long-term support is needed.How can I help you explore Laravel packages today?