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

Query Builder Laravel Package

makinacorpus/query-builder

A fluent SQL query builder for PHP that lets you construct complex SELECT, INSERT, UPDATE, and DELETE queries programmatically through a clean, chainable API. Lightweight and framework-agnostic, it integrates easily into Laravel or any PHP application.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require makinacorpus/query-builder and then open README.md first, followed by src/Query.php and src/Driver/. The public surface is intentionally small: a Query object plus a set of driver/dialect classes. Because this is not a Laravel package (no service provider, no facade, no auto-discovery), you will not find config/query-builder.php — you wire it yourself.

First use case: prototype a portable SELECT in php artisan tinker and inspect the emitted SQL and bindings before wiring it into a real query path.

use Makinacorpus\QueryBuilder\Query;
use Makinacorpus\QueryBuilder\Driver\MySQL; // verify exact driver class names in src/Driver

$q = (new Query(new MySQL()))
    ->select('id', 'email')
    ->from('users')
    ->where('active = ?', [true]);

echo $q->getSql();          // inspect the SQL string
print_r($q->getParameters()); // inspect bindings

Then execute through Laravel's own connection so you keep Laravel's parameter binding and events:

$rows = DB::select($q->getSql(), $q->getParameters());

The mental model: this package generates SQL and bindings; Laravel's DB facade still executes them. Do not expect Eloquent models, scopes, or lazy loading here.

Implementation Patterns

Dialect-bound singleton via a service provider. Bind the driver once so your app doesn't reconstruct it per query.

// app/Providers/QueryBuilderServiceProvider.php
$this->app->singleton(Query::class, function () {
    $driver = match (config('database.default')) {
        'mysql'  => new \Makinacorpus\QueryBuilder\Driver\MySQL(),
        'pgsql'  => new \Makinacorpus\QueryBuilder\Driver\PostgreSQL(),
        'sqlite' => new \Makinacorpus\QueryBuilder\Driver\SQLite(),
        default  => new \Makinacorpus\QueryBuilder\Driver\MySQL(),
    };
    return new Query($driver);
});

SQL + bindings DTO. Never return a bare string. Wrap it so execution helpers can't accidentally drop parameters:

final class SqlStatement {
    public function __construct(public string $sql, public array $bindings) {}
}

function compile(Query $q): SqlStatement {
    return new SqlStatement($q->getSql(), $q->getParameters());
}

function run(SqlStatement $s): array {
    return DB::select($s->sql, $s->bindings);
}

Cross-dialect generation for reports/exports. The real day-to-day value: emit MySQL syntax for one tenant, PostgreSQL for another, without branching your PHP:

$driver = $tenant->engine === 'pgsql' ? new PostgreSQL() : new MySQL();
$sql = (new Query($driver))->select('*')->from('invoices')->where('paid = ?', [false]);

Testing / snapshotting. Because output is a deterministic string, snapshot the SQL in tests instead of hitting a database. This also documents your SQL for reviewers. Assert the string and the bindings array — a query is always the pair.

Composition over inheritance. If your application has a reporting layer, keep it returning SqlStatement values and feed them to one runner. This keeps makinacorpus out of your domain classes and makes it swappable if you later move to a different generator.

Gotchas and Tips

  • No Eloquent integration, no scopes, no relationships. Do not try to reach model state from the query builder. If you need model behavior, keep the model on the Laravel side and only hand the builder table/column names.
  • You own identifier quoting and parameter safety. The builder should parameterize values, but never interpolate raw user input into select()/from()/orderBy() strings. Validate column names against a whitelist.
  • Dialect differences will bite you cross-engine. LIMIT/OFFSET vs TOP, RETURNING vs OUTPUT, reserved words, and boolean literal handling (true vs 1) are exactly the places a driver-agnostic builder must differ. Test the same logical query against each driver you actually target.
  • Small ecosystem (7 stars, 0 dependents). Treat the source as the spec: check composer.json for PHP version constraints, inspect src/Driver/ for what's actually implemented, and pin a version in composer.lock. There is no large StackOverflow corpus to lean on for edge cases.
  • Manual registration only. No extra.laravel in this package, so no auto-discovery — you add the provider yourself, or skip the container entirely and instantiate where needed.
  • Bindings are separate from SQL. getSql() alone is a debugging aid, not something to hand to DB::raw(). Always pass the matching bindings, or you will ship an injection or an SQLSTATE mismatch.
  • Missing driver = your extension point. If your engine (say, a cloud warehouse) isn't covered, extend the base driver/dialect class rather than regex-patching SQL strings. That keeps upgrades cheap.
  • Debugging tip: dump sql and bindings side-by-side; when a query fails, 90% of the time the driver was constructed with the wrong class, not a builder bug.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource