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