andydefer/jsonl-cache
JSONL Cache
Un système de cache persistant compatible PSR-16 basé sur des fichiers JSONL pour PHP 8.1+
Table des matières
- Introduction
- Installation
- Configuration
- Concepts fondamentaux
- Utilisation de base
- Opérations avancées
- Gestion du TTL
- Intégration Laravel
- Tests
- Architecture technique
- Référence technique
- Licence
Introduction
Le problème
Les caches traditionnels (Redis, Memcached, APC) sont performants mais nécessitent :
- Des services externes supplémentaires
- Une configuration complexe
- Une gestion mémoire spécifique
- Un déploiement particulier
La solution : JSONL Cache
JSONL Cache est un système de cache persistant basé sur des fichiers JSONL (JSON Lines), compatible avec l'interface PSR-16 (Common Interface for Caching Libraries).
| Problème | Solution JSONL Cache |
|---|---|
| Dépendance à Redis/Memcached | Stockage fichiers - 100% PHP |
| Configuration complexe | Zéro configuration, prêt à l'emploi |
| Perte de données au redémarrage | Persistance automatique |
| Pas de TTL natif | Support complet du Time To Live |
| Interface propriétaire | PSR-16 : changez de driver sans modifier votre code |
Installation
composer require andydefer/jsonl-cache
Pour Laravel, le package s'enregistre automatiquement via son Service Provider.
Publication de la configuration (Laravel)
php artisan vendor:publish --tag=jsonl-cache-config
Configuration
Fichier de configuration
// config/jsonl-cache.php
return [
// Chemin de base pour les fichiers de cache
'base_path' => env('JSONL_CACHE_PATH', storage_path('jsonl-cache')),
// TTL par défaut en secondes (null = pas d'expiration)
'default_ttl' => (int) env('JSONL_CACHE_TTL', 3600),
// Nombre de niveaux de hash (1-4)
'hash_levels' => (int) env('JSONL_CACHE_HASH_LEVELS', 2),
// Activation/désactivation du cache
'enabled' => (bool) env('JSONL_CACHE_ENABLED', true),
// Préfixe ajouté aux clés
'prefix' => env('JSONL_CACHE_PREFIX', 'cache'),
];
Variables d'environnement
JSONL_CACHE_PATH=/custom/cache/path
JSONL_CACHE_TTL=7200
JSONL_CACHE_HASH_LEVELS=2
JSONL_CACHE_ENABLED=true
JSONL_CACHE_PREFIX=app
Concepts fondamentaux
Une entrée = un fichier JSONL
storage/jsonl-cache/
├── 0/
│ ├── d/
│ │ └── user_123.jsonl
│ └── f/
│ └── session_abc.jsonl
├── e/
│ └── 1/
│ └── product_456.jsonl
└── f/
└── 3/
└── config_app.jsonl
Structure d'un fichier cache
{
"key": "cache_user_123",
"value": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}",
"expires_at": "2026-06-14T15:30:00+00:00",
"created_at": "2026-06-14T14:30:00+00:00"
}
Organisation par hash MD5
| Niveau | Description | Exemple |
|---|---|---|
| 1 | Premier caractère du MD5 | e |
| 2 | Second caractère du MD5 | 1 |
| Fichier | Clé nettoyée | user_123.jsonl |
Pourquoi ? Éviter d'avoir trop de fichiers dans un même répertoire (limites du système de fichiers).
Utilisation de base
Instanciation du service
Sans Laravel :
use AndyDefer\JsonlCache\Services\JsonlCacheService;
use AndyDefer\JsonlCache\Config\JsonlCacheConfig;
use AndyDefer\LaravelJsonl\JsonlService;
use AndyDefer\LaravelJsonl\Contexts\JsonlContext;
use AndyDefer\JsonlCache\Strategies\CachePathStrategy;
use AndyDefer\DomainStructures\Services\HydrationService;
use AndyDefer\PhpServices\Services\FileSystemService;
$config = new JsonlCacheConfig(app('config'));
$strategy = new CachePathStrategy('/tmp/cache', 2);
$fs = new FileSystemService();
$hydration = new HydrationService();
$jsonl = new JsonlService($strategy, $fs, new JsonlContext());
$cache = new JsonlCacheService($jsonl, $strategy, $config, $hydration, $fs);
Avec Laravel :
use AndyDefer\JsonlCache\Contracts\JsonlCacheInterface;
class MyController extends Controller
{
public function __construct(
private readonly JsonlCacheInterface $cache,
) {}
public function index()
{
// Utilisation directe
}
}
Stocker une valeur
// Stocker avec TTL par défaut (config)
$cache->set('user_123', ['name' => 'John Doe']);
// Stocker pour 1 heure
$cache->set('user_123', $userData, 3600);
// Stocker sans expiration
$cache->set('config_app', $config, null);
Lire une valeur
// Lecture simple
$user = $cache->get('user_123');
// Avec valeur par défaut
$user = $cache->get('user_123', ['name' => 'Guest']);
// Vérifier l'existence
if ($cache->has('user_123')) {
echo "Cache hit!";
}
Supprimer une valeur
// Supprimer une entrée
$cache->delete('user_123');
// Vider tout le cache
$cache->clear();
Types de valeurs supportés
// Tableau
$cache->set('array_key', ['a' => 1, 'b' => 2]);
// Objet (devient tableau)
$cache->set('object_key', (object) ['name' => 'John']);
// Scalaires
$cache->set('string_key', 'hello');
$cache->set('int_key', 42);
$cache->set('float_key', 3.14);
$cache->set('bool_key', true);
$cache->set('null_key', null);
Opérations avancées
Opérations par lots (Multiple)
// Lecture multiple
$values = $cache->getMultiple(['user_123', 'user_456', 'user_789'], 'default');
// Stockage multiple
$cache->setMultiple([
'user_123' => ['name' => 'John'],
'user_456' => ['name' => 'Jane'],
'user_789' => ['name' => 'Bob'],
], 3600);
// Suppression multiple
$cache->deleteMultiple(['user_123', 'user_456']);
Accès aux données brutes
// Récupérer l'enregistrement complet
$record = $cache->getRecord('user_123');
if ($record) {
echo $record->key; // 'cache_user_123'
echo $record->value; // '{"name":"John"}'
echo $record->expires_at; // DateTimeVO
echo $record->created_at; // DateTimeVO
}
// Récupérer le JSON brut
$raw = $cache->getRaw('user_123');
// '{"key":"cache_user_123","value":"{\"name\":\"John\"}","expires_at":"..."}'
Écrasement automatique
La méthode set() écrase automatiquement la valeur existante :
$cache->set('key', 'old value');
$cache->set('key', 'new value'); // Écrase l'ancienne
Gestion du TTL
Différents formats de TTL
// TTL en secondes (int)
$cache->set('key', $value, 3600); // 1 heure
$cache->set('key', $value, 60); // 1 minute
// TTL via DateInterval
$cache->set('key', $value, new DateInterval('PT1H')); // 1 heure
$cache->set('key', $value, new DateInterval('P1D')); // 1 jour
// Pas de TTL (null = valeur par défaut de la config)
$cache->set('key', $value, null);
// Expiration désactivée (0)
$cache->set('key', $value, 0);
Comportement de l'expiration
// Stocker pour 1 seconde
$cache->set('expiring_key', 'temporary', 1);
// Immédiatement disponible
echo $cache->get('expiring_key'); // 'temporary'
// Attendre l'expiration
sleep(2);
// Plus disponible
echo $cache->get('expiring_key', 'default'); // 'default'
$cache->has('expiring_key'); // false
TTL par défaut
La configuration default_ttl s'applique automatiquement :
// config/jsonl-cache.php
'default_ttl' => 3600, // 1 heure par défaut
// Utilisation
$cache->set('key', $value); // Expire dans 1 heure
$cache->set('key', $value, 0); // Jamais
Intégration Laravel
Service Provider
Le package enregistre automatiquement :
// Aliases disponibles
$cache = app(JsonlCacheInterface::class);
$cache = app('jsonl-cache');
Exemple dans un contrôleur
<?php
namespace App\Http\Controllers;
use AndyDefer\JsonlCache\Contracts\JsonlCacheInterface;
use App\Models\User;
final class UserController extends Controller
{
private const CACHE_TTL = 300; // 5 minutes
public function __construct(
private readonly JsonlCacheInterface $cache,
) {}
public function show(int $id): JsonResponse
{
$cacheKey = "user_{$id}";
// Tentative de lecture du cache
$user = $this->cache->get($cacheKey);
if ($user === null) {
$user = User::find($id);
$this->cache->set($cacheKey, $user->toArray(), self::CACHE_TTL);
}
return response()->json($user);
}
public function update(int $id, Request $request): JsonResponse
{
// Mise à jour en base...
$user = User::find($id);
$user->update($request->validated());
// Invalidation du cache
$this->cache->delete("user_{$id}");
return response()->json(['message' => 'Updated']);
}
}
Exemple dans un service
<?php
namespace App\Services;
use AndyDefer\JsonlCache\Contracts\JsonlCacheInterface;
final class WeatherService
{
private const CACHE_TTL = 1800; // 30 minutes
public function __construct(
private readonly JsonlCacheInterface $cache,
private readonly WeatherApiClient $api,
) {}
public function getForecast(string $city): array
{
$cacheKey = "weather_{$city}";
$forecast = $this->cache->get($cacheKey);
if ($forecast !== null) {
return $forecast;
}
$forecast = $this->api->fetchForecast($city);
$this->cache->set($cacheKey, $forecast, self::CACHE_TTL);
return $forecast;
}
}
Tests
Tester le cache
<?php
namespace Tests\Unit;
use AndyDefer\JsonlCache\Services\JsonlCacheService;
use Tests\TestCase;
final class CacheTest extends TestCase
{
private JsonlCacheService $cache;
protected function setUp(): void
{
parent::setUp();
$this->cache = app(JsonlCacheInterface::class);
}
public function test_cache_set_and_get(): void
{
$key = 'test_key';
$value = ['name' => 'John', 'email' => 'john@example.com'];
$this->cache->set($key, $value);
$cached = $this->cache->get($key);
$this->assertEquals($value, $cached);
}
public function test_cache_ttl(): void
{
$key = 'expiring_key';
$value = 'temporary';
$this->cache->set($key, $value, 1);
$this->assertEquals($value, $this->cache->get($key));
sleep(2);
$this->assertNull($this->cache->get($key));
}
public function test_cache_delete(): void
{
$key = 'to_delete';
$this->cache->set($key, 'value');
$this->assertTrue($this->cache->has($key));
$this->cache->delete($key);
$this->assertFalse($this->cache->has($key));
}
}
Architecture technique
Composants principaux
| Composant | Rôle |
|---|---|
JsonlCacheService |
Service principal (implémentation PSR-16) |
CachePathStrategy |
Stratégie de chemin (organisation par hash MD5) |
CacheRecord |
DTO des données de cache |
JsonlService |
Service de lecture/écriture JSONL (package laravel-jsonl) |
JsonlCacheConfig |
Wrapper de configuration |
JsonlCacheInterface |
Interface PSR-16 étendue |
Dépendances
JsonlCacheService
├── JsonlService (laravel-jsonl)
├── CachePathStrategy
├── JsonlCacheConfig
├── HydrationService
└── FileSystemInterface
Flux d'exécution (set)
set($key, $value, $ttl)
│
├── normalizeKey() → ajout préfixe, hash si >64
├── getTtlSeconds() → conversion TTL
├── createExpiresAt() → DateTimeVO
├── json_encode($value) → sérialisation
├── Suppression ancien fichier
├── Création CacheRecord
└── jsonl->write() → écriture JSONL
Flux d'exécution (get)
get($key)
│
├── normalizeKey()
├── getRecord()
│ ├── strategy->getFilePathForKey()
│ ├── fs->exists()
│ └── jsonl->readAll()
├── isExpired() → vérification expiration
├── delete() si expiré
└── json_decode($record->value) → désérialisation
Référence technique
Services
| Service | Description | Documentation |
|---|---|---|
JsonlCacheService |
Service principal PSR-16 | Voir référence |
Stratégies
| Stratégie | Description | Documentation |
|---|---|---|
CachePathStrategy |
Organisation par hash MD5 | Voir référence |
Interfaces
| Interface | Description | Documentation |
|---|---|---|
JsonlCacheInterface |
PSR-16 + méthodes additionnelles | Voir référence |
Licence
MIT © Andy Defer
Related Packages
Powerful PHP database abstraction layer (DBAL) with many features for database s...
Laravel Serializable Closure provides an easy and secure way to serialize closur...
Cli error handling for console/command-line PHP applications.
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v0.3.57 | ^8.2 | ^12.0| | MIT | |
| v0.3.56 | ^8.2 | ^12.0| | MIT | |
| v0.3.55 | ^8.2 | ^12.0| | MIT | |
| v0.3.54 | ^8.2 | ^12.0| | MIT | |
| v0.3.53 | ^8.2 | ^12.0| | MIT | |
| v0.3.52 | ^8.2 | ^12.0| | MIT | |
| v0.3.51 | ^8.2 | ^12.0| | MIT | |
| v0.3.50 | ^8.2 | ^12.0| | MIT | |
| v0.3.49 | ^8.2 | ^12.0| | MIT | |
| v0.3.48 | ^8.2 | ^12.0| | MIT | |
| v0.3.47 | ^8.2 | ^12.0| | MIT | |
| v0.3.46 | ^8.2 | ^12.0| | MIT | |
| v0.3.45 | ^8.2 | ^12.0| | MIT | |
| v0.3.44 | ^8.2 | ^12.0| | MIT | |
| v0.3.43 | ^8.2 | ^12.0| | MIT | |
| v0.3.42 | ^8.2 | ^12.0| | MIT | |
| v0.3.41 | ^8.2 | ^12.0| | MIT | |
| v0.3.40 | ^8.2 | ^12.0| | MIT | |
| v0.3.39 | ^8.2 | ^12.0| | MIT | |
| v0.3.38 | ^8.2 | ^12.0| | MIT | |
| v0.3.37 | ^8.2 | ^12.0| | MIT | |
| v0.3.36 | ^8.2 | ^12.0| | MIT | |
| v0.3.35 | ^8.2 | ^12.0| | MIT | |
| v0.3.34 | ^8.2 | ^12.0| | MIT | |
| v0.3.33 | ^8.2 | ^12.0| | MIT | |
| v0.3.32 | ^8.2 | ^12.0| | MIT | |
| v0.3.31 | ^8.2 | ^12.0| | MIT | |
| v0.3.30 | ^8.2 | ^12.0| | MIT | |
| v0.3.29 | ^8.2 | ^12.0| | MIT | |
| v0.3.28 | ^8.2 | ^12.0| | MIT |
Showing the latest 30 of 60. See every release on Packagist