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

سیستم ComputedValues - راهنمای استفاده

این راهنما به شما نشان می‌دهد چگونه از سیستم ComputedValues برای محاسبه و کش خودکار داده‌های مشتق شده در مدل‌های لاراول خود استفاده کنید.

پیش‌نیازها

  • مدل لاراول با جدول پایگاه داده
  • درک اساسی از رویدادهای لاراول
  • آشنایی با اینترفیس‌ها و ویژگی‌های PHP

پیاده‌سازی گام به گام

گام ۱: اضافه کردن ستون پایگاه داده

ستون JSON computed_values را به جدول خود اضافه کنید:

Schema::table('posts', function (Blueprint $table) {
$table->json('computed_values')->nullable();
});

گام ۲: پیاده‌سازی اینترفیس و ویژگی

مدل خود را به‌روزرسانی کنید:

use App\Services\ComputedValues\Contracts\HasComputedValues;
use App\Services\ComputedValues\Traits\HasComputedValuesField;

class Post extends Model implements HasComputedValues
{
use HasComputedValuesField;

// ... بقیه مدل شما
}

گام ۳: تعریف پیکربندی مقدار محاسبه شده

متد getComputedValueConfig() را به مدل خود اضافه کنید:

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;

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::SYNC,
cast: ComputedValueCastEnum::Integer
),
]);
}

گام ۴: ایجاد کنترل‌کننده

یک کلاس کنترل‌کننده در دایرکتوری Services/ComputedValueHandlers/ ماژول خود ایجاد کنید:

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 رویداد
$payload = $eventDto->getPayload();
$postId = $payload->get('post_id');

if ($postId === null) {
return new ComputedValueResultCollection([]);
}

// شمارش رسانه برای این پست
$mediaCount = MediaLibrary::where('post_id', $postId)->count();

// بازگرداندن نتیجه
return new ComputedValueResultCollection([
new ComputedValueResult(
entityId: $postId,
value: $mediaCount
),
]);
}
}

گام ۵: ثبت کشف

دستور کشف را برای ثبت پیکربندی خود اجرا کنید:

php artisan computed-values:discover
پاک کردن خودکار کش

دستور کشف به طور خودکار هر کش موجود را قبل از کشف پاک می‌کند تا نتایج تازه تضمین شود.

گام ۶: دسترسی به مقادیر محاسبه شده

به مقادیر محاسبه شده در کد خود دسترسی داشته باشید:

$post = Post::find(1);

// دسترسی به مقدار محاسبه شده (دسترسی خودکار)
$mediaCount = $post->media_count;

// بررسی وجود مقدار
if ($post->hasComputedValue('media_count')) {
echo "تعداد رسانه: " . $post->media_count;
}

// دریافت برچسب زمانی به‌روزرسانی
$updatedAt = $post->getComputedValueUpdatedAt('media_count');
echo "آخرین به‌روزرسانی: " . $updatedAt;

گزینه‌های پیکربندی

انواع رویداد

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

relatedEvents: [
MediaUploadedEvent::class,
MediaDeletedEvent::class,
]

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

فوری در همان درخواست پردازش می‌شود. برای محاسبات سریع استفاده کنید:

mode: ComputedValueModeEnum::SYNC

بهترین برای:

  • شمارش‌های ساده
  • محاسبات سریع (<100ms)
  • عملیات بدون وابستگی‌های خارجی

انواع داده (تبدیل)

نوع داده را برای مقدار محاسبه شده خود مشخص کنید:

cast: ComputedValueCastEnum::Integer

برای اعداد کامل مانند شمارش‌ها، شناسه‌ها، کمیت‌ها.

موارد استفاده رایج

مورد استفاده ۱: شمارش مدل‌های مرتبط

تعداد مدل‌های مرتبط موجود را بشمارید:

پیکربندی
new ComputedValueConfig(
key: 'comment_count',
relatedEvents: [
new EloquentEventConfig(
modelClass: Comment::class,
events: [EloquentEventEnum::CREATED, EloquentEventEnum::DELETED]
)
],
mode: ComputedValueModeEnum::SYNC,
cast: ComputedValueCastEnum::Integer
)
کنترل‌کننده
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$payload = $eventDto->getPayload();
$postId = $payload->get('post_id');

$count = Comment::where('post_id', $postId)->count();

return new ComputedValueResultCollection([
new ComputedValueResult($postId, $count),
]);
}

مورد استفاده ۲: محاسبه آمار

آمار تجمیعی را محاسبه کنید:

پیکربندی
new ComputedValueConfig(
key: 'view_statistics',
relatedEvents: [PostViewedEvent::class],
mode: ComputedValueModeEnum::ASYNC,
cast: ComputedValueCastEnum::Array
)
کنترل‌کننده
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$payload = $eventDto->getPayload();
$postId = $payload->get('post_id');

$stats = [
'total_views' => View::where('post_id', $postId)->count(),
'unique_views' => View::where('post_id', $postId)->distinct('user_id')->count(),
'today_views' => View::where('post_id', $postId)
->whereDate('created_at', today())
->count(),
];

