adelf/ventriloquist
adelf/ventriloquist is a lightweight Laravel/PHP package that lets one object speak on behalf of another, offering a simple way to delegate calls without tight coupling. Easy to drop into existing projects; see the README for setup and usage details.
Ventríloquo is a small, single-author package (last release August 2020, essentially no downstream dependents) that wraps complex SQL/Eloquent queries behind a friendlier interface. Treat the README as the only authoritative spec — the surface area is small enough to read end-to-end before you commit.
Install and wire it up
composer require adelf/ventriloquist
Then check two things immediately:
vendor/adelf/ventriloquist/composer.json and look at extra.laravel.providers / extra.laravel.aliases. If they're absent, register manually in config/app.php (Laravel ≤ 7 style) or in bootstrap/providers.php (Laravel 11 style).php artisan vendor:publish --tag=ventriloquist exists, run it and commit the resulting config so behaviour is reproducible across environments.Where to look first
README.md — intended API and the one worked example the author wrote.src/ — usually one service provider, one facade, and a query-builder/grammar class. The grammar class is where the actual SQL manipulation happens; read it before trusting it against your schema.tests/ — if present, these are your best documentation of edge cases. If absent (likely for a 1-star package), budget time for your own characterization tests.First use case
Pick a query you already have that is genuinely awkward in plain Eloquent — a multi-join report with conditional aggregates, or a filter set driven by request input. Wrap that with the package and compare against a hand-written baseline. Don't start by rewriting your whole data layer.
Isolate it behind a repository or query object. Since the package is unmaintained, never let its builder leak across your codebase. Keep it inside a single query class:
final class ActiveSubscriptionsQuery
{
public function __construct(private Ventriloquist $builder) {}
public function forAccount(int $accountId): self { /* ... */ return $this; }
public function toBuilder(): \Illuminate\Database\Eloquent\Builder
{
return $this->builder->/* package-specific chaining */->toEloquent();
}
}
If the package only exposes raw SQL (a common shape for "friendly complex query" libraries), the toBuilder() boundary is where you convert back to Eloquent so the rest of the app stays idiomatic.
Use it as a replacement for sprawling when() chains. The main legitimate win is turning a wall of ->when($request->foo, fn($q) => ...) into a declarative filter definition. Compose filters in a dedicated Filter class that delegates to the package.
Keep raw Eloquent for the 90% case. Reserve Ventríloquo for reporting endpoints, dashboards, and export queries where the join/aggregate complexity is real. Using it for User::where('email', $x)->first() adds a dependency for zero benefit.
Test at the SQL boundary. Assert against ->toSql() and ->getBindings() rather than full result sets where possible — it catches the package generating subtly wrong SQL without needing fixtures:
$this->assertStringContainsString('group by', strtolower($query->toSql()));
Pin the version and lock the transitive graph. composer.json:
"adelf/ventriloquist": "1.0.*"
and commit composer.lock. With a 2020-vintage package this is your only protection against a surprise breaking release (unlikely) or a yanked tag (more likely).
Watch pagination and eager loading. If the package returns a Builder, Laravel's paginate(), with(), and cursor() still work. If it returns custom result objects or raw SQL strings, you lose those and must re-implement them — check this before adopting.
Maintenance risk is the headline issue, not features. Zero dependents, one star, and a last release in 2020 means: no security patches, no Laravel 10/11 verification, and no community answers when it misbehaves. Budget a fork if you depend on it long-term. Forking a package this small (likely a few hundred lines) is often cheaper than adopting it.
PHP and Laravel version compatibility is unverified. A 2020 release predates PHP 8.1/8.2 and Laravel 9–12. Before anything else, run composer why-not adelf/ventriloquist and confirm it installs against your framework version. Watch for:
str_contains / str_starts_with polyfill assumptions (or the reverse — usage of functions that didn't exist on PHP 7).Illuminate\Support\Str / Arr helpers.Builder methods that were dropped in Laravel 9+.Service provider auto-discovery may silently not fire. If the facade class isn't found, that's the cause — not a namespace typo. Register it manually.
SQL injection surface. "Friendly interface for complex queries" usually means string interpolation somewhere. Grep src/ for DB::raw, ->whereRaw, and sprintf. Anything that concatenates identifiers (column names, directions, table names) from user input is a red flag; wrap those in a strict allow-list on your side regardless of what the package does.
Debugging: drop to the underlying builder. dd($query->toSql(), $query->getBindings()) is faster than reading the package internals. If the package exposes no toSql, that itself is a reason to reject it. Consider installing laravel/telescope or barryvdh/laravel-debugbar temporarily to inspect emitted queries in a real request.
Extension points are probably thin. Small packages typically offer one of: custom filter classes, a config-driven grammar, or nothing. If you need behaviour it doesn't support, prefer wrapping the output over patching internals — subclassing an unmaintained class couples you to its private structure.
Config quirks. If a published config exists, check for hard-coded defaults around LIMIT, identifier quoting, or date formatting. Database-specific quirks (Postgres :: casts, MySQL strict mode GROUP BY) are a common source of "works locally, 500s in production."
Escape hatch. Keep at least one complex query implemented in raw Eloquent alongside the package version during evaluation. If the package's output isn't byte-for-byte equivalent (modulo whitespace), stop and investigate before shipping. For a package with no community, your own characterization tests are the only safety net.
How can I help you explore Laravel packages today?