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

تبدیل داده‌های کش

مقدمه

سیستم تبدیل داده‌های کش با اجبار انواع داده‌های سازگار در سراسر اپلیکیشن، ایمنی نوع (Type Safety) را برای داده‌های کش شده تضمین می‌کند. این سیستم تبدیل خودکار نوع را هنگام ذخیره و بازیابی داده‌ها از کش فراهم می‌کند و یکپارچگی داده‌ها و تجربه توسعه‌دهنده را بهبود می‌بخشد.

انواع تبدیل پشتیبانی شده

سیستم از انواع تبدیل زیر از طریق CacheCastEnum پشتیبانی می‌کند:

نوع تبدیلنوع PHPتوضیحات
ARRAYarrayتبدیل به/از آرایه‌های PHP
BOOLEANboolتبدیل به/از مقادیر بولی
INTEGERintتبدیل به/از مقادیر صحیح
FLOATfloatتبدیل به/از مقادیر اعشاری
STRINGstringتبدیل به/از مقادیر رشته
OBJECTstdClassتبدیل به/از اشیاء عمومی
COLLECTIONCollectionتبدیل به/از مجموعه‌های Laravel
JSONarrayتضمین ساختار JSON معتبر
DATETIMECarbonتبدیل به/از اشیاء تاریخ-زمان Carbon
DATECarbonتبدیل به/از اشیاء تاریخ Carbon (زمان روی 00:00:00 تنظیم شده)
TIMESTAMPintتبدیل به/از برچسب‌های زمانی Unix

پیکربندی

تنظیم نوع تبدیل در CacheConfigDTO

انواع تبدیل در پیکربندی کش تعریف می‌شوند:

new CacheConfigDTO(
key: 'user_statistics',
relatedEvents: [UserActivityEvent::class],
sourceModule: 'User',
sourceEntity: User::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::ARRAY, // نوع تبدیل را اینجا تعریف کنید
)

نوع تبدیل پیش‌فرض

اگر نوع تبدیلی مشخص نشود، سیستم به طور پیش‌فرض از CacheCastEnum::ARRAY استفاده می‌کند.

اجزای معماری

CacheCastingService

سرویس اصلی مسئول تبدیل نوع:

class CacheCastingService
{
/**
* تبدیل مقدار به نوع مشخص شده
*/
public function cast(mixed $value, CacheCastEnum $castType): mixed
{
// پیاده‌سازی
}

/**
* تبدیل مقدار از کش به نوع مشخص شده
*/
public function castFromCache(mixed $value, CacheCastEnum $castType): mixed
{
// پیاده‌سازی
}
}

CacheCastEnum

تمام انواع تبدیل پشتیبانی شده را تعریف می‌کند:

enum CacheCastEnum: string
{
case ARRAY = 'array';
case BOOLEAN = 'boolean';
case INTEGER = 'integer';
case FLOAT = 'float';
case STRING = 'string';
case OBJECT = 'object';
case COLLECTION = 'collection';
case JSON = 'json';
case DATETIME = 'datetime';
case DATE = 'date';
case TIMESTAMP = 'timestamp';
}

یکپارچگی با CacheManager

سرویس تبدیل با مدیر کش یکپارچه شده است:

// داخل CacheManager
protected function processCacheResults(
CacheHandlerResultCollection $results,
CacheConfigDTO $config
): array {
return $results->map(function (CacheHandlerResult $result) use ($config) {
// اعمال تبدیل به داده نتیجه
$castedData = $this->castingService->cast(
$result->getData(),
$config->getCast()
);

return new CacheHandlerResult(
$result->getEntityId(),
$castedData
);
})->toArray();
}

نمونه‌های استفاده

پیکربندی مدل

class User extends Model implements CacheableModel
{
public function getCacheConfig(): CacheConfigCollection
{
return new CacheConfigCollection([
new CacheConfigDTO(
key: 'profile_data',
relatedEvents: [UserUpdatedEvent::class],
sourceModule: 'User',
sourceEntity: User::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::OBJECT,
),
new CacheConfigDTO(
key: 'activity_metrics',
relatedEvents: [UserActivityEvent::class],
sourceModule: 'User',
sourceEntity: User::class,
mode: CacheModeEnum::ASYNC,
cast: CacheCastEnum::ARRAY,
),
new CacheConfigDTO(
key: 'last_login',
relatedEvents: [UserLoginEvent::class],
sourceModule: 'User',
sourceEntity: User::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::DATETIME,
),
new CacheConfigDTO(
key: 'is_premium',
relatedEvents: [SubscriptionChangedEvent::class],
sourceModule: 'User',
sourceEntity: User::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::BOOLEAN,
),
]);
}
}