return new ComputedValueResultCollection([
new ComputedValueResult($postId, $stats),
]);
}

مورد استفاده ۳: بررسی وضعیت/پرچم‌ها

پرچم‌های بولین را محاسبه کنید:

پیکربندی
new ComputedValueConfig(
key: 'has_active_comments',
relatedEvents: [
new EloquentEventConfig(
modelClass: Comment::class,
events: [
EloquentEventEnum::CREATED,
EloquentEventEnum::UPDATED,
EloquentEventEnum::DELETED
]
)
],
mode: ComputedValueModeEnum::SYNC,
cast: ComputedValueCastEnum::Boolean
)
کنترل‌کننده
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$payload = $eventDto->getPayload();
$postId = $payload->get('post_id');

$hasActive = Comment::where('post_id', $postId)
->where('status', 'active')
->exists();

return new ComputedValueResultCollection([
new ComputedValueResult($postId, $hasActive),
]);
}

مورد استفاده ۴: برچسب زمانی آخرین فعالیت

پیگیری زمان وقوع آخرین رویداد:

پیکربندی
new ComputedValueConfig(
key: 'last_activity_at',
relatedEvents: [
CommentAddedEvent::class,
PostLikedEvent::class,
PostSharedEvent::class,
],
mode: ComputedValueModeEnum::SYNC,
cast: ComputedValueCastEnum::DateTime
)
کنترل‌کننده
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$payload = $eventDto->getPayload();
$postId = $payload->get('post_id');

$lastActivity = Activity::where('post_id', $postId)
->latest()
->first()
?->created_at;

return new ComputedValueResultCollection([
new ComputedValueResult($postId, $lastActivity ?? now()),
]);
}

مورد استفاده ۵: تجمیع‌های پیچیده

داده‌های مشتق شده پیچیده را محاسبه کنید:

پیکربندی
new ComputedValueConfig(
key: 'engagement_metrics',
relatedEvents: [
PostViewedEvent::class,
PostLikedEvent::class,
CommentAddedEvent::class,
],
mode: ComputedValueModeEnum::ASYNC,
cast: ComputedValueCastEnum::Array
)
کنترل‌کننده
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
$payload = $eventDto->getPayload();
$postId = $payload->get('post_id');

$metrics = [
'engagement_score' => $this->calculateEngagementScore($postId),
'trending_rank' => $this->calculateTrendingRank($postId),
'virality_index' => $this->calculateViralityIndex($postId),
'calculated_at' => now()->toIso8601String(),
];

return new ComputedValueResultCollection([
new ComputedValueResult($postId, $metrics),
]);
}

دسترسی به مقادیر محاسبه شده

دسترسی پایه

$post = Post::find(1);

// دسترسی مستقیم (دسترسی خودکار)
$mediaCount = $post->media_count;

// بررسی وجود
if ($post->hasComputedValue('media_count')) {
echo $post->media_count;
}

دریافت متادیتا

// دریافت برچسب زمانی آخرین به‌روزرسانی
$updatedAt = $post->getComputedValueUpdatedAt('media_count');
// برمی‌گرداند: "2024-01-15T10:30:00+00:00" یا null

// دریافت تمام آمار
$stats = $post->getCacheStatistics();
// برمی‌گرداند:
// [
// 'total_keys' => 3,
// 'cached_keys' => 2,
// 'empty_keys' => 1,
// 'keys_with_data' => ['media_count', 'view_stats'],
// 'keys_without_data' => ['last_activity'],
// 'last_updated' => '2024-01-15T10:30:00+00:00'
// ]

دریافت مقادیر چندگانه

// دریافت مقادیر خاص
$data = $post->getComputedValueForKeys(['media_count', 'view_stats']);
// برمی‌گرداند:
// [
// 'media_count' => [
// 'data' => 42,
// 'has_data' => true,
// 'updated_at' => '2024-01-15T10:30:00+00:00'
// ],
// 'view_stats' => [...]
// ]

// دریافت تمام مقادیر محاسبه شده
$allData = $post->getAllCacheData();

بررسی کامل بودن

// بررسی اینکه آیا تمام کلیدهای پیکربندی شده داده دارند
if ($post->hasCompleteCacheData()) {
echo "تمام مقادیر محاسبه شده در دسترس هستند";
}

در پاسخ‌های API

مقادیر محاسبه شده به طور خودکار در پاسخ‌های JSON ظاهر می‌شوند:

return response()->json($post);

// خروجی شامل:
// {
// "id": 1,
// "title": "پست من",
// "media_count": 42,
// "view_stats": {...},
// ...
// }

مقادیر محاسبه شده چندگانه

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

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::SYNC,
cast: ComputedValueCastEnum::Integer
),

// تعداد کامنت
new ComputedValueConfig(
key: 'comment_count',
relatedEvents: [
new EloquentEventConfig(
modelClass: Comment::class,
events: [EloquentEventEnum::CREATED, EloquentEventEnum::DELETED]
)
],
mode: ComputedValueModeEnum::SYNC,
cast: ComputedValueCastEnum::Integer
),

