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

Db Laravel Package

pear/db

PEAR DB is a database abstraction layer for PHP, offering a unified API across MySQL, PostgreSQL, SQLite, Oracle, and more. It supports prepared statements, transactions, flexible fetch modes, and error handling, making legacy PHP apps portable across database backends.

Deep Wiki
Context7

Getting Started

Install with composer require pear/db. The PEAR mirrors on Packagist generally ship a classmap autoloader, so DB resolves on its own — but if you get Class "DB" not found, the cause is PEAR's internal require_once 'DB/common.php' style includes, which Composer cannot resolve. Add the directory containing PEAR.php to include_path (via a "include-path" entry or a bootstrap line in AppServiceProvider::register()) and it works.

Read three files, in this order:

  1. DB.php — the factory. DB::connect($dsn, $options) is the whole entry point.
  2. DB/common.php — the API you will actually use: query, getOne, getRow, getCol, getAll, getAssoc, prepare/execute, autoExecute, quoteSmart, limitQuery.
  3. Only the one driver file you need: DB/mysqli.php, DB/pgsql.php, etc.

Your first real use case is almost certainly a one-shot Artisan command that pulls legacy rows into Eloquent. The legacy side already speaks PEAR DB; Laravel is the new side. Write it as an isolated command, not a service:

$dsn = 'mysqli://legacy:'.rawurlencode($pass).'@127.0.0.1/legacy_db?charset=utf8mb4';
$db  = DB::connect($dsn, ['persistent' => false]);

if (DB::isError($db)) {
    throw new RuntimeException($db->getMessage());
}

$db->setFetchMode(DB_FETCHMODE_ASSOC);

The shape to internalize immediately: DB::connect() returns either a connection or an error object. There are no exceptions anywhere in this package. Every line downstream depends on you remembering DB::isError().

Implementation Patterns

1. Bind it once, hand out a repository, never the raw handle.

$this->app->singleton('legacy.db', function () {
    $db = DB::connect(config('legacy.dsn'), [
        'persistent'  => false,
        'portability' => DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM,
    ]);

    if (DB::isError($db)) {
        throw new RuntimeException('Legacy DB: '.$db->getMessage());
    }

    return $db->setFetchMode(DB_FETCHMODE_ASSOC) ?: $db;
});

2. Convert errors at the boundary. PEAR_Error is not a Throwable, so Laravel's handler, report(), and try/catch are all blind to it. Funnel every call through one helper and never let a PEAR_Error escape the repository:

protected function run(callable $fn)
{
    $result = $fn();

    if (DB::isError($result)) {
        throw new QueryException('legacy', '', [], new Exception($result->getMessage(), $result->getCode()));
    }

    return $result;
}

3. Use the fetch helpers instead of hand-rolled loops. getOne, getRow, getCol, getAll, getAssoc are the reason this API survived twenty years. getAssoc($sql, false, false, false) returns keyed arrays in a single call — usually what you'd otherwise do with collect()->keyBy().

4. Prepared statements are client-emulated on the mysql driver. prepare() returns a handle; execute($sth, [$a, $b]) binds positionally. Only ? placeholders exist, and on mysql the substitution happens in PHP via quoteSmart(), not in the server. Safe for values, useless for identifiers — use $db->quoteIdentifier() for table and column names, never raw interpolation.

$sth  = $db->prepare('SELECT id, email FROM customers WHERE updated_at > ? AND status = ?');
$rows = $this->run(fn () => $db->execute($sth, [$since, 'active']));

5. Transactions are connection-global and do not nest. $db->autoCommit(false), then commit() or rollback(). No savepoints, no DB::transaction() equivalent. Always restore with autoCommit(true) in a finally — a leaked false poisons every subsequent query on that connection, including later jobs in the same worker.

6. Paginate with limitQuery. It rewrites LIMIT per driver, so the same call works against MySQL and Postgres. Pair it with a chunked loop for exports: getAll() on a large table blows memory because DB has no unbuffered or cursor mode.

