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

Druid Php Laravel Package

pixelfederation/druid-php

A PHP client for Apache Druid, the real-time analytics database. Build and run native JSON queries against Druid datasources through a fluent interface, letting your PHP backend fetch analytical results without hand-writing raw HTTP requests.

View on GitHub
Deep Wiki
Context7

Getting Started

pixelfederation/druid-php is a thin, fluent PHP wrapper around Apache Druid's native JSON query API. It gives you query objects (TimeseriesQuery, TopNQuery, GroupByQuery, SelectQuery, SearchQuery, ScanQuery, TimeBoundaryQuery), matching builders under Druid\QueryBuilders\*, plus aggregations and filters, and it POSTs the assembled JSON to the broker, returning decoded arrays. There is no ORM, no connection pool, and no schema layer — what you build maps 1:1 onto what Druid's /druid/v2/ endpoint accepts.

Install:

composer require pixelfederation/druid-php

Before you write a line of code, open the package's composer.json and check the require block. This is a 2017-era library and it almost certainly pins guzzlehttp/guzzle:^6; on a modern Laravel app (Guzzle 7) composer require will fail with a version conflict. Resolve that first — either fork/vendor the package with a loosened constraint, or accept it as a reference implementation and hand-roll the JSON. Do not discover this halfway through a feature.

Smoke-test with the simplest possible query — one datasource, one interval, one count — against the broker on port 8082:

use Druid\Druid;
use Druid\Client;
use Druid\Queries\TimeseriesQuery;
use Druid\Aggregations\CountAggregator;

$druid = new Druid(new Client('http://druid-broker:8082'));

$query = (new TimeseriesQuery())
    ->setDataSource('pageviews')
    ->setIntervals(['2024-01-01T00:00:00.000Z/2024-01-02T00:00:00.000Z'])
    ->setGranularity('all')
    ->addAggregation(new CountAggregator('count'));

$rows = $druid->query($query); // [['timestamp' => ..., 'result' => ['count' => 12345]]]

Where to look first: src/Queries/ for the six query types, src/QueryBuilders/ for the fluent entry points, src/Aggregations/ (CountAggregator, LongSumAggregator, DoubleSumAggregator, JavascriptAggregator, …) and src/Filters/ (SelectorFilter, RegexpFilter, InFilter, BoundFilter, …). Keep Druid's own native-query documentation open beside it — the class names mirror the JSON keys almost exactly, which is the fastest way to figure out what a builder method is called.

Implementation Patterns

Bind it once, configure it from config. Don't new Druid(...) in controllers. Register a singleton in a service provider, pull the broker URL from config/druid.php (env-driven), and construct the underlying Guzzle client there so you can set timeout, connect_timeout, and retry middleware in one place:

$this->app->singleton(Druid::class, function ($app) {
    $http = new \GuzzleHttp\Client([
        'base_uri' => config('druid.broker'),
        'timeout'  => 10,
    ]);
    return new Druid(new Client($http));
});

One query object per report/panel. The biggest maintainability win is treating each dashboard panel or report as a named class — PageviewsByCountryQuery, DailyActiveUsersQuery — that returns a configured query object. The fluent builders make this tempting to inline in a controller; resist it, because Druid queries are the kind of thing that gets tweaked weekly and you want them testable and greppable.

Compose filters and post-aggregations rather than branching on strings. Use GroupByQueryBuilder with setDataSource(), addDimension(), addAggregation(), addFilter(), setGranularity(), addOrdering(), setLimit(), then ->build(). Post-aggregations (addPostAggregation()) and setHaving() are how you get ratios and thresholds — the aggregator name you pass is the key you reference downstream, so treat those strings as constants shared between aggregation and post-aggregation definitions.

Map results to DTOs immediately. Druid returns ragged arrays whose shape differs per query type — groupBy nests the metrics under an event key alongside timestamp and version, timeseries returns result, topN returns result as a list. Wrap the Druid::query() call in a small mapper so the rest of the app never touches raw Druid output.

Test by asserting the payload, not the network. Inject a Guzzle MockHandler into the Client, run a query, and assert on the decoded JSON body. That catches the real class of bug here: a misspelled granularity, an interval without the / separator, an aggregator missing its name.

Cache aggressively above the client. Druid has its own result cache (useCache / populateCache in the query context), but for a dashboard you'll still want Laravel's cache in front, keyed on a hash of the query array. json_encode($query) is a fine cache key.

Use it for scheduled/export jobs too. The same query objects work fine in an artisan command feeding a CSV export or a nightly rollup — nothing about the package is request-scoped.

Gotchas and Tips

  • Archived since 2017 — assume no upstream fixes. It predates Druid SQL being the default idiom, sketch aggregators (ThetaSketch, Quantiles, DataSketches), joins, and most modern query context options. Budget for a fork in your vendor directory rather than expecting a PR to land. If your queries are complex, seriously consider skipping the library and POSTing plain JSON via Guzzle — you get SQL support and every new Druid feature for free.
  • No SQL. The package speaks native JSON only. If you want /druid/v2/sql, either use the underlying Guzzle client directly or a separate client. Mixing the two is fine and often pragmatic — SQL for ad-hoc, the builder for fixed reports.
  • Interval format is the #1 silent failure. Intervals are start/end ISO-8601 strings joined by a slash, in UTC. 2024-01-01/2024-01-02 fails; 2024-01-01T00:00:00.000Z/2024-01-02T00:00:00.000Z works. Empty results are usually a malformed interval, not a missing datasource.
  • Granularity accepts 'all', 'none', 'day', 'hour', etc., or a duration/period object. 'all' is what you want for a single scalar; forgetting it gives you one row per day and a query that "looks wrong."
  • Aggregator names must be unique and stable. Post-aggregations and having clauses reference them by string. Renaming a count aggregator silently breaks the post-aggregation; Druid will reject the query with a 500 and a JSON body explaining which reference was unresolved.
  • Error handling. Guzzle throws RequestException on any 4xx/5xx, and Druid's useful diagnostic lives in the response body, not the message. Always log $e->getResponse()->getBody()->getContents() — it names the offending field.
  • Pagination is not automatic. groupBy and select are threshold-limited (setThreshold() / pagingSpec). Large cardinality dimension combinations will be silently truncated at the default threshold. Set it explicitly and don't assume you got everything.
  • Timeouts. The default Guzzle timeout is zero (infinite) on some paths. An unbounded groupBy over a wide interval will hang a PHP-FPM worker until it dies. Always set a timeout, and set Druid's own query timeout context alongside it.
  • Extending is straightforward — that's the redeeming feature. Aggregations and filters are small classes implementing a toArray()/jsonSerialize() contract. Adding a new Druid aggregator the 2017 release doesn't know about is a 20-line class, which is why this package still works as a base despite being archived.
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.
teamzac/geocoder
ideatica/config-writer
keaton-php/pipe-dream
jacksonwebservices/laravel-user-suite
maksimru/conditional-laravel-debugbar
gismatul/nova-support-chat
gismatul/laravel-support-chat
networkrailbusinesssystems/oracle-api
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel