harrygulliford/laravel-firebird
A Firebird database driver for Laravel that adds connection, query builder, Eloquent, schema and migration support, letting you work with Firebird through Laravel’s familiar database APIs.
This package is a Firebird database driver for Laravel (connector, query grammar, schema grammar/builder, and Eloquent plumbing). The 4.0.0-beta.0 release is a compatibility rebaseline, so treat it as a new install target rather than an in-place patch.
Prerequisites (hard requirements in v4):
^8.212.x or 13.x4 or 5 (Firebird 3.x and older are no longer supported)Install and wire up:
composer require <vendor>/laravel-firebird:^4.0@beta
Confirm the PDO Firebird extension is present — this is the most common first failure:
php -m | grep -i firebird # expect: pdo_firebird
Add a connection in config/database.php:
'firebird' => [
'driver' => 'firebird',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', 3050),
'database' => env('DB_DATABASE'), // absolute path to .fdb, or host:/path
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'charset' => env('DB_CHARSET', 'UTF8'),
],
Then set DB_CONNECTION=firebird and verify:
php artisan tinker
>>> DB::connection('firebird')->selectOne('select 1 as ok from rdb$database');
>>> DB::connection('firebird')->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
Where to look first: the package's service provider (driver registration) and the query grammar — the grammar is where most v4 changes landed (limit/offset, unions, exists aliasing, insertGetId). If you maintain a custom grammar subclass, read those before anything else.
First real use case: pointing Eloquent at an existing legacy Firebird schema. Start read-only — a Model with protected $table, $primaryKey mapped to the real (uppercase) column, and $incrementing / $keyType set explicitly. Get reads correct before attempting writes.
Connection-per-database. Firebird is file-based; each .fdb is a distinct connection. Declare multiple named connections rather than swapping the database value at runtime. Keep charset=UTF8 explicit — mismatches surface as mojibake rather than errors.
Models against legacy schemas. Firebird folds unquoted identifiers to uppercase, so rdb$-era columns come back as MY_COLUMN. Either quote identifiers consistently or use lowercase accessors. Be explicit about:
protected $table = 'ORDERS';
protected $primaryKey = 'ORDER_ID';
public $incrementing = true; // generator/sequence-backed
protected $keyType = 'integer';
Inserts with generated keys. v4 adds insertGetId() via RETURNING, so the idiomatic path now works:
$id = DB::connection('firebird')->table('ORDERS')->insertGetId([
'CUSTOMER_ID' => $customerId,
'TOTAL' => 42.50,
]);
For Eloquent, Model::create() populating the primary key now depends on this path — verify it on your Firebird version, and keep a fallback (select gen_id(...) from rdb$database) if you're on a build where RETURNING behaves inconsistently.
Pagination. v4 switched to standard Firebird syntax:
... offset 20 rows fetch first 10 rows only
This means pagination works through Laravel's normal paginate() / simplePaginate() and cursorPaginate() without a custom length-aware paginator. If you previously wrote a workaround for ROWS/TO semantics, remove it.
Unions with limit/offset. Union compilation was reworked, including limit/offset on the union as a whole. Prefer the builder:
$active = DB::table('USERS')->where('ACTIVE', 1);
$archived = DB::table('USERS_ARCHIVE')->where('ACTIVE', 1);
$rows = $active->unionAll($archived)->orderBy('ID')->limit(50)->offset(100)->get();
Lateral joins. Now supported — useful in Firebird for "latest row per parent" queries:
DB::table('CUSTOMERS as c')
->joinLateral(function ($q) {
$q->from('ORDERS as o')
->whereColumn('o.CUSTOMER_ID', 'c.ID')
->orderByDesc('o.CREATED_AT')
->limit(1);
}, 'last_order')
->get();
Keep lateral subqueries tightly filtered; Firebird's optimizer treats them conservatively.
Date/time where clauses. whereDate, whereTime, whereYear etc. are now supported. Prefer them over raw EXTRACT(...) strings — but note they still compile to Firebird EXTRACT/cast expressions, so indexes on the raw TIMESTAMP column generally won't be used. For hot paths, precompute a date column or add a computed index.
Migrations. Schema grammar signatures were updated for Laravel 12/13. If you extend the schema builder or grammar, re-check parent method signatures — mismatches fail at class-load time, not at query time. Prefer publishing migrations as-is over subclassing where possible.
Testing. Run integration tests against a real Firebird 4 and 5 instance in CI (containerized). SQLite/MySQL substitutes will not catch grammar regressions — limit/offset, union, exists alias quoting, and RETURNING are precisely the areas that differ.
This is a beta, and it is a breaking release. Dropping PHP < 8.2, Laravel < 12, and Firebird < 4 means an upgrade from 3.x is a coordinated project: bump PHP, bump Laravel, then upgrade Firebird, then this package. Pin ^4.0@beta explicitly and gate on a staging run; do not float to ^4.0 until 4.0.0 stable ships.
Old limit/offset syntax is gone. Any raw SQL or hand-built query carrying ROWS n TO m still works server-side, but the grammar now emits offset ... rows / fetch first ... rows only. Mixing hand-written and grammar-generated pagination in the same codebase produces inconsistent plans; consolidate.
exists alias quoting changed. The fix prevents case-conversion issues, but it also means subquery aliases your app generates internally now appear quoted and case-preserved. If you reference a generated alias by name in raw SQL, match the exact casing.
drop if exists was rewritten. It no longer trusts the older table-existence compilation path. Consequence: Schema::dropIfExists() now issues differently shaped DDL, which can behave differently inside transactions and against objects owned by another user. Test rollback migrations, not just migrate.
Server version detection now parses PDO server metadata. If you connect through a proxy, pooler, or a client library that reports a synthetic version string, the driver may mis-detect. When behavior diverges between environments, dump PDO::ATTR_SERVER_VERSION first — feature gating (lateral joins, RETURNING) depends on this parse.
Identifier casing is the #1 debugging trap. Unquoted Firebird identifiers fold to uppercase; quoted ones preserve case. Symptom: "column not found" for a column you can see in the schema. Debug with ->toSql() and inspect the quoting, or query rdb$relation_fields.
Firebird has no native boolean. Depending on your schema you'll get CHAR(1) ('T'/'F') or small integers. Cast at the model boundary ($casts) rather than sprinkling comparisons; predicates generated as where('ACTIVE', true) will not do what you expect.
Generators are not AUTO_INCREMENT. insertGetId() maps to RETURNING, but the underlying generator/trigger must exist. If a legacy table lacks one, inserts succeed with a null PK. Verify per-table during migration, not per-application.
Transactions and DDL. Firebird does not nest DDL the way MySQL does. Wrap data migrations in DB::transaction(), but run schema changes outside it, and expect long-running ALTER TABLE to block concurrent connections.
Debugging tools that pay off here:
DB::listen(fn ($q) => logger($q->sql)) — inspect the compiled SQL, since grammar bugs are invisible above the builder.DB::connection()->getQueryLog() with DB::enableQueryLog() for one-off requests.php artisan migrate --pretend to review generated DDL before touching a real database.APP_DEBUG off against production .fdb files: exception messages can leak the absolute database path and credentials.Extension points. The usual customizations — a custom connection factory binding, a grammar subclass for a dialect quirk, or a PostProcessor — are all still available, but the grammar is the most volatile surface across 3.x→4.x. Keep such subclasses thin, isolate them in one namespace, and re-run the package's grammar tests after every minor bump rather than overriding grammar methods speculatively. If you find yourself overriding compileLimit, compileUnion, or insertGetId, that is a strong signal to file upstream — v4 already changed all three in ways most forks were patching around.
How can I help you explore Laravel packages today?