7. autoExecute for cheap writes — $db->autoExecute('customers', $data, DB_AUTOQUERY_UPDATE, 'id = '.$db->quoteSmart($id)). Convenient, and the single most common place people concatenate a where-clause. Validate the key before it goes in.

8. Keep it off the request path where you can. The durable pattern is: legacy handle behind a repository, repository called from queued jobs and console commands, Eloquent/PDO owning the request path. DB will not enforce a query timeout for you; set one at the driver level if you must call it synchronously.

9. Never share a transaction with Eloquent. pear/db opens its own socket. DB::beginTransaction() in Laravel and autoCommit(false) here are two unrelated transactions on two connections. Write skew and deadlocks are yours to debug. One owner per unit of work.

10. Testing. Do not mock PEAR_Error — test the repository against a real database. And do not reach for the sqlite driver as a shortcut: it maps to the long-removed ext/sqlite (SQLite 2), not sqlite3 or PDO, so it will not load on a modern PHP build. Use a MySQL/Postgres container or a hand-written fake behind your repository interface.

Gotchas and Tips

  • @ suppression collides with Laravel's error handler. Large parts of PEAR DB still prefix calls with @ or raise E_USER_NOTICE. Laravel's HandleExceptions converts notices into ErrorException, so code that "worked" in a bare PHP script throws here. Scope it: wrap DB::connect() in a temporary set_error_handler(fn () => true) and restore immediately after.

  • Bad credentials return, they don't throw. A wrong password yields a DB_Error object, which is truthy — if ($db) passes and you fail three lines later with a confusing "call to a member function on…" message. Always DB::isError().

  • affectedRows() lies on MySQL DELETE. Without DB_PORTABILITY_DELETE_COUNT, a delete reports 0 because MySQL returns the count from a different call. Turn portability on — but note DB_PORTABILITY_RTRIM will silently right-trim your strings, so mask it off (DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM) unless you actually want that.

  • Everything is a string. Numeric columns come back as "42" on most drivers. === comparisons, json_encode output, and strictly typed DTO constructors all surprise you. Cast once, in the repository.

  • Fetch mode is mutable state on the handle. Any legacy file calling setFetchMode(DB_FETCHMODE_ORDERED) changes it for everyone sharing the singleton. Set it at bind time and re-assert it in critical repository methods.

  • Charset is not inherited. The mysqli driver takes the server default, which on old installs is latin1. Put ?charset=utf8mb4 in the DSN or run SET NAMES right after connecting, or emoji and non-ASCII names round-trip as mojibake.

  • DSN passwords need URL-encoding. @, /, and : in a password break the DSN parser, and the failure presents as "unknown database". rawurlencode() user and password when building the DSN from config.

  • Sequences are driver-dependent. nextId() uses a real sequence on Postgres and an emulated sequence table on MySQL; createSequence()/dropSequence() are no-ops or errors depending on driver. Prefer auto-increment columns and stop calling nextId().

  • Octane and long-running workers will bite. The singleton holds a live socket; after a worker recycle the handle is dead and every call errors. Forget the instance per job (app()->forgetInstance('legacy.db')), wrap with reconnect-and-retry, and disconnect() in finally for console commands.

  • Debugging. $db->setOption('debug', 2) prints every statement to stdout — useful in a command, catastrophic in a web response. Prefer PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, fn ($e) => Log::error($e->getMessage())) so failures land in your normal log channel, and log $db->last_query (set by query()/execute()) when you need the exact SQL.

  • The one extension point worth taking. Subclass DB_common (or your driver) and override raiseError() to throw instead of return. That single change makes the whole package behave like modern PHP and saves you a hundred DB::isError() checks. Do it early.

  • Pin the exact version. This is maintenance-mode code — 11 stars, essentially the PEAR archive, with rare releases. A patch bump is not a signal that behavior was audited. Lock it in composer.json and treat every update as a change to a vendored dependency.

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.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules