سیستم ComputedValues
نمای کلی
سیستم ComputedValues یک معماری رویدادمحور برای محاسبه و کش خودکار دادههای مشتق شده در مدلهای لاراول است. این سیستم با حذف مدیریت دستی کش، مقادیر محاسبه شده را به صورت خودکار در زمان وقوع رویدادهای کسبوکار یا رویدادهای چرخه حیات مدلهای Eloquent بهروزرسانی میکند.
ویژگیهای کلیدی
- معماری رویدادمحور: پاسخ خودکار به BusinessEvents و رویدادهای مدل Eloquent
- ایمنی نوع داده: سیستم نوع کامل مبتنی بر enum با اعتبارسنجی جامع
- پردازش همزمان/ناهمزمان: الگوی استراتژی برای پردازش همزمان یا مبتنی بر صف
- پیشگیری از شرایط رقابتی: قفلگذاری خوشبینانه با استفاده از برچسبزمانی ISO 8601
- محافظت از فیلد: جلوگیری از دستکاری مستقیم فیلد computed_values
- تبدیل نوع: تبدیل خودکار نوع داده هنگام خواندن (integer, boolean, array, JSON, datetime و غیره)
- کشف مدل: اسکن و ثبت خودکار مدلهای دارای مقادیر محاسبه شده
- لاگ جامع: لاگ کامل با زمینه برای اشکالزدایی و پایش
- تفکیک کنترلکننده: کشف کنترلکننده مبتنی بر قرارداد با پشتیبانی از زیرپوشه موجودیت
شروع سریع
۱. پیادهسازی اینترفیس
use App\Services\ComputedValues\Contracts\HasComputedValues;
use App\Services\ComputedValues\Traits\HasComputedValuesField;
use App\Services\ComputedValues\Collections\ComputedValueConfigCollection;
use App\Services\ComputedValues\DTOs\ComputedValueConfig;
use App\Services\ComputedValues\DTOs\EloquentEventConfig;
use App\Services\ComputedValues\Enums\ComputedValueCastEnum;
use App\Services\ComputedValues\Enums\ComputedValueModeEnum;
use App\Services\ComputedValues\Enums\EloquentEventEnum;
class Post extends Model implements HasComputedValues
{
use HasComputedValuesField;
public function getComputedValueConfig(): ComputedValueConfigCollection
{
return new ComputedValueConfigCollection([
new ComputedValueConfig(
key: 'media_count',
relatedEvents: [
new EloquentEventConfig(
modelClass: MediaLibrary::class,
events: [
EloquentEventEnum::CREATED,
EloquentEventEnum::DELETED
]
)
],
mode: ComputedValueModeEnum::ASYNC,
cast: ComputedValueCastEnum::Integer
),
]);
}
}
۲. اضافه کردن ستون computed_values
Schema::table('posts', function (Blueprint $table) {
$table->json('computed_values')->nullable();
});
۳. ایجاد کنترلکننده
ایجاد کنترلکننده در app/Modules/CMS/Services/ComputedValueHandlers/MediaCountComputedValueHandler.php:
<?php
declare(strict_types=1);
namespace App\Modules\CMS\Services\ComputedValueHandlers;
use App\Core\Media\Entities\MediaLibrary;
use App\Services\ComputedValues\Collections\ComputedValueResultCollection;
use App\Services\ComputedValues\Contracts\ComputedValueHandler;
use App\Services\ComputedValues\DTOs\ComputedValueEvent;
use App\Services\ComputedValues\DTOs\ComputedValueResult;
final class MediaCountComputedValueHandler implements ComputedValueHandler
{
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$payload = $eventDto->getPayload();
$attributes = $payload->get('attributes');
if ($attributes === null) {
return new ComputedValueResultCollection([]);
}
$postId = $attributes['post_id'] ?? null;
if ($postId === null) {
return new ComputedValueResultCollection([]);
}
$mediaCount = MediaLibrary::where('post_id', $postId)->count();
return new ComputedValueResultCollection([
new ComputedValueResult(
entityId: (int) $postId,
value: $mediaCount
),
]);
}
}
۴. اجرای کشف
php artisan computed-values:discover
۵. دسترسی به مقادیر محاسبه شده
$post = Post::find(1);
// دسترسی خودکار با تبدیل نوع
$mediaCount = $post->media_count; // عدد صحیح را برمیگرداند (تبدیل اعمال شده)
// بررسی وجود
$hasData = $post->hasComputedValue('media_count');
// دریافت متادیتا
$updatedAt = $post->getComputedValueUpdatedAt('media_count');
معماری
جریان رویداد
ساختار دایرکتوری
app/Services/ComputedValues/
├── Collections/ # مجموعههای تایپ شده
│ ├── ComputedValueConfigCollection.php
│ └── ComputedValueResultCollection.php
├── Console/ # دستورات Artisan
│ └── ComputedValueDiscoveryCommand.php
├── Contracts/ # اینترفیسها
│ ├── ComputedValueHandler.php
│ └── HasComputedValues.php
├── DTOs/ # اشیاء انتقال داده
│ ├── ComputedValueConfig.php
│ ├── ComputedValueEvent.php
│ ├── ComputedValueResult.php
│ └── EloquentEventConfig.php
├── Enums/ # شمارشها
│ ├── ComputedValueCastEnum.php
│ ├── ComputedValueModeEnum.php
│ └── EloquentEventEnum.php
├── Exceptions/ # استثناهای سفارشی
│ ├── ComputedValueCastingException.php
│ ├── ComputedValueFieldAccessException.php
│ └── ... (21 استثنای خاص)
├── Jobs/ # کارهای صف
│ └── ProcessComputedValueUpdateJob.php
├── Listeners/ # مشترکین رویداد
│ └── ComputedValueEventSubscriber.php
├── Providers/ # سرویس پروایدرها
│ └── ComputedValuesServiceProvider.php
├── Services/ # سرویسهای اصلی
│ ├── ComputedValueCastingService.php
│ ├── ComputedValueManager.php
│ ├── ComputedValueStorageService.php
│ ├── EloquentEventAdapter.php
│ └── ModelDiscoveryService.php
└── Traits/ # ویژگیهای قابل استفاده مجدد
└── HasComputedValuesField.php
گزینههای پیکربندی
ComputedValueConfig
new ComputedValueConfig(
key: 'computed_value_key', // کلید snake_case (باید با حرف شروع شود)
relatedEvents: [ // رویدادهایی که محاسبه مجدد را فعال میکنند
BusinessEventClass::class, // نام کلاس BusinessEvent
new EloquentEventConfig( // یا پیکربندی مدل + رویدادهای خاص
modelClass: SomeModel::class,
events: [
EloquentEventEnum::CREATED,
EloquentEventEnum::UPDATED
]
)
],
mode: ComputedValueModeEnum::SYNC, // پردازش همزمان یا ناهمزمان
cast: ComputedValueCastEnum::Integer // نوع داده هدف (اعمال شده هنگام خواندن)
);
انواع رویداد
- رویدادهای کسبوکار
- رویدادهای Eloquent
- رویدادهای ترکیبی
رویدادهای سفارشی برنامه که اینترفیس BusinessEvent را پیادهسازی میکنند:
relatedEvents: [MediaUploadedEvent::class]
مدلهای خاص را با EloquentEventConfig هدف قرار دهید:
relatedEvents: [
new EloquentEventConfig(
modelClass: MediaLibrary::class,
events: [
EloquentEventEnum::CREATED,
EloquentEventEnum::DELETED
]
)
]
BusinessEvents و رویدادهای Eloquent را ترکیب کنید:
relatedEvents: [
MediaUploadedEvent::class, // BusinessEvent
new EloquentEventConfig( // رویداد Eloquent
modelClass: Comment::class,
events: [EloquentEventEnum::CREATED]
)
]
حالتهای پردازش
| حالت | توضیح | مورد استفاده |
|---|---|---|
| SYNC | پردازش فوری در همان درخواست | محاسبات سریع (<100ms) |
| ASYNC | پردازش مبتنی بر صف | محاسبات سنگین، فراخوانی API خارجی |
انواع تبدیل
سیستم از تبدیل نوع جامع اعمال شده هنگام خواندن داده پشتیبانی میکند:
| نوع تبدیل | نوع PHP | توضیح |
|---|---|---|
Array | array | آرایه PHP (پیشفرض) |
Boolean | bool | true/false با تبدیل هوشمند |
Integer | int | اعداد کامل |
Float | float | اعداد اعشاری |
String | string | متن |
Object | stdClass | شیء PHP |
Collection | Collection | مجموعه Laravel |
Json | string | رشته JSON |
DateTime | Carbon | شیء datetime کربن |
Date | Carbon | تاریخ کربن (شروع روز) |
Timestamp | int | برچسب زمانی Unix |
تبدیل هنگام خواندن داده اعمال میشود، نه هنگام ذخیرهسازی. دادهها به همان شکلی که هستند در فیلد JSON ذخیره میشوند که انعطافپذیری را تضمین کرده و از دست رفتن داده جلوگیری میکند.
قرارداد نامگذاری کنترلکننده
کنترلکنندهها باید از این الگوی نامگذاری پیروی کنند:
{Key}ComputedValueHandler
مثالها:
media_count→MediaCountComputedValueHandlerview_statistics→ViewStatisticsComputedValueHandlerlast_activity_at→LastActivityAtComputedValueHandler
مکان و تفکیک کنترلکننده
سیستم مسیرهای متعددی را با اولویت امتحان میکند:
- اولویت ۱: زیرپوشه موجودیت
- اولویت ۲: مسیر مستقیم
- مسیرهای جایگزین
توصیه شده برای مدلها با نامهای کلیدی یکسان:
App\Modules\{Module}\Services\ComputedValueHandlers\{Entity}\{Handler}
مثال:
App\Modules\CMS\Services\ComputedValueHandlers\Post\MediaCountComputedValueHandler
برای کنترلکنندههای منحصر به فرد:
App\Modules\{Module}\Services\ComputedValueHandlers\{Handler}
مثال:
App\Modules\CMS\Services\ComputedValueHandlers\MediaCountComputedValueHandler
همچنین پشتیبانی میکند:
App\Core\{Module}\Services\ComputedValueHandlers\{Handler}
App\Services\ComputedValueHandlers\{Module}\{Handler}
محافظت از فیلد
سیستم از دستکاری مستقیم فیلد computed_values جلوگیری میکند:
// ❌ مسدود - ComputedValueFieldAccessException را پرتاب میکند
$post->computed_values = ['data' => 'value'];
$post->update(['computed_values' => ['data' => 'value']]);
Post::where('computed_values->key', 'value')->get();
// ✅ مجاز - دسترسی از طریق متدهای دسترسی خودکار
$mediaStats = $post->media_stats;
$hasData = $post->hasComputedValue('media_stats');
$updatedAt = $post->getComputedValueUpdatedAt('media_stats');
دسترسی مستقیم به فیلد computed_values عمداً مسدود شده تا یکپارچگی داده حفظ شده و از شرایط رقابتی جلوگیری شود. همیشه از متدهای دسترسی ارائه شده استفاده کنید.
پیشگیری از شرایط رقابتی
سیستم از قفلگذاری خوشبینانه با برچسبزمانی ISO 8601 استفاده میکند:
- برچسب زمانی رویداد هنگام شروع پردازش تولید میشود
- قبل از ذخیرهسازی، برچسب زمانی موجود با برچسب زمانی فعلی مقایسه میشود
- اگر داده موجود جدیدتر باشد (برچسب زمانی بعدی)، بهروزرسانی نادیده گرفته میشود
- لاگ جامع شرایط رقابتی جلوگیری شده را ردیابی میکند
- برای هر دو حالت پردازش همزمان و ناهمزمان کار میکند
مثال جریان:
رویداد A (10:00:00) → کنترلکننده → ذخیرهسازی (10:00:05) ✓ ذخیره شد
رویداد B (10:00:02) → کنترلکننده → ذخیرهسازی (10:00:06) ✗ نادیده گرفته شد (A جدیدتر است)
کشف مدل
سیستم به طور خودکار مدلهایی را که HasComputedValues را پیادهسازی میکنند کشف میکند:
# کشف و کش پیکربندی مدلها
# توجه: این دستور به طور خودکار کش را قبل از کشف پاک میکند
php artisan computed-values:discover
# فقط پاک کردن کش کشف (بدون کشف مجدد)
php artisan computed-values:discover --clear
# نمایش آمار کشف
php artisan computed-values:discover --stats
اسکنهای کشف
فرآیند کشف این دایرکتوریها را اسکن میکند:
app/Models/app/Modules/*/Entities/app/Modules/*/Models/app/Core/*/Entities/app/Core/*/Models/
مدیریت کش کشف
| ویژگی | مقدار |
|---|---|
| مدت زمان کش | 24 ساعت (86400 ثانیه) |
| کلید کش | computed_values_system:model_discovery |
| درایور کش | درایور کش پیشفرض Laravel |
همیشه دستور کشف را در حین استقرار اجرا کنید تا کش قبل از آنلاین شدن برنامه گرم شود. این سربار را در اولین درخواست پس از استقرار از بین میبرد.
متدهای کمکی
بررسی وجود داده
$post->hasComputedValue('media_stats'); // bool
دریافت برچسب زمانی بهروزرسانی
$post->getComputedValueUpdatedAt('media_stats'); // رشته ISO 8601 یا null
دریافت آمار
$stats = $post->getComputedValueStatistics();
// برمیگرداند:
// [
// 'total_keys' => 3,
// 'keys_with_data' => 2,
// 'keys_without_data' => 1,
// 'configured_keys' => ['media_count', 'view_count', 'last_activity'],
// 'keys_with_values' => ['media_count', 'view_count'],
// 'keys_without_values' => ['last_activity'],
// 'last_updated' => '2024-01-15T10:30:00+00:00'
// ]
دریافت مقادیر چندگانه
$data = $post->getComputedValueForKeys(['media_count', 'view_count']);
// برمیگرداند:
// [
// 'media_count' => ['data' => 42, 'has_data' => true, 'updated_at' => '...'],
// 'view_count' => ['data' => 1523, 'has_data' => true, 'updated_at' => '...']
// ]
دریافت تمام مقادیر محاسبه شده
$allData = $post->getAllComputedValueData();
// دادههای JSON خام computed_values را برمیگرداند
بررسی کامل بودن داده
$post->hasCompleteComputedValueData(); // bool - true اگر تمام کلیدها داده داشته باشند
ملاحظات عملکرد
- از حالت ASYNC استفاده کنید برای محاسبات سنگین
- نتایج کشف را کش کنید (به طور خودکار برای 24 ساعت کش میشود)
- از کوئریهای N+1 اجتناب کنید در کنترلکنندهها
- از هدفگیری رویداد خاص استفاده کنید با EloquentEventConfig
- پردازش دستهای بهروزرسانیهای چند موجودیت در کنترلکنندهها
بهترین شیوهها
- از کلیدهای توصیفی استفاده کنید:
media_countنهmcیاm_cnt - کنترلکنندهها را متمرکز نگه دارید: یک مسئولیت در هر کنترلکننده
- از وابستگیهای چرخهای اجتناب کنید: رویدادهایی که باعث حلقههای بینهایت میشوند را فعال نکنید
- از انواع تبدیل مناسب استفاده کنید: نوع داده را با استفاده مطابقت دهید
- حالت پردازش صحیح را انتخاب کنید: همزمان برای عملیات سریع، ناهمزمان برای محاسبات سنگین
- موارد لبه را مدیریت کنید: همیشه مقادیر null و مجموعههای خالی را بررسی کنید
- از کوئریهای N+1 اجتناب کنید: از بارگذاری دستهای و بارگذاری مشتاق در کنترلکنندهها استفاده کنید
- عملیات مهم را لاگ کنید: از لاگر تزریق شده وابستگی در کنترلکنندهها استفاده کنید
- تستهای جامع بنویسید: تست واحد کنترلکنندهها، تست یکپارچه جریان کامل
- عملکرد را پایش کنید: عمق صف را برای عملیات ناهمزمان مشاهده کنید
نیازمندیهای سیستم
- PHP 8.1+
- Laravel 10+
- MySQL 5.7+ یا PostgreSQL 9.5+ (برای پشتیبانی از ستون JSON)
- Redis (اختیاری، برای پردازش صف)
مستندات مرتبط
- راهنمای استفاده: راهنمای پیادهسازی گام به گام با مثالها
- راهنمای توسعهدهنده: معماری فنی و موضوعات پیشرفته
نسخه: 1.0
نویسنده: بهنام مرادی
آخرین بهروزرسانی: دسامبر 2025