English 🌐
📦  mostafax/dynamic-hybrid-reporting-engine
متاح على Packagist ثبّته في ثوانٍ عبر Composer composer require mostafax/dynamic-hybrid-reporting-engine افتح صفحة الـ Package ↗

محرك التقارير
الهجين الديناميكي

DSL واحد بـ JSON — MySQL + MongoDB — أي تقرير، أي تصدير، صفر كود مخصص

📤
Result
JSON / CSV / XLSX
🗄️
MySQL / MongoDB
Adapter ينفّذ
⚙️
ReportEngine
يتحقق ويوجّه
📋
JSON DSL
عرّف تقريرك

التقارير دايماً الجزء الصعب

كل مشروع بيحتاج تقارير في النهاية — لكن كتابة Queries منفصلة لكل مصدر، لكل Format، لكل Tenant، مش قابل للتوسع.

😩  الطريقة القديمة تقرير واحد = Class كامل
⏳ MySQLRevenueReport.php+80 سطر
⏳ MongoAnalyticsReport.php+110 سطر
⏳ ExcelExportController.php+60 سطر
⏳ CsvExportController.php+45 سطر
...×12 تقرير
// متكرر في كل تقرير $query = DB::table('orders') ->join('customers', ...) ->where('tenant_id', $tenantId) ->groupBy('status') ->selectRaw('SUM(amount) as total...');
⚠ تقرير جديد = 3–5 ملفات جديدة في كل مرة
  • كل تقرير جديد بيتطلب Class PHP من الصفر
  • Query Logic منفصل وغير متوافق بين MySQL وMongoDB
  • كود التصدير (Excel, CSV, JSON) متكرر في كل Controller
  • لا Validation مركزي — مخاطر Injection مش ملحوظة
  • Multi-tenant Filtering بيتحط يدوياً في كل Query
  • Widgets الـ Dashboard ملهاش عقد Binding موحد
  • الـ Caching متنسوخ بشكل مش متسق في التقارير المختلفة

Execution Pipeline من 8 خطوات

كل تقرير — بغض النظر عن المصدر — بيمر على نفس الـ Pipeline المحصّن قبل أي استدعاء للـ Database.

Entry

DSL Input

JSON object (أو PHP array) بيوصف التقرير: المصدر، الجدول، الحقول، الـ Filters، الـ Aggregations، الترتيب، الـ Pagination.

1
2
Core

DslParser

DslParser بيحوّل الـ Input الخام لـ QueryDefinition مكتوبة بالنوع (Immutable) مع Hash MD5 ثابت.

Validation

QueryValidator

بيطبّق الـ Limits: أقصى Rows، أقصى Joins، أقصى Conditions، أقصى Aggregations. بيرفض أي Operator غير مسموح.

3
4
Security

QuerySanitizer + ACL

QuerySanitizer بيتحقق من كل Identifier بـ Regex. FieldAccessControl بيشيل الـ Fields المحظورة.

Cache

Redis Cache Probe

QueryCacheManager بيشوف Redis بالـ Hash. مفاتيح الـ Cache مقسّمة بالـ Tenant — بيانات Tenant ميتسرّبش لتاني.

5
6
Routing

DataSourceResolver

بيجيب الـ Adapter الصح من الـ Registry: MySQLDataSource أو MongoDataSource. ممكن تضيف Custom Adapters وقت الـ Runtime.

Execution

Adapter ينفّذ

MySQL بيستخدم Laravel Query Builder. MongoDB بيبني Pipeline $facet — Data + totalCount في رحلة واحدة للـ Database.

7
8
Result

ExecutionResult + Event

بيرجع ExecutionResult بالـ Data والعدد الكلي. بيحفظ في Redis وبيطلق Event ReportExecuted.

كل حاجة جاهزة — مفيش حاجة تربطها

🔀

Unified JSON DSL

Schema واحدة لـ MySQL وMongoDB — Fields، Filters (AND/OR متداخل)، Aggregations، Joins، Group-By، ترتيب، Pagination.

🔌

Adapters قابلة للتوسع

سجّل Custom Data Sources وقت الـ Runtime. ضيف PostgreSQL، Elasticsearch، أو أي HTTP API.

Redis Query Cache

النتائج محفوظة بالـ Hash. مفاتيح الـ Cache مقسّمة بالـ Tenant وبتتبطل تلقائياً عند التحديث.

🔐

حماية من Injection

Regex Whitelist على كل اسم Column أو Table. القيم دايماً PDO Binding — محتش في SQL أو MongoDB أبداً.

🛡️

Field-Level ACL

Global Deny List + Role-Based Deny List مع Wildcard Patterns. الـ Fields المحظورة بتتشال تلقائياً.

📤

3 Export Formats

CSV (UTF-8 BOM Streaming)، XLSX (PhpSpreadsheet)، وJSON — كلهم Streamed للـ Client مباشرة بدون Temp Files.

🏢

Multi-tenant Ready

Tenant ID بيتحط في كل Query تلقائياً. MySQL بياخد WHERE Clause، وMongoDB بياخد $match Stage — صفر كود إضافي.

🧩

Blade Components أصلية

ضمّن KPIs وCharts وTables وFilters في أي Blade Template بـ Tag واحد. Bootstrap وTailwind. RTL جاهز.

Livewire Integration

اختياري — livewire:report-widget بيضيف Real-time Filtering وSorting وPagination بدون أي JavaScript.

📊

Dashboard و Widgets

Domain Entities لـ Dashboards وWidgets (Table، Bar Chart، Line Chart، KPI Card، Heatmap). كل Widget مربوط بـ Report محفوظ.

📋

Execution Audit Log

dhr_report_executions بتسجّل كل Run: Execution Time، Rows، Memory، Cache Hit، Status.

🎨

6 ثيمات للـ KPI Widget

variant="flat|gradient|dark|glass|minimal|bold" — ثيمات CSS قابلة للتبديل اللحظي مع Color Picker وTransitions سلسة.

🍃

MongoDB $facet Pipeline

بيبني $match → $group → $project → $sort → $facet تلقائياً — Data + totalCount في رحلة واحدة. BSONArray وObjectId بتتحوّل تلقائياً.

من الصفر لـ Report في دقايق

الـ Facade، وHTTP API، وExport Endpoint — كلهم شغّالين من أول لحظة.

<!-- KPI Cards — variant: flat | gradient | dark | glass | minimal | bold --> <x-reporting-engine::kpi-widget report="revenue-by-status" :cols="3" color="#0077A8" variant="glass" /> <!-- Chart.js مع SSR Table Fallback (بدون JS) --> <x-reporting-engine::chart-widget report="revenue-by-status" chart-type="bar" label-column="status" value-column="total_revenue" /> <!-- MongoDB Pie Chart --> <x-reporting-engine::chart-widget report="demo-mongo-revenue-by-channel" chart-type="pie" label-column="channel" value-column="total_revenue" /> <!-- Table + Filter + Export --> <x-reporting-engine::report-filter report="orders" :inline="true" /> <x-reporting-engine::report-widget report="orders" :show-export="true" :per-page="15" /> <!-- Livewire: Real-time Filter + Sort + Paginate --> <livewire:report-widget report="orders" :per-page="20" :show-filters="true" :show-export="true" />
// نفّذ Report عبر الـ Facade — من أي مكان في التطبيق use Mostafax\ReportingEngine\Support\Facades\ReportingEngine; $result = ReportingEngine::run([ 'source' => 'mysql', 'table' => 'orders', 'fields' => [ ['column' => 'id', 'alias' => 'order_id'], ['column' => 'customer_name'], ], 'aggregations' => [ ['function' => 'sum', 'column' => 'amount', 'alias' => 'revenue'], ['function' => 'count', 'column' => 'id', 'alias' => 'total' ], ], 'filters' => [ 'operator' => 'AND', 'conditions' => [ ['field' => 'status', 'operator' => '=', 'value' => 'completed'], ], ], 'group_by' => ['status'], 'order_by' => [['column' => 'revenue', 'direction' => 'desc']], 'pagination' => ['page' => 1, 'per_page' => 25], ]); // $result هو ExecutionResult Value Object $result->data; // Array بالـ Rows $result->total; // Total Count (بدون LIMIT) $result->metadata->executionTimeMs; // مثلاً 12.4 $result->metadata->cacheHit; // true لو جاي من Redis
// POST /api/reporting/run — Ad-hoc Execution بدون حفظ POST /api/reporting/run Content-Type: application/json { "definition": { "source": "mongodb", "table": "analytics", "aggregations": [ { "function": "count_distinct", "column": "userId", "alias": "unique" } ], "pagination": { "page": 1, "per_page": 10 } } } // POST /api/reporting — احفظ Report POST /api/reporting { "name": "إيرادات حسب الحالة", "definition": { ... } } // POST /api/reporting/{id}/run — نفّذ Saved Report POST /api/reporting/{id}/run { "page": 2, "per_page": 50 } // GET /api/reporting/{id}/export — Stream File Download GET /api/reporting/{id}/export?format=xlsx GET /api/reporting/{id}/export?format=csv GET /api/reporting/{id}/export?format=json
// اعمل Custom Adapter — مثلاً PostgreSQL class PostgreSQLDataSource implements DataSourceInterface { public function supports(string $sourceType): bool { return $sourceType === 'pgsql'; } public function query(QueryDefinition $def): ExecutionResult { $rows = DB::connection('pgsql') ->table($def->table)->>...; return new ExecutionResult($rows, $total, $metadata); } } // سجّله في AppServiceProvider $this->app->make(DataSourceResolver::class) ->register('pgsql', new PostgreSQLDataSource());
// Stream Export مباشر من الـ Controller public function download(Request $req, string $id): StreamedResponse { // Streams مباشرة — بدون Temp File، بدون Memory Spike return $this->export->exportById( reportId: $id, format: $req->query('format', 'csv'), // csv | xlsx | json userRoles: $req->user()->roles(), ); } // ExporterFactory قابل للتوسع — سجّل Custom Formats: $factory->register('parquet', new ParquetExporter());

