A simple, secure, and highly customizable caching engine for PHP. This library provides time-based caching with built-in encryption support, making it suitable for application-level, server-level, and database-level caching scenarios.
- Time-based caching with configurable TTL (Time-to-Live)
- Built-in encryption for sensitive data protection
- Multiple storage backends (File-based included, extensible via Storage interface)
- Static API for backward compatibility and simple usage
- Key prefixing for namespace isolation
- Comprehensive error handling with custom exceptions
composer require webfiori/cache
- Download the latest release from GitHub releases
- Extract the files to your project directory
- Include the library in your project:
require_once 'path/to/WebFiori/Cache/Cache.php';
// You'll also need to manually include all dependencies
require_once 'path/to/WebFiori/Cache/Storage.php';
require_once 'path/to/WebFiori/Cache/Item.php';
require_once 'path/to/WebFiori/Cache/FileStorage.php';
require_once 'path/to/WebFiori/Cache/KeyManager.php';
require_once 'path/to/WebFiori/Cache/SecurityConfig.php';
// And all exception classes...
Note: Manual installation is not recommended. Use Composer for automatic dependency management.
This library requires PHP 8.1 or higher.
Build Status |
---|
<?php
require_once 'vendor/autoload.php';
use WebFiori\Cache\Cache;
// Set up encryption key (recommended for production)
$_ENV['CACHE_ENCRYPTION_KEY'] = 'your-64-character-hex-key-here';
// Simple cache usage
$data = Cache::get('user_data', function() {
// This callback runs only on cache miss
return fetchUserDataFromDatabase();
}, 3600); // Cache for 1 hour
// Check if item exists
if (Cache::has('user_data')) {
echo "Data is cached!";
}
// Remove specific item
Cache::delete('user_data');
// Clear all cache
Cache::flush();
use WebFiori\Cache\Cache;
// Store data with default TTL (60 seconds)
Cache::set('my_key', 'Hello World');
// Store data with custom TTL (1 hour)
Cache::set('my_key', 'Hello World', 3600);
// Override existing cache item
Cache::set('my_key', 'New Value', 3600, true);
Retrieve Only:
// Returns cached data or null if not found/expired
$data = Cache::get('my_key');
if ($data === null) {
echo "Cache miss!";
}
Retrieve or Create:
// Recommended approach - automatically handles cache miss
$data = Cache::get('expensive_operation', function() {
// This callback only runs on cache miss
return performExpensiveOperation();
}, 1800); // Cache for 30 minutes
// With parameters
$userData = Cache::get('user_profile', function($userId, $includePrefs) {
return fetchUserProfile($userId, $includePrefs);
}, 3600, [123, true]); // Pass parameters to callback
Check Item Existence:
if (Cache::has('my_key')) {
echo "Item exists and is not expired";
}
Remove Single Item:
Cache::delete('my_key');
Clear All Cache:
Cache::flush();
Update TTL:
// Extend TTL of existing item to 2 hours
Cache::setTTL('my_key', 7200);
Enable/Disable Caching:
// Disable caching (useful for debugging)
Cache::setEnabled(false);
// Re-enable caching
Cache::setEnabled(true);
// Check if caching is enabled
if (Cache::isEnabled()) {
echo "Caching is active";
}
use WebFiori\Cache\Cache;
use WebFiori\Cache\Item;
// Get detailed cache item information
$item = Cache::getItem('my_key');
if ($item !== null) {
echo "Key: " . $item->getKey() . "\n";
echo "Created: " . date('Y-m-d H:i:s', $item->getCreatedAt()) . "\n";
echo "Expires: " . date('Y-m-d H:i:s', $item->getExpiryTime()) . "\n";
echo "TTL: " . $item->getTTL() . " seconds\n";
echo "Data: " . $item->getData() . "\n";
}
// Set a prefix for cache isolation
Cache::withPrefix('user_');
// This will actually store with key 'user_profile_123'
Cache::set('profile_123', $userData);
// Or chain the prefix setting
Cache::withPrefix('session_')->set('abc123', $sessionData);
For modern applications, use dependency injection instead of static methods:
use WebFiori\Cache\Cache;
use WebFiori\Cache\FileStorage;
// Create cache instance with custom storage
$storage = new FileStorage('/path/to/cache/directory');
$cache = new Cache($storage, true, 'myapp_');
// Note: The Cache class currently only supports static methods
// For dependency injection, you would need to create a wrapper class
// or use the static methods with custom drivers:
// Set custom driver globally
Cache::setDriver($storage);
// Then use static methods as normal
Cache::set('key', 'value', 3600);
$data = Cache::get('key');
// Or create a factory method
$cache = Cache::create($storage, true, 'myapp_');
Set these environment variables for security configuration:
# Required: 64-character hexadecimal encryption key
CACHE_ENCRYPTION_KEY=your64characterhexadecimalencryptionkeyhere1234567890abcdef
# Optional: Security settings
CACHE_ENCRYPTION_ENABLED=true
CACHE_ENCRYPTION_ALGORITHM=aes-256-cbc
CACHE_FILE_PERMISSIONS=600
CACHE_DIR_PERMISSIONS=700
use WebFiori\Cache\KeyManager;
use WebFiori\Cache\SecurityConfig;
use WebFiori\Cache\Item;
// Generate and set encryption key
$key = KeyManager::generateKey();
KeyManager::setEncryptionKey($key);
// Custom security configuration
$config = new SecurityConfig();
$config->setEncryptionEnabled(true);
$config->setEncryptionAlgorithm('aes-256-gcm');
$config->setFilePermissions(0600);
$config->setDirectoryPermissions(0700);
// Apply to cache item
$item = new Item('secure_data', $sensitiveData, 3600);
$item->setSecurityConfig($config);
Implement the Storage
interface to create custom storage backends:
use WebFiori\Cache\Storage;
use WebFiori\Cache\Item;
class RedisStorage implements Storage {
private $redis;
public function __construct($redisConnection) {
$this->redis = $redisConnection;
}
public function store(Item $item) {
$key = $item->getPrefix() . $item->getKey();
$data = $item->getDataEncrypted();
$this->redis->setex($key, $item->getTTL(), $data);
}
public function read(string $key, ?string $prefix) {
$item = $this->readItem($key, $prefix);
return $item ? $item->getDataDecrypted() : null;
}
public function readItem(string $key, ?string $prefix): ?Item {
$fullKey = $prefix . $key;
$data = $this->redis->get($fullKey);
if ($data === false) {
return null;
}
// Reconstruct Item object from stored data
// Implementation depends on your storage format
return $this->reconstructItem($key, $data);
}
public function has(string $key, ?string $prefix): bool {
return $this->redis->exists($prefix . $key);
}
public function delete(string $key) {
$this->redis->del($key);
}
public function flush(?string $prefix) {
if ($prefix) {
$keys = $this->redis->keys($prefix . '*');
if (!empty($keys)) {
$this->redis->del($keys);
}
} else {
$this->redis->flushdb();
}
}
private function reconstructItem(string $key, $data): Item {
// Implementation depends on your data format
// This is a simplified example
return new Item($key, $data, 3600);
}
}
// Use custom storage
$redisStorage = new RedisStorage($redisConnection);
Cache::setDriver($redisStorage);
Method | Description | Parameters | Returns |
---|---|---|---|
get($key, $generator, $ttl, $params) |
Retrieve or create cache item | string $key , callable $generator , int $ttl = 60 , array $params = [] |
mixed |
set($key, $data, $ttl, $override) |
Store cache item | string $key , mixed $data , int $ttl = 60 , bool $override = false |
bool |
has($key) |
Check if item exists | string $key |
bool |
delete($key) |
Remove cache item | string $key |
void |
flush() |
Clear all cache | - | void |
getItem($key) |
Get detailed item info | string $key |
Item|null |
setTTL($key, $ttl) |
Update item TTL | string $key , int $ttl |
bool |
isEnabled() |
Check if caching is enabled | - | bool |
setEnabled($enable) |
Enable/disable caching | bool $enable |
void |
setDriver($driver) |
Set storage driver | Storage $driver |
void |
getDriver() |
Get current storage driver | - | Storage |
withPrefix($prefix) |
Set key prefix | string $prefix |
Cache |
Method | Description | Returns |
---|---|---|
getKey() |
Get item key | string |
getData() |
Get raw data | mixed |
getDataDecrypted() |
Get decrypted data | mixed |
getDataEncrypted() |
Get encrypted data | string |
getTTL() |
Get time-to-live | int |
getCreatedAt() |
Get creation timestamp | int |
getExpiryTime() |
Get expiry timestamp | int |
setTTL($ttl) |
Set time-to-live | void |
generateKey() |
Generate encryption key (static) | string |
Method | Description | Parameters |
---|---|---|
store($item) |
Store cache item | Item $item |
read($key, $prefix) |
Read item data | string $key , ?string $prefix |
readItem($key, $prefix) |
Read item object | string $key , ?string $prefix |
has($key, $prefix) |
Check item existence | string $key , ?string $prefix |
delete($key) |
Delete item | string $key |
flush($prefix) |
Clear cache | ?string $prefix |
Variable | Description | Default | Example |
---|---|---|---|
CACHE_ENCRYPTION_KEY |
64-char hex encryption key | Required | a1b2c3d4e5f6... |
CACHE_ENCRYPTION_ENABLED |
Enable/disable encryption | true |
true|false |
CACHE_ENCRYPTION_ALGORITHM |
Encryption algorithm | aes-256-cbc |
aes-256-gcm |
CACHE_FILE_PERMISSIONS |
Cache file permissions | 600 |
644 |
CACHE_DIR_PERMISSIONS |
Cache directory permissions | 700 |
755 |
The default FileStorage
class stores cache files in the WebFiori/Cache/cache
directory. You can customize this:
use WebFiori\Cache\FileStorage;
use WebFiori\Cache\Cache;
// Custom cache directory
$storage = new FileStorage('/var/cache/myapp');
Cache::setDriver($storage);
- All cached data is encrypted by default using AES-256-CBC
- Encryption keys should be 64-character hexadecimal strings
- Keys are managed through environment variables or
KeyManager
class - Each cache item can have its own encryption configuration
- Cache files are created with restrictive permissions (600 by default)
- Cache directories use 700 permissions by default
- Permissions are configurable via environment variables or
SecurityConfig
- Always set an encryption key in production environments
- Use environment variables for sensitive configuration
- Regularly rotate encryption keys for high-security applications
- Set appropriate file permissions for your environment
- Use prefixes to isolate different application components
- Implement proper error handling for cache operations
use WebFiori\Cache\KeyManager;
// Generate a new encryption key
$key = KeyManager::generateKey();
echo "Your new encryption key: " . $key;
// Save this key securely and set it as CACHE_ENCRYPTION_KEY
The library provides specific exceptions for different error conditions:
use WebFiori\Cache\Cache;
use WebFiori\Cache\Exceptions\InvalidCacheKeyException;
use WebFiori\Cache\Exceptions\CacheStorageException;
use WebFiori\Cache\Exceptions\CacheDriverException;
use WebFiori\Cache\Exceptions\CacheException;
try {
Cache::set('', 'data'); // Invalid key
} catch (InvalidCacheKeyException $e) {
echo "Invalid cache key: " . $e->getMessage();
}
try {
Cache::get('some_key');
} catch (CacheStorageException $e) {
echo "Storage error: " . $e->getMessage();
} catch (CacheException $e) {
echo "General cache error: " . $e->getMessage();
}
- Use appropriate TTL values - longer for stable data, shorter for frequently changing data
- Implement cache warming for critical data
- Use key prefixes to organize and manage cache efficiently
- Monitor cache hit rates to optimize your caching strategy
- Consider cache size limits when using file storage
- Use dependency injection in modern applications for better testability
use WebFiori\Cache\Cache;
class SessionManager {
public function __construct() {
// Set prefix for session isolation
Cache::withPrefix('session_');
}
public function getSession($sessionId) {
return Cache::get($sessionId, function() use ($sessionId) {
return $this->loadSessionFromDatabase($sessionId);
}, 1800); // 30 minutes
}
public function saveSession($sessionId, $data) {
Cache::set($sessionId, $data, 1800, true);
}
public function destroySession($sessionId) {
Cache::delete($sessionId);
}
private function loadSessionFromDatabase($sessionId) {
// Your database loading logic here
return [];
}
}
use WebFiori\Cache\Cache;
class UserRepository {
public function findUser($id) {
return Cache::get("user_{$id}", function() use ($id) {
// Database query only runs on cache miss
return $this->database->query("SELECT * FROM users WHERE id = ?", [$id]);
}, 3600); // Cache for 1 hour
}
public function findUsersByRole($role) {
return Cache::get("users_by_role_{$role}", function() use ($role) {
return $this->database->query("SELECT * FROM users WHERE role = ?", [$role]);
}, 1800); // Cache for 30 minutes
}
public function invalidateUserCache($id) {
Cache::delete("user_{$id}");
// Also invalidate related caches
Cache::flush(); // Or more selective deletion
}
}
use WebFiori\Cache\Cache;
class ApiClient {
public function getWeatherData($city) {
return Cache::get("weather_{$city}", function() use ($city) {
// External API call only on cache miss
$response = file_get_contents("https://api.weather.com/data/{$city}");
return json_decode($response, true);
}, 900); // Cache for 15 minutes
}
public function getUserProfile($userId, $includePrivate = false) {
$cacheKey = "profile_{$userId}_" . ($includePrivate ? 'full' : 'public');
return Cache::get($cacheKey, function() use ($userId, $includePrivate) {
return $this->fetchUserProfile($userId, $includePrivate);
}, $includePrivate ? 300 : 3600); // Private data cached shorter
}
}
This library is licensed under MIT License. See LICENSE file for more details.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Run the test suite:
composer test
- Submit a pull request
- Issues: GitHub Issues
- Documentation: This README and inline code documentation