Skip to main content

Cache Data Casting

Introduction

The Cache Data Casting system ensures type safety for cached data by enforcing consistent data types across the application. This system provides automatic type conversion when storing and retrieving data from the cache, improving data integrity and developer experience.

Supported Cast Types

The system supports the following cast types through the CacheCastEnum:

Cast TypePHP TypeDescription
ARRAYarrayConverts to/from PHP arrays
BOOLEANboolConverts to/from boolean values
INTEGERintConverts to/from integer values
FLOATfloatConverts to/from floating-point values
STRINGstringConverts to/from string values
OBJECTstdClassConverts to/from generic objects
COLLECTIONCollectionConverts to/from Laravel Collections
JSONarrayEnsures valid JSON structure
DATETIMECarbonConverts to/from Carbon datetime objects
DATECarbonConverts to/from Carbon date objects (time set to 00:00:00)
TIMESTAMPintConverts to/from Unix timestamps

Configuration

Setting Cast Type in CacheConfigDTO

Cast types are defined in the cache configuration:

new CacheConfigDTO(
key: 'user_statistics',
relatedEvents: [UserActivityEvent::class],
sourceModule: 'User',
sourceEntity: User::class,
mode: CacheModeEnum::SYNC,
cast: CacheCastEnum::ARRAY, // Define cast type here
)

Default Cast Type

If no cast type is specified, the system defaults to CacheCastEnum::ARRAY.

Architecture Components

CacheCastingService

The core service responsible for type casting:

class CacheCastingService
{
/**
* Cast value to the specified type
*/
public function cast(mixed $value, CacheCastEnum $castType): mixed
{
// Implementation
}

/**
* Cast value from cache to the specified type
*/
public function castFromCache(mixed $value, CacheCastEnum $castType): mixed
{
// Implementation
}
}

CacheCastEnum

Defines all supported cast types:

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';
}

Integration with CacheManager

The casting service is integrated with the cache manager:

// Inside CacheManager
protected function processCacheResults(
CacheHandlerResultCollection $results,
CacheConfigDTO $config
): array {
return $results->map(function (CacheHandlerResult $result) use ($config) {
// Apply casting to the result data
$castedData = $this->castingService->cast(
$result->getData(),
$config->getCast()
);

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

Usage Examples

Model Configuration

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,
),
]);
}
}

Cache Handler Implementation

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

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

// Return data that will be cast to 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'),
]
)
]);
}
}

Accessing Casted Data

$user = User::find(1);

// Access as object (cast: OBJECT)
$profile = $user->profile_data;
echo $profile->name;
echo $profile->email;

// Access as array (cast: ARRAY)
$metrics = $user->activity_metrics;
echo $metrics['total_posts'];
echo $metrics['engagement_rate'];

// Access as Carbon instance (cast: DATETIME)
$lastLogin = $user->last_login;
echo $lastLogin->diffForHumans(); // "2 hours ago"

// Access as boolean (cast: BOOLEAN)
$isPremium = $user->is_premium;
if ($isPremium) {
// Premium user logic
}

Type Casting Examples

@tab ARRAY

// Configuration
new CacheConfigDTO(
key: 'related_products',
// other config...
cast: CacheCastEnum::ARRAY,
)

// Handler returns
return new CacheHandlerResult(
$productId,
[1, 2, 3, 4, 5] // Array of related product IDs
);

// Access
$relatedIds = $product->related_products; // array
foreach ($relatedIds as $id) {
// Process each ID
}

@tab BOOLEAN

// Configuration
new CacheConfigDTO(
key: 'is_featured',
// other config...
cast: CacheCastEnum::BOOLEAN,
)

// Handler returns
return new CacheHandlerResult(
$productId,
true // or false, 1, 0, "1", "0", "true", "false"
);

// Access
if ($product->is_featured) {
// Featured product logic
}

@tab DATETIME

// Configuration
new CacheConfigDTO(
key: 'last_viewed_at',
// other config...
cast: CacheCastEnum::DATETIME,
)

// Handler returns
return new CacheHandlerResult(
$productId,
'2023-12-01 15:30:00' // String date
// Also accepts Carbon instances, timestamps
);

// Access
$lastViewed = $product->last_viewed_at; // Carbon instance
echo $lastViewed->format('Y-m-d H:i');
echo $lastViewed->diffForHumans();

@tab COLLECTION

// Configuration
new CacheConfigDTO(
key: 'tags',
// other config...
cast: CacheCastEnum::COLLECTION,
)

// Handler returns
return new CacheHandlerResult(
$productId,
['electronics', 'gadgets', 'new'] // Array will be converted to Collection
);

// Access
$tags = $product->tags; // Laravel Collection
$filteredTags = $tags->filter(fn($tag) => strlen($tag) > 5);
$tagCount = $tags->count();

Error Handling

CacheCastException

Thrown when casting fails:

try {
$value = $this->castingService->cast($invalidValue, CacheCastEnum::DATETIME);
} catch (CacheCastException $e) {
// Handle casting error
logger()->warning('Cache casting failed', [
'value' => $invalidValue,
'target_type' => CacheCastEnum::DATETIME->value,
'message' => $e->getMessage()
]);

// Return fallback value
return null;
}

Graceful Degradation

The system implements graceful degradation for casting errors:

// Inside 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("Cannot cast to datetime: invalid format");
} catch (Exception $e) {
// Log error and return null
logger()->warning('Failed to cast to datetime', [
'value' => $value,
'error' => $e->getMessage()
]);

return null;
}
}

Best Practices

1. Choose Appropriate Types

// ✅ Good: Using appropriate types
new CacheConfigDTO(
key: 'is_active',
// other config...
cast: CacheCastEnum::BOOLEAN,
)

new CacheConfigDTO(
key: 'view_count',
// other config...
cast: CacheCastEnum::INTEGER,
)

new CacheConfigDTO(
key: 'last_activity',
// other config...
cast: CacheCastEnum::DATETIME,
)

// ❌ Bad: Using generic types when specific ones are better
new CacheConfigDTO(
key: 'is_active',
// other config...
cast: CacheCastEnum::STRING, // Should be BOOLEAN
)

2. Maintain Type Consistency

// ✅ Good: Consistent type usage
class UserStatsCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// Always return integers for counts
return new CacheHandlerResult(
$eventDto->getEntityId(),
[
'view_count' => (int) $viewCount,
'like_count' => (int) $likeCount,
'comment_count' => (int) $commentCount,
]
);
}
}

// ❌ Bad: Inconsistent types
class UserStatsCacheHandler implements CacheHandler
{
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// Mixing strings and integers for counts
return new CacheHandlerResult(
$eventDto->getEntityId(),
[
'view_count' => $viewCount, // Sometimes string, sometimes int
'like_count' => (string) $likeCount, // Always string
'comment_count' => (int) $commentCount, // Always int
]
);
}
}

3. Handle Null Values

// ✅ Good: Proper null handling
$lastLogin = $user->last_login; // DATETIME cast

if ($lastLogin instanceof Carbon) {
echo $lastLogin->diffForHumans();
} else {
echo 'Never logged in';
}

// Alternative with null coalescing
echo $user->last_login?->diffForHumans() ?? 'Never logged in';

Troubleshooting

Common Issues

1. "Cannot cast value to [type]"

Cause: The value provided cannot be converted to the target type.

Solution:

  • Ensure the cache handler returns data in a compatible format
  • Add type conversion in the handler
  • Check for null values
// Inside cache handler
$value = $someValue ?? 0; // Ensure not null for INTEGER cast
$date = $someDate ?? now(); // Ensure not null for DATETIME cast

2. "Undefined property when accessing cache key"

Cause: The cache key doesn't exist or has null data.

Solution:

  • Check if the cache key exists before accessing
  • Use null coalescing operator
$viewCount = $post->view_count ?? 0;

3. "Method not found on cache value"

Cause: The cast type doesn't match the expected usage.

Solution:

  • Ensure the cast type matches how you plan to use the data
  • Check the cast type in the cache configuration
// If using collection methods
new CacheConfigDTO(
key: 'tags',
// other config...
cast: CacheCastEnum::COLLECTION, // Not ARRAY
)

Debug Commands

// Check cast type for a cache key
$model = Product::find(1);
$config = $model->getCacheConfig()->getByKey('related_products');
dd($config->getCast());

// Debug raw cache data vs. casted data
dd([
'raw' => $model->getRawAttribute('cache')['related_products']['data'] ?? null,
'casted' => $model->related_products
]);

Performance Considerations

  • Casting happens on-demand when accessing cache data
  • Complex casts (like DATETIME) have minimal overhead
  • Collection casting creates new collection instances
  • Type casting improves code reliability with minimal performance impact