Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laminas Db Laravel Package

laminas/laminas-db

Laminas\Db is a database abstraction layer for PHP, offering adapters, SQL construction, result sets, metadata, and TableGateway/RowGateway patterns. It supports PDO and driver-based connections for portable database code in Laminas apps.

View on GitHub
Deep Wiki
Context7

Getting Started

laminas/laminas-db is not a Laravel package — there is no service provider, no facade, no auto-discovery. It is a standalone DB abstraction (Adapter → Sql → Statement → ResultSet → TableGateway/RowGateway) that you wire up manually, usually inside a repository or a dedicated reporting service.

Minimal setup

composer require laminas/laminas-db

Read in this order: docs.laminas.dev/laminas-db/adapters → /sql → /result-set → /table-gateway → /row-gateway. Skip the MVC integration chapter; it targets Laminas MVC, not Laravel.

Cheapest first integration — reuse Laravel's PDO so you don't open a second connection pool:

use Laminas\Db\Adapter\Adapter;
use Laminas\Db\Adapter\Driver\Pdo\Pdo;

$adapter = new Adapter(['driver' => new Pdo(DB::connection()->getPdo())]);

Standalone config (when you genuinely want an independent connection):

$adapter = new Adapter([
    'driver'   => 'Pdo_Mysql',          // or Pdo_Pgsql, Pdo_Sqlsrv, Mysqli, Pgsql, Oci8, IbmDb2
    'hostname' => config('database.connections.mysql.host'),
    'database' => config('database.connections.mysql.database'),
    'username' => config('database.connections.mysql.username'),
    'password' => config('database.connections.mysql.password'),
    'port'     => config('database.connections.mysql.port', 3306),
]);

First use case: a parameterized read, then a Sql\Select. Both are one-liners once the adapter exists:

$result = $adapter->query('SELECT id, email FROM users WHERE status = ?', [1]);
foreach ($result as $row) { /* $row is an ArrayObject */ }

$sql    = new Laminas\Db\Sql\Sql($adapter);
$select = $sql->select('users')->where(['status' => 1])->limit(10)->offset(0);
$rows   = $sql->prepareStatementForSqlObject($select)->execute();

Bind the adapter as a singleton in a service provider and inject it into repositories — that's the whole "getting started" story. Everything after this is a decision about whether you actually want two database layers in one app.

Implementation Patterns

Repository wrapping the adapter. The dominant day-to-day pattern: one class per aggregate, adapter injected via constructor, domain objects returned (not arrays). It keeps Laminas imports out of controllers/models and makes it trivially swappable back to DB:: later.

final class UserReportRepository
{
    public function __construct(private Adapter $db) {}

    public function activeSince(DateTimeImmutable $since): array
    {
        $sql = new Sql($this->db);
        $select = $sql->select('users')
            ->columns(['id', 'email', 'created_at'])
            ->where(['status' => 1])
            ->where->greaterThanOrEqualTo('created_at', $since->format('Y-m-d H:i:s'));

        return iterator_to_array(
            $sql->prepareStatementForSqlObject($select)->execute()
        );
    }
}

Prepared statements with ParameterContainer when you need types or named placeholders (the array form gets stringified, which matters for LIMIT/OFFSET on some drivers):

use Laminas\Db\Sql\Predicate\Expression;
use Laminas\Db\Adapter\ParameterContainer;

$select->where(new Expression('email = :email'));
$stmt = $sql->prepareStatementForSqlObject($select);
$stmt->getParameterContainer()->offsetSet('email', $email, ParameterType::STRING);
$stmt->execute();

TableGateway + HydratingResultSet when you want the package's RowDataGateway/TableDataGateway behaviour. This is the only part of the package that gives you something Laravel's query builder doesn't express as directly — a reusable table-scoped object with a hydration prototype:

$gateway = new TableGateway('users', $adapter, null, new HydratingResultSet($hydrator, new UserDto()));
$result  = $gateway->select(['status' => 1]);
$user    = $result->current(); // UserDto or null

HydratingResultSet is the practical bridge to Laravel-land: point a Laminas\Hydrator\ReflectionHydrator or ObjectProperty at a DTO and you get typed objects without touching Eloquent.

RowGateway for single-row write cycles:

$result = $gateway->select(['id' => $id])->current(); // RowGateway via RowGatewayFeature
$result->email = '[email protected]';
$result->save(); // issues UPDATE ... WHERE id = ?

Transactions live on the driver connection, not the adapter facade:

$conn = $adapter->getDriver()->getConnection();
$conn->beginTransaction();
try { /* ... */ $conn->commit(); }
catch (Throwable $e) { $conn->rollBack(); throw $e; }

Never mix this with DB::transaction() unless you are certain the adapter wraps the same PDO handle — Laravel's transaction counter and Laminas' will diverge and you get silent partial commits. Pick one layer per unit of work.

Cross-vendor reporting. The legitimate reason to reach for this in a Laravel app: a reporting/export module that must emit identical SQL against MySQL and SQL Server, using Platform for identifier quoting and limit emulation. Sql\Select handles the LIMIT/OFFSET vs TOP/OFFSET FETCH divergence for you.

DDL (rarely). Laminas\Db\Sql\Ddl\* can build CREATE TABLE/ALTER programmatically. It is not a migration system — no versioning, no rollback, no batch tracking. Don't use it as one; keep Laravel migrations as the schema source of truth.

Laminas interop. If you're embedding a package that expects AdapterAwareInterface, register Laminas\Db\Adapter\AdapterServiceDelegator with AdapterAwareTrait-composing classes; this injects via setDbAdapter().

Gotchas and Tips

It is security-only maintenance. The README explicitly marks the package feature-complete, and the TSC has declined new features. No new adapters, no PHP-version-driven improvements beyond keeping CI green. Treat every dependency on it as a long-term liability with an exit plan.

Do not use this to replace Eloquent or the query builder in a Laravel app. You'll reimplement soft deletes, timestamps, casts, model events, eager loading, and DB::listen() visibility. Scope it to legacy Zend/Laminas migrations, vendor-agnostic reporting, or a codebase already deeply invested in TableGateway. 145 stars and zero dependents is a signal about ecosystem momentum, not quality — the code is fine, the trajectory isn't.

The adapter is not a Laravel Connection. DB::transaction(), DB::listen(), query counting in Telescope/Debugbar, and assertQueryCount in tests all see nothing. If you wrap Laravel's PDO, statements execute on the same handle but Laravel's transaction bookkeeping is bypassed. If you use standalone credentials, you have two pools and two transaction scopes.

Identifier quoting is inconsistent. Sql\Select quotes via the platform; raw $adapter->query('SELECT ...') does not. Aliases with reserved words, schema-qualified names, and MySQL backtick vs SQL Server bracket quoting all bite. Pass identifiers through $adapter->getPlatform()->quoteIdentifier() when composing raw fragments.

prepareStatementForSqlObject() + execute($params) mutates and reuses the ParameterContainer. Reusing the same Sql/Select object across iterations accumulates stale values. Build a fresh Sql per call, or call $stmt->getParameterContainer()->setFromArray([...]) deliberately.

Type binding matters for LIMIT. Binding an integer as a string works with emulated prepares and fails with native ones on some drivers. Use ParameterType::INTEGER or Expression with a cast when you can't trust the driver.

HydratingResultSet::current() returns null, not false, on an empty set since 2.11.1 — this was called out as a potential BC break. Strict === false checks from old Zend code will silently pass.

Exception unwrapping. Laminas wraps driver errors in Laminas\Db\Adapter\Exception\* (e.g. InvalidQueryException). Call getPrevious() to reach the PDOException for the SQLSTATE and driver error code — otherwise your Laravel error handler and log context are useless.

Debugging: install the profiler. $adapter->setProfiler(new Laminas\Db\Adapter\Profiler\Profiler()) then inspect $profiler->getProfiles() for the exact SQL and bindings. There is no other observability hook.

lastInsertValue/SequenceFeature has a history of regressions (fixed in 2.11.1). Prefer $adapter->getDriver()->getConnection()->getLastGeneratedValue() and verify per-driver; on PostgreSQL, sequences need explicit configuration.

Ddl\Float was removed in 2.12.0 (reserved word since PHP 7). Use Ddl\Floating. Old tutorial code will fail on autoload.

MySQLi SSL flags were corrected in 2.11.4 — pin ^2.14 minimum, don't run anything below 2.11.4.

Extension points worth knowing: getResultPrototype() on each driver (inspect/replace the ResultSet prototype), custom Platform subclasses for dialect quirks, custom Hydrator strategies for value objects, and the AdapterServiceDelegator/AdapterAwareTrait pair for DI into Laminas-style classes.

Composer weight. It pulls laminas-servicemanager, laminas-hydrator, laminas-stdlib, and laminas-eventmanager into a Laravel app that already ships a container. That's a non-trivial dependency surface for a "query helper" — one more reason to keep the integration behind a single repository class you can delete in an afternoon.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource