nette/database
Nette Database is a lightweight yet powerful PHP database layer with an intuitive query builder, safe parameter binding, and support for MySQL, PostgreSQL, SQLite, MS SQL, and Oracle. Clean API plus optional ORM-like conveniences make queries simple and secure.
Architecture fit: conditional, not default. This is not a Laravel package. nette/database is a standalone, framework-agnostic DB layer (Connection + Explorer + Selection/ActiveRow) with its own PDO wrapper, its own DI extension for Nette DI, and its own Tracy debug panel. Laravel already ships two overlapping stacks — the DB facade/Query Builder and Eloquent. Adopting nette/database inside a Laravel app means running a third data-access paradigm unless it strictly replaces one of the two, which is rarely viable on a live codebase. The honest framing for stakeholders: this is not an "add a Composer package" decision, it is an "own and maintain a bridge" decision.
Where it genuinely fits:
nette/database while new feature work lands on Eloquent. The package's stability record (backports through PHP 8.5 on the 3.2 line, steady 3.2.x patch cadence) makes it a credible "keep the old world alive" dependency.nette/database ships drivers for MySQL, PostgreSQL, SQLite, MS SQL Server, and Oracle in one package, with a real reflection layer for Table, Column, Index, ForeignKey. For an enterprise client anchored on Oracle or MSSQL, that breadth is the only defensible differentiator.Where it does not fit: a greenfield or MySQL/Postgres-only Laravel app. Everything the description advertises — fluent parameterized builder, row objects, transactions, multi-DB support — is already covered by Laravel's Query Builder with better framework integration. Opportunity score of 67.5 against a 52.6 overall reads to me as "interesting tech, weak contextual fit," which matches my read.
Integration feasibility: medium-low, and the hard part is connections, not queries.
DB::transaction() are invisible to nette/database reads on a separate connection. Any code path that mixes the two silently loses read-your-writes and can deadlock against itself under load. On v3.x this is partially mitigable by sharing the underlying PDO handle (Connection::getPdo() exists); v4.0.0-RC1 removes getPdo() and getDsn(), which removes exactly the escape hatch we would need. That is a material argument for pinning v3.2.x and treating v4 as a future migration, not an adoption target.convertBoolean / convertDateTime are attractive, but an RC with removed interfaces (IRow, IRowContainer) and removed methods is a moving target. Also undocumented in the supplied sources: the PHP floor for v4.NOASSERTION on a commercially consumed dependency requires legal review before we build on it. Nette packages are typically BSD-3-Clause, but "typically" is not a sign-off. Treat this as a gate, not a footnote.Dependents: 0 is not credible and should be struck from the decision record — nette/database is a core dependency of the Nette framework stack. 539 stars is a fair signal of niche, not of abandonment.Technical risk:
0001-01-01, TINYINT(1) → bool, DECIMAL precision 0 → int, convertBoolean default flip in v4. Row-shape contracts we build on are not stable across minor versions.SqlPreprocessor is mature and the IN (?) handling was hardened.Selection::transaction() nesting is supported, but nesting semantics differ from Laravel's savepoint-based nesting. Must be documented explicitly for the team.Key questions to resolve before commitment:
NOASSERTION license?Stack fit. nette/database 3.2.x targets PHP 8.1+ with explicit support through 8.5, and Laravel 10/11 sit at 8.1/8.2 — so the runtime matrix is compatible. Dependency surface is small (nette/utils, optionally nette/di); the DI extension is only needed under Nette DI and should be bypassed so we don't drag Nette's container into a Laravel app. PSR-4 autoloading coexists cleanly with Laravel's Composer setup. Where fit breaks down is the framework seam: Laravel's container, config, and DB facade have no knowledge of Nette\Database\Connection, and nette/database has no knowledge of Laravel's connection config, retry policy, or query listener.
Migration path (recommended, phased):
nette/database only behind a dedicated namespace (App\Legacy\Db or similar) with a thin repository interface per aggregate. No controller, job, or model touches Explorer directly. The interface is the exit plan — write it as though the package will be removed.Nette\Database\Connection from the same PDO instance Laravel already holds so both layers share one connection and one transaction. If sharing proves fragile, fall back to a dedicated, explicitly read-only connection and enforce that at the repository level — never allow writes on the sidecar.Explorer/Connection singletons from config/database.php, publishes a config file, and registers a query listener into Laravel's logging/Telescope so nette/database traffic isn't invisible in production. Skip the Tracy ConnectionPanel entirely — it is a Nette/Tracy artifact and does not belong in a Laravel runtime; wire the package's query events into Laravel's logging instead.ActiveRow usage outside the legacy namespace. Map DeadlockException, LockTimeoutException, CheckConstraintViolationException, and ConnectionLostException (3.2.9+) into our existing retry policy — note that Laravel already has causedByConcurrencyError, so pick one policy and delete the other to avoid double-retry.Compatibility notes to bake into the plan:
getPdo() / getDsn() for anything load-bearing if a v4 move is on the roadmap; the connection-sharing trick in step 2 is v3-only.Sequencing. Legal clearance and a two-day read-only spike precede any committed work. Spike scope: one real Oracle/MSSQL query path, one transaction-sharing proof, one query-log capture. Only after the spike passes do we sequence: service provider → read-only repositories → write paths with documented transaction ownership → observability → deprecation tracking. Do not attempt writes in phase one.
Maintenance. The 3.2 line is actively maintained — PHP 8.5 support, a steady release cadence, and a 3.2.9 release that added meaningful exception taxonomy plus test infrastructure. That is better than many community packages. But the maintenance burden we absorb is the bridge: config binding, query logging, retry-policy translation, and the ADR that stops engineers from mixing Eloquent and Explorer in one unit of work. Budget this as a standing line item, not a one-off integration cost. The v4 RC signals a forthcoming BC-heavy major, which means a planned migration project roughly 12–18 months out.
Support. Community-maintained under the Nette umbrella with no commercial support tier. Effective maintainer concentration is high — a real bus-factor consideration for a system-of-record dependency. Documentation lives on nette.org and in the repo's docs/, is Nette-idiomatic, and has zero Laravel framing. Our Laravel-native engineers will not find answers by pattern-matching to Eloquent; expect the bridge owner to become an internal SME and to write the internal docs themselves. The repo's own agent-facing documentation explicitly warns that the core and Explorer layers are "two independent worlds" with sharp edges — treat that as an authoritative onboarding warning.
Scaling. The package adds little overhead itself; it wraps PDO and inherits whatever connection behaviour PDO and the driver provide. It does not provide connection pooling, read/write splitting, or query caching — Laravel's connection config does, so keep those concerns in Laravel and treat nette/database as a consumer of an already-configured handle. The real scaling risk is behavioural: ActiveRow's lazy execution and accessed-column narrowing turn a single logical view into a fan-out of queries, and N+1 at the Explorer layer will not show up in Laravel's usual Eloquent-oriented tooling unless the query listener is wired in first. Under Oracle, verify applyLimit offset behaviour on the pinned version — 3.2.8 shipped a fix for OciDriver returning inconsistent results with differing offsets, which tells you this path is less travelled.
Failure modes to design for:
Selection::transaction() does not behave like Laravel's savepoints. Mitigation: forbid nesting across the boundary; one owner per unit of work.convertBoolean/convertDateTime default-on in v4, zero-date → NULL, TINYINT(1) → bool, getInsertId() returning int. Mitigation: contract tests asserting exact row types per repository, run on every dependency bump.ConnectionPanel is unusable in Laravel and removed interfaces in v4 further reduce hooks. Mitigation: query listener registered in the service provider, alerting if no nette/database queries are captured (a silent listener is itself a failure).DeadlockException/LockTimeoutException/ConnectionLostException are only useful if the retry policy is written against them. Mitigation: map them explicitly and test with fault injection.Ramp-up. Two distinct curves. For engineers, the API is genuinely approachable — a PDO-like surface with a fluent builder, and Selection/ResultSet ergonomics are close enough to Laravel's builder that a competent PHP developer is productive in a day. The second curve is the dangerous one: understanding when ActiveRow executes, how column narrowing changes generated SQL, and why mixing it with Eloquent transactions is unsafe takes materially longer and is learned by incident unless it is taught. Plan a half-day internal workshop and an ADR with worked examples. Estimate 2–3 weeks for the bridge and first repository, plus a 10–15% ongoing maintenance tax on the legacy surface, against which you weigh the cost of a full Eloquent port. For an Oracle/MSSQL estate that port cost is high enough to justify this; for MySQL/Postgres it is not.
How can I help you explore Laravel packages today?