الـ Cache بيغيّر كل حاجة

نفس الـ Aggregation Query — فرق كبير في زمن الاستجابة بين Redis ساخن وبارد.

بطيء
بدون Cache (Cold)
كل Request بيضرب الـ Database
🐌
820msإيرادات حسب الحالة
640msMongoDB $facet Aggregation
1.4sتقرير عملاء Multi-Join
3.2sتجهيز Export لـ 10k Row
💥 كل تحديث للـ Dashboard بينفّذ Aggregation كاملة على الـ Database
سريع
Cache ساخن (Redis)
النتيجة جاية مباشرة من الـ Cache
3msإيرادات حسب الحالة
2msMongoDB $facet Aggregation
4msتقرير عملاء Multi-Join
4msتجهيز Export لـ 10k Row
✅ مفتاح Redis مقسّم بالـ Tenant — بيتحذف تلقائياً عند تحديث الـ Report

Defense in Depth — في كل مستوى

ستة Security Controls مستقلة بتشتغل قبل أول استدعاء للـ Database.

الطبقةالـ Classبيحمي من إيه
Identifier InjectionQuerySanitizerRegex Whitelist على كل اسم Column وTable — أي Injection Query بتترفض قبل الـ Builder
Value InjectionLaravel Query Builderكل القيم PDO Bindings — مش بتتحط في النص أبداً
Field ExposureFieldAccessControlالـ Fields المحظورة عالمياً (password, api_key) بتتشال؛ الـ Fields المحظورة في الـ Filters بترمي 422
Query LimitsQueryValidatorأقصى Rows، Joins، Conditions، Aggregations — بيمنع Resource Exhaustion
Rate LimitingQueryLimitMiddlewareBudget لكل User عبر Laravel RateLimiter — المتبقي في الـ Response Headers
Tenant IsolationReportEngine + BuildersTenant ID بيتضاف تلقائياً — Tenant مش ممكن يقرأ بيانات التاني

اللي الـ Package بيقدمه

0خطوة في الـ Pipeline
كل Report بيمر على 8 مراحل محصّنة قبل استدعاء الـ Database
0Export Formats
CSV وXLSX وJSON — كلهم Streamed بدون Temp Files
0% Injection-free
Identifiers محمية بـ Regex؛ القيم دايماً PDO Binding
99ms (مع Cache)
Redis ساخن بيرد على أي Report في أقل من 5ms بغض النظر عن التعقيد

شغّال في 3 أوامر

ثبّت، انشر الـ Config، شغّل الـ Migration — بعدها REST API جاهز والـ Facade شغّالة.

bash — laravel artisan
$ composer require mostafax/dynamic-hybrid-reporting-engine
✓ Package installed successfully
 
# مطلوب: Redis Client
$ composer require predis/predis
✓ predis/predis — Pure-PHP Redis Client
 
$ php artisan vendor:publish --tag=reporting-engine-config
✓ Config published → config/reporting-engine.php
 
$ php artisan vendor:publish --tag=reporting-engine-migrations
✓ Migrations published → database/migrations/
 
$ php artisan migrate
✓ dhr_reports created
✓ dhr_report_executions created
✓ dhr_dashboards created
✓ dhr_widgets created
 
# اختياري: XLSX + Livewire
$ composer require phpoffice/phpspreadsheet livewire/livewire
✓ Excel Export + Livewire enabled
 
# Demo Seeder — MySQL (80 orders) + MongoDB (120 events) + 5 تقارير
$ php artisan db:seed --class=ReportingEngineDemoSeeder
✓ demo_orders seeded (80 rows — MySQL)
✓ demo_events seeded (120 docs — MongoDB)
✓ dhr_reports: 5 تقارير Demo محفوظة
✓ Visit: /reporting-demo
 
# KPI Widget — 6 ثيمات قابلة للتبديل:
<x-reporting-engine::kpi-widget report="id" variant="flat" />
<x-reporting-engine::kpi-widget report="id" variant="gradient" />
<x-reporting-engine::kpi-widget report="id" variant="dark" />
<x-reporting-engine::kpi-widget report="id" variant="glass" />
<x-reporting-engine::kpi-widget report="id" variant="minimal" />
<x-reporting-engine::kpi-widget report="id" variant="bold" />
 
# REST API جاهز الآن على:
POST /api/reporting/run ← Ad-hoc Execution
POST /api/reporting ← حفظ Report
POST /api/reporting/{id}/run ← تنفيذ Saved Report
GET /api/reporting/{id}/export ← Stream Export