π«π· Read in French | π¬π§ Read in English
If this package is useful to you, consider becoming a sponsor to support the development and maintenance of this open source project.
A modern and secure caching system for PHP 8+ with support for multiple drivers (File, Redis, Memcached, Array), tags, TTL, and invalidation.
composer require julienlinard/php-cacheRequirements: PHP 8.0 or higher
<?php
require_once __DIR__ . '/vendor/autoload.php';
use JulienLinard\Cache\Cache;
// Initialize with configuration
Cache::init([
'default' => 'file', // or 'array', 'redis'
'drivers' => [
'array' => [],
'file' => [
'path' => __DIR__ . '/cache',
'ttl' => 3600, // Default TTL in seconds
],
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => null,
'database' => 0,
],
],
]);
// Simple usage
Cache::set('user_123', ['name' => 'John', 'email' => 'john@example.com'], 3600);
$user = Cache::get('user_123');- β Multiple Drivers: Array, File, Redis
- β TTL (Time To Live): Automatic entry expiration
- β Tags: Tag system for grouped invalidation
- β Security: Key validation, protection against injections
- β Secure Serialization: JSON usage with validation
- β Multiple Operations: getMultiple, setMultiple, deleteMultiple
- β Increment/Decrement: Support for numeric values
- β Fluid Interface: Simple and intuitive API
The Array driver stores data in memory. Useful for testing and development.
use JulienLinard\Cache\Cache;
Cache::init([
'default' => 'array',
'drivers' => [
'array' => [
'prefix' => 'myapp', // Optional prefix for all keys
'ttl' => 3600, // Default TTL
],
],
]);The File driver stores data in files on the filesystem.
Cache::init([
'default' => 'file',
'drivers' => [
'file' => [
'path' => __DIR__ . '/cache', // Cache directory
'prefix' => 'myapp',
'ttl' => 3600,
'file_permissions' => 0644, // File permissions
'directory_permissions' => 0755, // Directory permissions
],
],
]);The Redis driver requires the PHP Redis extension.
# Install Redis extension
pecl install redisCache::init([
'default' => 'redis',
'drivers' => [
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => 'your_password', // Optional
'database' => 0,
'timeout' => 2.0,
'persistent' => false, // Persistent connection
'persistent_id' => null,
'prefix' => 'myapp',
'ttl' => 3600,
],
],
]);// With default TTL
Cache::set('key', 'value');
// With custom TTL (in seconds)
Cache::set('key', 'value', 3600);
// Complex data
Cache::set('user', [
'id' => 123,
'name' => 'John',
'email' => 'john@example.com',
], 3600);// Simple retrieval
$value = Cache::get('key');
// With default value
$value = Cache::get('key', 'default_value');
// Complex data
$user = Cache::get('user', []);if (Cache::has('key')) {
// Key exists
}Cache::delete('key');Cache::clear();$values = Cache::getMultiple(['key1', 'key2', 'key3'], null);
// Returns: ['key1' => value1, 'key2' => value2, 'key3' => value3]Cache::setMultiple([
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
], 3600); // Common TTL for all keys$deleted = Cache::deleteMultiple(['key1', 'key2', 'key3']);
// Returns the number of deleted keys// Increment
Cache::set('counter', 0);
Cache::increment('counter'); // 1
Cache::increment('counter', 5); // 6
// Decrement
Cache::decrement('counter'); // 5
Cache::decrement('counter', 2); // 3$value = Cache::pull('key'); // Retrieves and deletes in one operation// Use a specific driver
Cache::set('key', 'value', 3600, 'redis');
$value = Cache::get('key', null, 'redis');
// Or get the driver directly
$redisCache = Cache::driver('redis');
$redisCache->set('key', 'value');Tags allow grouping cache entries and invalidating them together.
// Create a tagged cache
$taggedCache = Cache::tags(['users', 'posts']);
// Store values with tags
$taggedCache->set('user_1', ['name' => 'John']);
$taggedCache->set('user_2', ['name' => 'Jane']);
// Get keys associated with a tag
$keys = $taggedCache->getKeysByTag('users');
// Invalidate all keys with a tag
$taggedCache->invalidateTags('users');
// or multiple tags
$taggedCache->invalidateTags(['users', 'posts']);use JulienLinard\Cache\CacheManager;
$manager = CacheManager::getInstance([
'default' => 'file',
'drivers' => [
'file' => ['path' => __DIR__ . '/cache'],
],
]);
// Get a driver
$driver = $manager->driver('file');
// Register a custom driver
$customDriver = new MyCustomDriver();
$manager->registerDriver('custom', $customDriver);
// Change the default driver
$manager->setDefaultDriver('redis');The system automatically validates keys for security:
- β
Allowed characters: letters, numbers,
_,-,. - β Maximum length: 250 characters
- β
Protection against path injections (
..,/,\)
use JulienLinard\Cache\KeyValidator;
// Validate a key
try {
KeyValidator::validate('valid_key_123');
} catch (InvalidKeyException $e) {
// Invalid key
}
// Sanitize a key
$cleanKey = KeyValidator::sanitize('invalid/key@test');
// Returns: 'invalid_key_test'use JulienLinard\Cache\Exceptions\CacheException;
use JulienLinard\Cache\Exceptions\InvalidKeyException;
use JulienLinard\Cache\Exceptions\DriverException;
try {
Cache::set('key', 'value');
} catch (InvalidKeyException $e) {
// Invalid key
} catch (DriverException $e) {
// Driver error
} catch (CacheException $e) {
// Other cache error
}- Key Validation: Protection against path injections
- Secure Serialization: JSON usage with strict validation
- File Permissions: Permission control for File driver
- Atomic Writing: File driver uses temporary files to prevent corruption
- Input Validation: All entries are validated before storage
// β
GOOD: Simple and descriptive keys
Cache::set('user_123', $userData);
// β BAD: Keys with special characters
Cache::set('user/123', $userData); // Throws exception
// β
GOOD: Use prefixes
Cache::init([
'drivers' => [
'file' => ['prefix' => 'myapp'],
],
]);
// β
GOOD: Validate data before caching
$data = validateAndSanitize($userInput);
Cache::set('key', $data);# Run tests
composer test
# With code coverage
composer test-coverageuse JulienLinard\Cache\Cache;
function getUser(int $id): array
{
$cacheKey = "user_{$id}";
// Check cache
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
// Retrieve from database
$user = fetchUserFromDatabase($id);
// Cache for 1 hour
Cache::set($cacheKey, $user, 3600);
return $user;
}use JulienLinard\Cache\Cache;
// Store users with tag
$usersCache = Cache::tags('users');
$usersCache->set('user_1', $user1, 3600);
$usersCache->set('user_2', $user2, 3600);
// When a user is updated, invalidate the tag
function updateUser(int $id, array $data): void
{
// Update in database
updateUserInDatabase($id, $data);
// Invalidate all entries with 'users' tag
$usersCache = Cache::tags('users');
$usersCache->invalidateTags('users');
}use JulienLinard\Cache\Cache;
function renderView(string $template, array $data): string
{
$cacheKey = 'view_' . md5($template . serialize($data));
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
$html = renderTemplate($template, $data);
Cache::set($cacheKey, $html, 1800); // 30 minutes
return $html;
}use JulienLinard\Cache\Cache;
function incrementPageViews(string $pageId): int
{
$key = "page_views_{$pageId}";
if (!Cache::has($key)) {
// Initialize with 24h expiration
Cache::set($key, 0, 86400);
}
return Cache::increment($key);
}use JulienLinard\Cache\Cache;
use JulienLinard\Doctrine\EntityManager;
function getCachedEntity(EntityManager $em, string $entityClass, int $id): ?object
{
$cacheKey = strtolower($entityClass) . "_{$id}";
if (Cache::has($cacheKey)) {
$data = Cache::get($cacheKey);
// Rebuild entity from data
return $em->getRepository($entityClass)->find($id);
}
$entity = $em->getRepository($entityClass)->find($id);
if ($entity) {
// Store entity data
Cache::set($cacheKey, $entity->toArray(), 3600);
}
return $entity;
}Cache::init(array $config): Initialize the managerCache::get(string $key, mixed $default = null, ?string $driver = null): Retrieve a valueCache::set(string $key, mixed $value, ?int $ttl = null, ?string $driver = null): Store a valueCache::has(string $key, ?string $driver = null): Check existenceCache::delete(string $key, ?string $driver = null): Delete a valueCache::clear(?string $driver = null): Clear cacheCache::increment(string $key, int $value = 1, ?string $driver = null): IncrementCache::decrement(string $key, int $value = 1, ?string $driver = null): DecrementCache::pull(string $key, mixed $default = null, ?string $driver = null): Retrieve and deleteCache::tags(string|array $tags, ?string $driver = null): Create tagged cacheCache::driver(?string $driver = null): Get a driver
All drivers implement CacheInterface with the following methods:
get(string $key, mixed $default = null): mixedset(string $key, mixed $value, ?int $ttl = null): booldelete(string $key): boolhas(string $key): boolclear(): boolgetMultiple(array $keys, mixed $default = null): arraysetMultiple(array $values, ?int $ttl = null): booldeleteMultiple(array $keys): intincrement(string $key, int $value = 1): int|falsedecrement(string $key, int $value = 1): int|falsepull(string $key, mixed $default = null): mixed
Check that the cache directory exists and is writable:
$cachePath = __DIR__ . '/cache';
if (!is_dir($cachePath)) {
mkdir($cachePath, 0755, true);
}- Check that Redis extension is installed:
php -m | grep redis - Check that Redis is running:
redis-cli ping - Check connection parameters in configuration
Keys must follow this format:
- Allowed characters:
a-z,A-Z,0-9,_,-,. - Maximum length: 250 characters
- No relative paths (
..,/,\)
MIT License - See the LICENSE file for more details.
Contributions are welcome! Feel free to open an issue or a pull request.
For any questions or issues, please open an issue on GitHub.
If this package is useful to you, consider becoming a sponsor to support the development and maintenance of this open source project.
Developed with β€οΈ by Julien Linard