پیاده‌سازی کنترل‌کننده کش

class UserProfileCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$userId = $eventDto->getEntityId();
$user = User::find($userId);

if (!$user) {
return new CacheHandlerResultCollection();
}

// برگرداندن داده‌ای که به OBJECT تبدیل خواهد شد
return new CacheHandlerResultCollection([
new CacheHandlerResult(
$userId,
[
'name' => $user->name,
'email' => $user->email,
'avatar' => $user->avatar_url,
'bio' => $user->bio,
'joined_at' => $user->created_at->format('Y-m-d'),
]
)
]);
}
}

دسترسی به داده‌های تبدیل شده

$user = User::find(1);

// دسترسی به عنوان شیء (cast: OBJECT)
$profile = $user->profile_data;
echo $profile->name;
echo $profile->email;

// دسترسی به عنوان آرایه (cast: ARRAY)
$metrics = $user->activity_metrics;
echo $metrics['total_posts'];
echo $metrics['engagement_rate'];

// دسترسی به عنوان نمونه Carbon (cast: DATETIME)
$lastLogin = $user->last_login;
echo $lastLogin->diffForHumans(); // "2 ساعت پیش"

// دسترسی به عنوان بولی (cast: BOOLEAN)
$isPremium = $user->is_premium;
if ($isPremium) {
// منطق کاربر پریمیوم
}

نمونه‌های تبدیل نوع

@tab ARRAY

// پیکربندی
new CacheConfigDTO(
key: 'related_products',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::ARRAY,
)

// کنترل‌کننده برمی‌گرداند
return new CacheHandlerResult(
$productId,
[1, 2, 3, 4, 5] // آرایه شناسه‌های محصولات مرتبط
);

// دسترسی
$relatedIds = $product->related_products; // array
foreach ($relatedIds as $id) {
// پردازش هر شناسه
}

@tab BOOLEAN

// پیکربندی
new CacheConfigDTO(
key: 'is_featured',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::BOOLEAN,
)

// کنترل‌کننده برمی‌گرداند
return new CacheHandlerResult(
$productId,
true // یا false, 1, 0, "1", "0", "true", "false"
);

// دسترسی
if ($product->is_featured) {
// منطق محصول ویژه
}

@tab DATETIME

// پیکربندی
new CacheConfigDTO(
key: 'last_viewed_at',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::DATETIME,
)

// کنترل‌کننده برمی‌گرداند
return new CacheHandlerResult(
$productId,
'2023-12-01 15:30:00' // رشته تاریخ
// همچنین نمونه‌های Carbon و برچسب‌های زمانی را می‌پذیرد
);

// دسترسی
$lastViewed = $product->last_viewed_at; // نمونه Carbon
echo $lastViewed->format('Y-m-d H:i');
echo $lastViewed->diffForHumans();

@tab COLLECTION

// پیکربندی
new CacheConfigDTO(
key: 'tags',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::COLLECTION,
)

// کنترل‌کننده برمی‌گرداند
return new CacheHandlerResult(
$productId,
['electronics', 'gadgets', 'new'] // آرایه به Collection تبدیل خواهد شد
);

// دسترسی
$tags = $product->tags; // Laravel Collection
$filteredTags = $tags->filter(fn($tag) => strlen($tag) > 5);
$tagCount = $tags->count();

مدیریت خطا

CacheCastException

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

try {
$value = $this->castingService->cast($invalidValue, CacheCastEnum::DATETIME);
} catch (CacheCastException $e) {
// مدیریت خطای تبدیل
logger()->warning('تبدیل کش شکست خورد', [
'value' => $invalidValue,
'target_type' => CacheCastEnum::DATETIME->value,
'message' => $e->getMessage()
]);

// برگرداندن مقدار پشتیبان
return null;
}

تنزل تدریجی

سیستم تنزل تدریجی را برای خطاهای تبدیل پیاده‌سازی می‌کند:

// داخل CacheCastingService
protected function castToDateTime(mixed $value): ?Carbon
{
try {
if ($value instanceof Carbon) {
return $value;
}

if (is_string($value)) {
return Carbon::parse($value);
}

if (is_numeric($value)) {
return Carbon::createFromTimestamp($value);
}

throw new CacheCastException("نمی‌توان به datetime تبدیل کرد: فرمت نامعتبر");
} catch (Exception $e) {
// لاگ خطا و برگرداندن null
logger()->warning('تبدیل به datetime شکست خورد', [
'value' => $value,
'error' => $e->getMessage()
]);

return null;
}
}

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

1. انتخاب انواع مناسب

// ✅ خوب: استفاده از انواع مناسب
new CacheConfigDTO(
key: 'is_active',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::BOOLEAN,
)

new CacheConfigDTO(
key: 'view_count',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::INTEGER,
)

new CacheConfigDTO(
key: 'last_activity',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::DATETIME,
)

// ❌ بد: استفاده از انواع عمومی وقتی انواع مشخص بهتر هستند
new CacheConfigDTO(
key: 'is_active',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::STRING, // باید BOOLEAN باشد
)

2. حفظ سازگاری نوع

// ✅ خوب: استفاده سازگار از نوع
class UserStatsCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// همیشه اعداد صحیح برای شمارش‌ها برگردان
return new CacheHandlerResult(
$eventDto->getEntityId(),
[
'view_count' => (int) $viewCount,
'like_count' => (int) $likeCount,
'comment_count' => (int) $commentCount,
]
);
}
}

// ❌ بد: انواع ناسازگار
class UserStatsCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// ترکیب رشته‌ها و اعداد صحیح برای شمارش‌ها
return new CacheHandlerResult(
$eventDto->getEntityId(),
[
'view_count' => $viewCount, // گاهی رشته، گاهی int
'like_count' => (string) $likeCount, // همیشه رشته
'comment_count' => (int) $commentCount, // همیشه int
]
);
}
}

3. مدیریت مقادیر Null

// ✅ خوب: مدیریت صحیح null
$lastLogin = $user->last_login; // DATETIME cast

if ($lastLogin instanceof Carbon) {
echo $lastLogin->diffForHumans();
} else {
echo 'هرگز وارد نشده';
}

// جایگزین با null coalescing
echo $user->last_login?->diffForHumans() ?? 'هرگز وارد نشده';

عیب‌یابی

مسائل رایج

1. "نمی‌توان مقدار را به [نوع] تبدیل کرد"

علت: مقدار ارائه شده نمی‌تواند به نوع هدف تبدیل شود.

راه‌حل:

  • اطمینان حاصل کنید کنترل‌کننده کش داده‌ها را در فرمت سازگار برمی‌گرداند
  • تبدیل نوع را در کنترل‌کننده اضافه کنید
  • مقادیر null را بررسی کنید
// داخل کنترل‌کننده کش
$value = $someValue ?? 0; // اطمینان از عدم null بودن برای INTEGER cast
$date = $someDate ?? now(); // اطمینان از عدم null بودن برای DATETIME cast

2. "خاصیت تعریف نشده هنگام دسترسی به کلید کش"

علت: کلید کش وجود ندارد یا داده null دارد.

راه‌حل:

  • قبل از دسترسی وجود کلید کش را بررسی کنید
  • از عملگر null coalescing استفاده کنید
$viewCount = $post->view_count ?? 0;

3. "متد روی مقدار کش یافت نشد"

علت: نوع تبدیل با استفاده مورد انتظار مطابقت ندارد.

راه‌حل:

  • اطمینان حاصل کنید نوع تبدیل با نحوه استفاده از داده‌ها مطابقت دارد
  • نوع تبدیل را در پیکربندی کش بررسی کنید
// اگر از متدهای collection استفاده می‌کنید
new CacheConfigDTO(
key: 'tags',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::COLLECTION, // نه ARRAY
)

دستورات دیباگ

// بررسی نوع تبدیل برای یک کلید کش
$model = Product::find(1);
$config = $model->getCacheConfig()->getByKey('related_products');
dd($config->getCast());

// دیباگ داده خام کش در مقابل داده تبدیل شده
dd([
'raw' => $model->getRawAttribute('cache')['related_products']['data'] ?? null,
'casted' => $model->related_products
]);

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

  • تبدیل به صورت درخواستی هنگام دسترسی به داده‌های کش انجام می‌شود
  • تبدیل‌های پیچیده (مانند DATETIME) سربار کمی دارند
  • تبدیل Collection نمونه‌های collection جدید ایجاد می‌کند
  • تبدیل نوع قابلیت اطمینان کد را با حداقل تأثیر عملکرد بهبود می‌بخشد