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

Fluentaql Laravel Package

laravel-freelancer-nl/fluentaql

A fluent SQL builder for Laravel that lets you compose queries through a chainable PHP API instead of raw strings, keeping statements readable, parameterized, and easy to maintain while retaining full control over the generated SQL.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a standalone, framework-agnostic fluent builder that emits AQL strings plus a bind-variable array — it does not talk to ArangoDB for you. You pair it with the official PHP driver (triagens/arangodb).

Install and pin. The package is archived (last release 2022-05-16), so pin an exact version and commit the lockfile:

composer require laravel-freelancer-nl/fluentaql:0.4.*   # confirm latest tag with `composer show -a`

Where to look first, in this order:

  1. README.md — the fluent surface, one example per AQL clause.
  2. tests/ — for an archived package this is the real documentation; each test shows a builder call next to its expected AQL output.
  3. src/ — specifically the main AQL entry class and the clause classes, to confirm method names and argument order before you commit to them in your codebase.

First use case — a filtered, paginated read. Build the query, then hand the string and binds to the driver:

use LaravelFreelancerNL\FluentAQL\AQL;

$aqb = new AQL();

$aqb->for('user', 'users')                       // FOR user IN users
    ->filter('user.status == @status')           // FILTER ...
    ->sort('user.createdAt DESC')
    ->limit(2, 20)                               // LIMIT 20, 2  (offset, count)
    ->return('user');                            // RETURN user

$query    = $aqb->getQuery();      // "FOR user IN users FILTER user.status == @status ..."
$bindVars = $aqb->getBindVars();   // ['status' => 'active', ...]

Method names and bind-registration style (bind() vs. values passed alongside a clause) vary between minor versions. Verify against src/ at your pinned tag — the examples here follow the README's fluent style.

Wire it up once. Create a thin service that owns the connection and a factory method for builders, then inject that instead of new AQL() everywhere:

final class AqlClient
{
    public function __construct(private Connection $connection) {}

    public function query(): AQL
    {
        return new AQL();
    }

    public function all(AQL $aqb): array
    {
        return $this->connection
            ->query($aqb->getQuery(), $aqb->getBindVars())
            ->getAll();
    }
}

Implementation Patterns

Repository wrapper per collection. Don't leak builders into controllers. One repository per document collection, each method returning domain objects:

final class UserRepository
{
    public function __construct(private AqlClient $aql) {}

    public function activeSince(DateTimeInterface $since, int $limit = 50): array
    {
        $aqb = $this->aql->query()
            ->for('u', 'users')
            ->filter('u.status == @status AND u.createdAt >= @since')
            ->sort('u.createdAt DESC')
            ->limit($limit)
            ->return('u');

        return array_map(
            fn ($row) => User::fromDocument($row),
            $this->aql->all($aqb->bind(['status' => 'active', 'since' => $since->format('c')]))
        );
    }
}

Conditional filter composition. The builder's value is in assembling optional predicates without string concatenation:

$aqb->for('u', 'users');

if ($status = $criteria->status()) {
    $aqb->filter('u.status == @status')->bind(['status' => $status]);
}

if ($search = $criteria->search()) {
    $aqb->filter('LIKE(u.name, @search, true)')->bind(['search' => "%{$search}%"]);
}

$aqb->sort('u.name ASC')->limit($perPage)->return('u');

Keep every filter() call a complete boolean expression; don't try to open a parenthetical in one call and close it in the next.

Bind everything that is a value. Values go through binds so AQL compiles once and the server caches the plan. Identifiers (collections, attribute paths) are not bindable in AQL — those must be validated against an allowlist in your code, never interpolated from request input.

Raw AQL escape hatch. For clauses the builder doesn't model well (complex COLLECT ... AGGREGATE, PRUNE, some graph options), use the raw-AQL method to splice in a fragment while still handing off your binds. Treat any raw fragment as a code-level constant — review it in PRs like SQL.

Testing without a database. The builder is pure string generation, so a unit suite that asserts getQuery() and getBindVars() catches regressions in milliseconds. Follow the package's own test style: assert the full query string plus the bind array, not just assertStringContainsString. Keep a separate, smaller integration suite against a disposable ArangoDB (Docker or arangodb/test image) for the queries where the compiler matters.

Reuse and composition. A builder instance is mutable, so treat it as a one-shot value object: create, compose, execute, discard. If you need to share a common base (FOR u IN users FILTER u.tenantId == @tenant), export a closure that takes a fresh builder and returns it, rather than storing a half-built builder in a service property.

Write operations. Use the builder's insert/update/replace/remove/upsert clauses so you get the RETURN NEW / RETURN OLD handling and OPTIONS { ignoreErrors: true } in one place, and keep the document payload as a bind variable rather than an inline object literal.

Graph traversals. Model them as a dedicated repository method returning raw paths; mapping traversal results to domain objects generically tends to obscure the vertices/edges shape you actually need.

Gotchas and Tips

  • It is archived. No security patches, no PHP 8.3+ compatibility fixes, no AQL 3.11+ features. Decide deliberately: pin and vendor, or fork the repo into your own namespace. If your AQL usage is modest, hand-written query constants plus binds may cost less than owning a fork.
  • Despite the vendor name, there is no Laravel integration. No service provider, no facade, no config file, no DB-style connection management. You register the driver connection and the AqlClient yourself in a service provider.
  • Bind-variable keys are auto-generated. Don't build logic that assumes specific keys like @0 or @value1; always read getBindVars() as the source of truth and pass the pair together. Mixing a manually written bind name with the builder's generated one is a common cause of "bind parameter 'x' was not declared."
  • Clause order is enforced (or should be). AQL requires FOR → FILTER → SORT → LIMIT → RETURN and LET/COLLECT in specific positions. If the builder lets you append out of order you'll get a server-side parse error, not a builder exception — so keep your composition code in clause order and don't rely on the builder to reorder.
  • Debugging loop: dump getQuery() and getBindVars() side by side, then paste the query into the ArangoDB web UI's AQL editor with the binds as JSON. Isolate whether the failure is your string, your binds, or your data before touching PHP.
  • Identifier validation is on you. Anything spliced as a collection name or attribute path is an injection surface. Allowlist collection names; validate sort fields against a map of 'created' => 'u.createdAt' rather than passing request values through.
  • Empty FILTER results vs. errors. A filter that references a missing attribute silently drops documents in AQL. When a query "returns nothing," check attribute spelling (including case) and whether the value type matches — AQL is strict about 1 vs. "1".
  • Check PHP constraint compatibility early. A 2022 package may cap at ^7.4|^8.0; if you're on 8.3+ you'll need --ignore-platform-req or a fork. Verify before designing around it.
  • Extension points are the clause classes. Because the builder is just composing clause objects, adding an unsupported AQL construct is usually a small subclass or a raw-AQL call rather than a rewrite — but any extension you write is now your maintenance burden, so document it inline.
  • Watch the score, not just the stars. 12 stars, 0 dependents, one maintainer, archived: treat this as a vendored utility, not a dependency you can expect to upgrade your way out of. Budget time for a fork on day one if the project is long-lived.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib