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 Type | PHP Type | Description |
|---|---|---|
ARRAY | array | Converts to/from PHP arrays |
BOOLEAN | bool | Converts to/from boolean values |
INTEGER | int | Converts to/from integer values |
FLOAT | float | Converts to/from floating-point values |
STRING | string | Converts to/from string values |
OBJECT | stdClass | Converts to/from generic objects |
COLLECTION | Collection | Converts to/from Laravel Collections |
JSON | array | Ensures valid JSON structure |
DATETIME | Carbon | Converts to/from Carbon datetime objects |
DATE | Carbon | Converts to/from Carbon date objects (time set to 00:00:00) |
TIMESTAMP | int | Converts 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