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

Fluentpdo Laravel Package

envms/fluentpdo

FluentPDO is a lightweight PHP library for building SQL queries with a fluent, chainable API on top of PDO. It supports joins, subqueries, and smart column detection without a full ORM, keeping queries readable and fast. Works with MySQL, SQLite, and PostgreSQL on existing PDO connections.

Deep Wiki
Context7

Getting Started

What it is (and isn't): FluentPDO is a standalone query builder that wraps a raw PDO instance. It is not a Laravel package — there is no service provider, facade, model, or migration integration. For a Laravel developer, think of it as a drop-in replacement for DB::table() that you'd reach for in legacy codebases, standalone scripts, bulk import/reporting jobs, or projects where pulling in Eloquent is overkill. Inside a normal Laravel app you should default to the framework's query builder; reach for FluentPDO when you inherit a PDO-based codebase or need something that runs outside Laravel entirely.

Install and wire up:

composer require envms/fluentpdo
use FluentPDO\FluentPDO;

$pdo    = new PDO('mysql:host=localhost;dbname=app', $user, $pass, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$fpdo = new FluentPDO($pdo);

Inside Laravel, reuse the existing connection so you inherit its config rather than opening a second socket:

$fpdo = new FluentPDO(DB::connection()->getPdo());

First query — the canonical read:

$activeUsers = $fpdo->from('users')
    ->where('active', 1)
    ->orderBy('created_at DESC')
    ->limit(25)
    ->fetchAll();   // array of associative arrays

from() sets the primary table and defaults to SELECT *. The query object is iterable, so foreach ($fpdo->from('users') as $row) also works.

Where to look first: the GitHub README is effectively the manual — it covers from/select/where/join/insertInto/update/deleteFrom and the fetch helpers in a single page. Skim the tests/ directory for the authoritative behaviour of edge cases (aliasing, where() binding variants) since the public docs lag behind the code. The website (fluentpdo.org) mirrors the README but has not been updated recently; treat the source as the source of truth.

First real use case: a read-only reporting endpoint or Artisan command where you want readable joins without Eloquent overhead:

$rows = $fpdo->from('orders')
    ->select('orders.id, users.name, SUM(items.price) AS total')
    ->innerJoin('users ON users.id = orders.user_id')
    ->leftJoin('items ON items.order_id = orders.id')
    ->groupBy('orders.id')
    ->having('total > ?', 100)
    ->fetchAll();

Implementation Patterns

CRUD without a model layer

FluentPDO is at its best when you want explicit SQL-shaped code and plain arrays back:

// INSERT
$id = $fpdo->insertInto('users', ['name' => 'Ada', 'email' => '[email protected]'])->execute();

// UPDATE (returns affected row count)
$affected = $fpdo->update('users', ['active' => 0])
    ->where('last_login < ?', $cutoff)
    ->execute();

// DELETE
$deleted = $fpdo->deleteFrom('users')->where('id', $id)->execute();

insertInto() returns the last insert id (driver-dependent); update()/deleteFrom() return the affected row count.

Where-clause styles

FluentPDO accepts several where() shapes, and mixing them is normal:

->where('id', 1)                          // id = ?
->where('id > ?', 10)                    // operator embedded in the string
->where(['active' => 1, 'role' => 'admin']) // ANDed equality array
->where('name LIKE ?', 'Ada%')           // LIKE with bound value
->whereNot('status', 'banned')

Values are always bound as parameters, so where('email', $request->email) is safe. OR logic is typically expressed inside a single string (->where('a = ? OR b = ?', 1, 2)) rather than through a dedicated orWhere() method.

Joins and smart aliasing

Joins are declared with the ON clause inline, and FluentPDO derives aliases from the table name automatically:

$fpdo->from('article')
    ->leftJoin('user ON user.id = article.user_id')
    ->select('article.*, user.name AS author');

When the same table appears twice, supply an explicit alias in the join string (->join('user u2 ON u2.id = article.editor_id')) and reference the alias in select()/where(). The alias is what makes select() unambiguous — always qualify columns once more than one table is involved.

Subqueries and reusable base queries

Build a base query once and clone it for variations (count, page, export) to avoid duplicated where() chains:

$base = $fpdo->from('orders')->where('created_at > ?', $since);

$total = (clone $base)->select('COUNT(*)')->fetchColumn();
$page  = (clone $base)->limit(50)->offset($offset)->fetchAll();

There is no count() helper — issue a COUNT(*) select yourself.

Fetch helpers

$row   = $fpdo->from('users')->where('id', 1)->fetch();        // single row or false
$pair  = $fpdo->from('roles')->fetchPairs('id', 'name');       // [id => name] lookups
$all   = $fpdo->from('users')->fetchAll();

fetchPairs() is the workhorse for building dropdown data and lookup maps without a loop.

Integrating with a Laravel codebase

  • Reads/reports: wrap DB::connection()->getPdo() and use FluentPDO for complex reporting queries where you want joins expressed as strings rather than builder calls.
  • Legacy bridging: when migrating a non-Laravel project, keep FluentPDO behind a repository class and return plain arrays, then swap implementations later.
  • Transactions: use the same PDO. Either call $pdo->beginTransaction() on the PDO FluentPDO wraps, or use DB::transaction() — do not mix them, because Laravel's transaction counter won't see the raw PDO calls.
  • Mapping to DTOs: hydrate manually (UserData::fromArray($row)), since FluentPDO returns arrays, not objects.
  • Logging: $query->getQuery() returns the SQL string and $query->getParameters() the bound values, which is the only reliable way to see what's actually executed.

Gotchas and Tips

Maintenance risk is the headline. Last release was January 2022. It still works on modern PHP in practice, but you should verify on your PHP version (8.2/8.3) in CI before adopting, and pin the version in composer.json. Treat it as stable-but-unmaintained rather than actively developed.

No hydration, no relationships, no eager loading. Everything is arrays; there is no ->with(). Expect to write your own mapping layer and to be careful about N+1 loops — you're back to hand-written JOINs.

Identifiers are not escaped. Values are bound, but table names, column names, and anything passed to select(), orderBy(), groupBy(), or from() are interpolated. Never pass user input into those. orderBy($request->sort) is a SQL injection vector:

$allowed = ['name', 'created_at'];
$sort = in_array($request->sort, $allowed, true) ? $request->sort : 'id';
$fpdo->from('users')->orderBy("$sort DESC");

Update and delete guard against full-table changes. update()/deleteFrom() without a where() throw — this is a safety feature, not a bug. Add an explicit ->where('1=1') only if you genuinely intend a full-table operation.

Smart aliasing collisions. With self-joins or two tables sharing a column name, ambiguous columns surface as PDO errors, not builder errors. Fix by qualifying every column and giving every repeated table an alias. When debugging, start with getQuery() — an unexpected alias is usually the culprit.

Set PDO attributes explicitly. If you construct the PDO yourself, set ERRMODE_EXCEPTION and DEFAULT_FETCH_MODE => FETCH_ASSOC. Without exception mode, failed queries return false and you'll chase silent failures. When reusing Laravel's PDO, verify the fetch mode you get back — don't assume it matches what the docs show.

Transactions don't integrate with Laravel. DB::transaction() and $pdo->beginTransaction() operate on the same connection but maintain separate state. Pick one API per unit of work; mixing them produces surprising rollback behaviour.

LIMIT/OFFSET with bound parameters. On some drivers, binding limit/offset as strings fails. FluentPDO casts limits to integers internally, but if you build limits by hand or via raw fragments, cast explicitly: ->limit((int) $perPage).

Debugging tips.

  • Dump getQuery() alongside getParameters() before execute()/fetchAll().
  • Older tutorials show $fpdo->debug = true; that behaviour was removed/changed in the envms fork — don't rely on it. Use getQuery() instead.
  • For a slow query, run the getQuery() output through EXPLAIN in your database client; FluentPDO adds no profiling.
  • fetch() returns false on no match; distinguish that from a row of falsy values.

Extension points. Subclass FluentPDO to add project-wide helpers (soft-delete scoping, audit columns, a paginate() that returns [rows, total]). Retrieve the underlying connection with $fpdo->getPdo() when you need raw PDO for bulk inserts or COPY. There is no event/hook system — decorating the class or wrapping queries in your own repository is the intended integration path.

Choose it deliberately. Compared with Laravel's DB::table(), FluentPDO gives you nicer join syntax and a smaller surface area, at the cost of ecosystem support, hydration, and active maintenance. Inside a Laravel app, prefer the framework builder unless you have a concrete reason (legacy parity, standalone script, unusual join ergonomics). Outside Laravel, it's a reasonable lightweight choice — just budget for the fact that you're building the mapping, pagination, and logging layers yourself.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource