composer require mostafax/dynamic-hybrid-reporting-engine
Open package page ↗
One JSON DSL. MySQL + MongoDB. Any report, any export, zero custom query code.
Every project eventually needs reports — but writing separate queries per source, per format, per tenant, is unsustainable.
Every report — regardless of source — passes through the same hardened pipeline before a single database call is made.
A JSON object (or PHP array) describes the report: source, table, fields, filters, aggregations, grouping, sorting, pagination.
DslParser converts raw input into a typed, immutable QueryDefinition value object with a stable MD5 hash.
Enforces limits: max rows, max joins, max conditions, max aggregations. Rejects unknown operators before any DB call.
QuerySanitizer regex-whitelists every identifier. FieldAccessControl strips globally-denied and role-denied columns.
QueryCacheManager checks Redis by definition hash. Cache keys are tenant-scoped — one tenant's cache never leaks to another.
Resolves the correct adapter from the registry: MySQLDataSource or MongoDataSource. Custom adapters can be registered at runtime.
MySQL uses Laravel Query Builder with correct LIMIT/OFFSET. MongoDB builds a $facet pipeline — data + totalCount in one round-trip.
Returns ExecutionResult with data, total, and ExecutionMetadata (time, memory, cache hit). Fires ReportExecuted event.
The same JSON structure works for both MySQL and MongoDB. Only the "source" field changes.
One schema for MySQL and MongoDB — fields, filters (nested AND/OR), aggregations, joins, group-by, order-by, pagination.
Register custom data sources at runtime via DataSourceResolver::register(). Add PostgreSQL, Elasticsearch, or any HTTP API.
Results cached by definition hash. Cache keys are tenant-scoped and tag-invalidated when a report is updated.
Identifier regex whitelist on every column/table name. Values are always PDO-bound — never interpolated into SQL.
Global deny list + per-role deny list with wildcard patterns. Denied fields are silently stripped; denied filter fields throw immediately.
CSV (UTF-8 BOM streaming), XLSX (PhpSpreadsheet), and JSON — all streamed directly to the client, no temp file needed.
Tenant ID injected into every query automatically. MySQL gets a WHERE clause; MongoDB gets a $match stage — zero boilerplate.
Embed KPIs, charts, tables, filters, and export buttons in any Blade template with a single tag. Bootstrap & Tailwind. RTL ready.
Optional livewire:report-widget adds real-time filtering, sorting, and pagination — zero JavaScript required.
Domain entities for Dashboards and Widgets (table, bar chart, line chart, KPI card, heatmap). Each widget binds to a saved report.
dhr_report_executions records every run: ms, rows, memory, cache hit, status — full observability out of the box.
variant="flat|gradient|dark|glass|minimal|bold" — live-switchable CSS themes with smooth transitions and a color picker. Zero JS required.
Auto-built $match → $group → $project → $sort → $facet — data + totalCount in one round-trip. BSONArray/ObjectId normalised automatically.
The Facade, the HTTP API, and the export endpoint — all work out of the box.
The same aggregation query — completely different latency when Redis cache is warm versus cold.
Six independent security controls fire before the first database call is made.
| Layer | Class | What It Protects Against |
|---|---|---|
| Identifier Injection | QuerySanitizer | Regex whitelist on every column/table name — ; DROP TABLE is rejected before touching the builder |
| Value Injection | Laravel Query Builder | All values passed as PDO bindings — never string-interpolated into SQL or Mongo queries |
| Field Exposure | FieldAccessControl | Globally-denied fields (password, api_key, …) stripped silently; denied filter fields throw 422 |
| Query Limits | QueryValidator | Max rows, joins, conditions, aggregations per query — prevents resource exhaustion |
| Rate Limiting | QueryLimitMiddleware | Per-user request budget via Laravel RateLimiter; remaining budget exposed in response headers |
| Tenant Isolation | ReportEngine + Builders | Tenant ID auto-appended to every query — one tenant can never read another's data |
Install, publish, migrate — then the REST API is live and the Facade is ready.