thomask/druid-bundle
A Symfony bundle that integrates Apache Druid analytics into PHP applications. It registers Druid client services and configuration in the Symfony container, letting you run SQL and native Druid queries directly from your services with minimal setup.
This is a Symfony 2/3 bundle, not a Laravel package — it ships no service providers, facades, or Laravel bindings. If you are in a Laravel codebase, treat it as reference code for the client class rather than something you composer require into a Laravel app.
For a Symfony app:
composer require thomask/druid-bundle
Register the bundle in app/AppKernel.php:
new Tmk\Bundle\DruidBundle\TmkDruidBundle(),
Configure in config.yml:
tmk_druid:
host: localhost
port: 8082 # broker
indexer_host: localhost
indexer_port: 8090 # overlord / indexer
timeout: 30
First use case — pull the client service and run a native Druid JSON query:
$client = $this->get('tmk_druid.client');
$query = [
'queryType' => 'timeseries',
'dataSource' => 'wikipedia',
'granularity' => 'hour',
'aggregations' => [['type' => 'count', 'name' => 'rows']],
'intervals' => ['2014-01-01T00:00:00Z/2014-01-02T00:00:00Z'],
];
$rows = $client->query($query);
Read the bundle Client class in Client/Client.php first — that is the only file that matters day-to-day; everything else is DI wiring.
Wrap the client, don't leak it into controllers. Druid's response shape is stable but unforgiving; wrap tmk_druid.client behind a DruidService so you can normalize responses, add retries, and (in Laravel) bind it from a service provider without touching callers.
Two distinct APIs, two distinct clients. The bundle exposes broker queries (/druid/v2/) separately from indexer operations (/druid/indexer/v1/ — create/kill datasources, enable/disable segments). Do not mix hosts: the broker port is 8082, the overlord is 8090. Getting these crossed yields 404s that look like Druid is down.
Prefer raw native JSON over partial builders. Druid evolves faster than this bundle; if the client exposes a query builder, treat it as a helper for the 3–4 query types you actually use (timeseries, topN, groupBy, select) and fall back to raw arrays for everything else (scan, timeBoundary, segmentMetadata).
Datasource lifecycle belongs in a command, not a controller. Creating or killing a datasource from a web request is a good way to take Druid down mid-request. Put createDataSource / deleteDataSource calls behind a Symfony Console command and require explicit confirmation.
Cache aggressively at the application layer. Druid is fast but every query is a fresh HTTP round-trip with no connection reuse. Wrap query() in a short-lived cache keyed by the serialized query payload (TTL 30–300s for dashboards).
Log the raw request and response. When a query fails, Druid's error is in the JSON body — not the HTTP status. Log the full request body before send and the full body on non-2xx.
ServiceProvider, no Facade, no config publishing. Symfony 2 bundle auto-registration only. In Laravel you'd copy Client/Client.php and its query classes into app/Services/Druid, replace any Symfony DI hints with constructor-injected Guzzle, and bind via a provider.{"error": "..."} for query syntax mistakes, bad intervals, and missing datasources alike. The bundle may surface these as generic exceptions; check the body, not the exception message.[], not null. Guard against current($rows) returning false and against count($rows) === 0 when rendering — a zero-row timeseries is not an error.DateTimeImmutable and be explicit about UTC — Druid returns "2014-01-01T00:00:00.000Z" regardless of your app timezone.timeout explicitly in config; a groupBy over 30 days can easily exceed 30s and the HTTP client will drop the response while Druid keeps computing.resultFormat, context, SQL endpoint). If your Druid is newer than ~0.9, assume the bundle knows none of the modern options and pass them through raw arrays.How can I help you explore Laravel packages today?