پرش به مطلب اصلی

سیستم 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 // نوع داده هدف (اعمال شده هنگام خواندن)
);

انواع رویداد

رویدادهای سفارشی برنامه که اینترفیس BusinessEvent را پیاده‌سازی می‌کنند:

relatedEvents: [MediaUploadedEvent::class]

حالت‌های پردازش

حالتتوضیحمورد استفاده
SYNCپردازش فوری در همان درخواستمحاسبات سریع (<100ms)
ASYNCپردازش مبتنی بر صفمحاسبات سنگین، فراخوانی API خارجی

انواع تبدیل

سیستم از تبدیل نوع جامع اعمال شده هنگام خواندن داده پشتیبانی می‌کند:

نوع تبدیلنوع PHPتوضیح
Arrayarrayآرایه PHP (پیش‌فرض)
Booleanbooltrue/false با تبدیل هوشمند
Integerintاعداد کامل
Floatfloatاعداد اعشاری
Stringstringمتن
ObjectstdClassشیء PHP
CollectionCollectionمجموعه Laravel
Jsonstringرشته JSON
DateTimeCarbonشیء datetime کربن
DateCarbonتاریخ کربن (شروع روز)
Timestampintبرچسب زمانی Unix
رفتار تبدیل نوع

تبدیل هنگام خواندن داده اعمال می‌شود، نه هنگام ذخیره‌سازی. داده‌ها به همان شکلی که هستند در فیلد JSON ذخیره می‌شوند که انعطاف‌پذیری را تضمین کرده و از دست رفتن داده جلوگیری می‌کند.

قرارداد نام‌گذاری کنترل‌کننده

کنترل‌کننده‌ها باید از این الگوی نام‌گذاری پیروی کنند:

{Key}ComputedValueHandler

مثال‌ها:

  • media_countMediaCountComputedValueHandler
  • view_statisticsViewStatisticsComputedValueHandler
  • last_activity_atLastActivityAtComputedValueHandler

مکان و تفکیک کنترل‌کننده

سیستم مسیرهای متعددی را با اولویت امتحان می‌کند:

توصیه شده برای مدل‌ها با نام‌های کلیدی یکسان:

App\Modules\{Module}\Services\ComputedValueHandlers\{Entity}\{Handler}

مثال:

App\Modules\CMS\Services\ComputedValueHandlers\Post\MediaCountComputedValueHandler

محافظت از فیلد

سیستم از دستکاری مستقیم فیلد 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 استفاده می‌کند:

  1. برچسب زمانی رویداد هنگام شروع پردازش تولید می‌شود
  2. قبل از ذخیره‌سازی، برچسب زمانی موجود با برچسب زمانی فعلی مقایسه می‌شود
  3. اگر داده موجود جدیدتر باشد (برچسب زمانی بعدی)، به‌روزرسانی نادیده گرفته می‌شود
  4. لاگ جامع شرایط رقابتی جلوگیری شده را ردیابی می‌کند
  5. برای هر دو حالت پردازش همزمان و ناهمزمان کار می‌کند

مثال جریان:

رویداد 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 اگر تمام کلیدها داده داشته باشند

ملاحظات عملکرد

  1. از حالت ASYNC استفاده کنید برای محاسبات سنگین
  2. نتایج کشف را کش کنید (به طور خودکار برای 24 ساعت کش می‌شود)
  3. از کوئری‌های N+1 اجتناب کنید در کنترل‌کننده‌ها
  4. از هدف‌گیری رویداد خاص استفاده کنید با EloquentEventConfig
  5. پردازش دسته‌ای به‌روزرسانی‌های چند موجودیت در کنترل‌کننده‌ها

بهترین شیوه‌ها

  1. از کلیدهای توصیفی استفاده کنید: media_count نه mc یا m_cnt
  2. کنترل‌کننده‌ها را متمرکز نگه دارید: یک مسئولیت در هر کنترل‌کننده
  3. از وابستگی‌های چرخه‌ای اجتناب کنید: رویدادهایی که باعث حلقه‌های بی‌نهایت می‌شوند را فعال نکنید
  4. از انواع تبدیل مناسب استفاده کنید: نوع داده را با استفاده مطابقت دهید
  5. حالت پردازش صحیح را انتخاب کنید: همزمان برای عملیات سریع، ناهمزمان برای محاسبات سنگین
  6. موارد لبه را مدیریت کنید: همیشه مقادیر null و مجموعه‌های خالی را بررسی کنید
  7. از کوئری‌های N+1 اجتناب کنید: از بارگذاری دسته‌ای و بارگذاری مشتاق در کنترل‌کننده‌ها استفاده کنید
  8. عملیات مهم را لاگ کنید: از لاگر تزریق شده وابستگی در کنترل‌کننده‌ها استفاده کنید
  9. تست‌های جامع بنویسید: تست واحد کنترل‌کننده‌ها، تست یکپارچه جریان کامل
  10. عملکرد را پایش کنید: عمق صف را برای عملیات ناهمزمان مشاهده کنید

نیازمندی‌های سیستم

  • PHP 8.1+
  • Laravel 10+
  • MySQL 5.7+ یا PostgreSQL 9.5+ (برای پشتیبانی از ستون JSON)
  • Redis (اختیاری، برای پردازش صف)

مستندات مرتبط


نسخه: 1.0
نویسنده: بهنام مرادی
آخرین به‌روزرسانی: دسامبر 2025