// آمار بازدید
new ComputedValueConfig(
key: 'view_statistics',
relatedEvents: [PostViewedEvent::class],
mode: ComputedValueModeEnum::ASYNC,
cast: ComputedValueCastEnum::Array
),
]);
}
نکته

هر مقدار محاسبه شده به کنترل‌کننده خود نیاز دارد که از قرارداد نام‌گذاری پیروی کند.

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

قرارداد نام‌گذاری

نام کلاس کنترل‌کننده باید با کلید کش مطابقت داشته باشد:

کلید کشنام کلاس کنترل‌کننده
media_countMediaCountComputedValueHandler
view_statisticsViewStatisticsComputedValueHandler
last_activity_atLastActivityAtComputedValueHandler

مکان کنترل‌کننده

کنترل‌کننده‌ها را در دایرکتوری Services/ComputedValueHandlers/ ماژول خود قرار دهید:

app/Modules/CMS/Services/ComputedValueHandlers/
MediaCountComputedValueHandler.php
CommentCountComputedValueHandler.php
ViewStatisticsComputedValueHandler.php

ساختار کنترل‌کننده

<?php

declare(strict_types=1);

namespace App\Modules\{Module}\Services\ComputedValueHandlers;

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 {CacheKey}ComputedValueHandler implements ComputedValueHandler
{
public function handle(ComputedValueEvent $eventDto): ComputedValueResultCollection
{
// 1. استخراج داده از رویداد
$payload = $eventDto->getPayload();
$entityId = $payload->get('entity_id');

// 2. اعتبارسنجی
if ($entityId === null) {
return new ComputedValueResultCollection([]);
}

// 3. محاسبه مقدار
$value = $this->computeValue($entityId);

// 4. بازگرداندن نتیجه
return new ComputedValueResultCollection([
new ComputedValueResult($entityId, $value),
]);
}

private function computeValue(int $entityId): mixed
{
// منطق محاسبه شما
return 0;
}
}

عیب‌یابی

مقادیر به‌روز نمی‌شوند

بررسی کنید:

  1. مدل اینترفیس HasComputedValues را پیاده‌سازی کرده است
  2. مدل از ویژگی HasComputedValuesField استفاده می‌کند
  3. ستون computed_values در پایگاه داده وجود دارد
  4. کلاس کنترل‌کننده وجود دارد و از قرارداد نام‌گذاری پیروی می‌کند
  5. کشف اجرا شده است: php artisan computed-values:discover

مشاهده لاگ‌ها:

tail -f storage/logs/laravel.log | grep "Computed value"

خطای کنترل‌کننده یافت نشد

بررسی کنید:

  1. نام کلاس کنترل‌کننده با کلید کش مطابقت دارد (StudlyCase)
  2. کنترل‌کننده در دایرکتوری صحیح برای ماژول شما قرار دارد
  3. کنترل‌کننده اینترفیس ComputedValueHandler را پیاده‌سازی می‌کند
  4. کنترل‌کننده متد handle() را دارد

کارهای ناهمزمان پردازش نمی‌شوند

بررسی کنید:

  1. کارگران صف در حال اجرا هستند: php artisan queue:work
  2. پیکربندی صف صحیح است
  3. کارهای ناموفق: php artisan queue:failed

تلاش مجدد کارهای ناموفق:

php artisan queue:retry all

دسترسی مستقیم به فیلد مسدود شد

این رفتار مورد انتظار است. سیستم از دستکاری مستقیم جلوگیری می‌کند:

// ❌ این موارد ComputedValueFieldAccessException را پرتاب می‌کنند
$post->computed_values = ['data' => 'value'];
$post->update(['computed_values' => ['data' => 'value']]);

// ✅ به جای آن از متدهای دسترسی خودکار استفاده کنید
$mediaCount = $post->media_count;
محافظت از فیلد

دسترسی مستقیم به فیلد computed_values عمداً مسدود شده است. این از یکپارچگی داده محافظت کرده و از شرایط رقابتی جلوگیری می‌کند. همیشه از متدهای دسترسی ارائه شده استفاده کنید.

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

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

دستورات

# کشف و ثبت مدل‌ها (به طور خودکار ابتدا کش را پاک می‌کند)
php artisan computed-values:discover

# فقط پاک کردن کش کشف (بدون کشف مجدد)
php artisan computed-values:discover --clear

# نمایش آمار کشف (از نتایج کش شده در صورت وجود استفاده می‌کند)
php artisan computed-values:discover --stats

# بررسی وضعیت صف
php artisan queue:work

# مشاهده کارهای ناموفق
php artisan queue:failed

# تلاش مجدد کارهای ناموفق
php artisan queue:retry all

پشتیبانی

اگر با مشکلی مواجه شدید:

  1. این راهنما را بررسی کنید
  2. لاگ‌ها را در storage/logs/laravel.log مرور کنید
  3. نام‌گذاری و مکان کنترل‌کننده را بررسی کنید
  4. دستور کشف را اجرا کنید
  5. پایگاه داده را برای ستون computed_values بررسی کنید
  6. با تیم توسعه تماس بگیرید

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