openclassrooms/cache
openclassrooms/cache is a straightforward caching package for PHP applications. It lets you store and retrieve reusable data to avoid redundant work, improve response times, and simplify everyday caching needs. Lightweight and easy to integrate into OpenClassrooms-style projects.
Install via Composer (check the repo's composer.json for the PHP version and the doctrine/cache constraint before you install — this package pins to the pre-PSR-6 Doctrine Cache line):
composer require openclassrooms/cache
Read src/ before the README. With a package this small and this lightly adopted, the source is the documentation. Look for:
Doctrine\Common\Cache\CacheProvider (or Cache);Confirm the Doctrine Cache namespace in use. Older versions use Doctrine\Common\Cache\*; the newer split uses Doctrine\Cache\*. Match the import in src/ to avoid a fatal class-not-found at boot.
Wire it into the Laravel container in a service provider so you never instantiate it inline:
// app/Providers/AppServiceProvider.php
$this->app->singleton(\OpenClassrooms\Cache\...::class, function ($app) {
return new \OpenClassrooms\Cache\...(
new \Doctrine\Common\Cache\RedisCache(/* ... */)
);
});
First use case: cache an expensive read (a dashboard aggregate, a third-party API payload, a rendered fragment) behind a stable key, with an explicit TTL. Prove the read-through path works, then prove invalidation works. If invalidation doesn't behave as expected, stop and read Gotchas below before layering it into more code.
Read-through with an explicit miss path. The Doctrine contract is fetch() returning false on a miss. Wrap it in a small helper so callers don't repeat the === false dance:
public function remember(string $key, int $ttl, callable $resolve)
{
$value = $this->cache->fetch($key);
if ($value === false) {
$value = $resolve();
$this->cache->save($key, $value, $ttl);
}
return $value;
}
Decorator around repositories, not inside models. Keep Eloquent models cache-agnostic. Put caching in a repository or query-object decorator so the domain layer stays testable and swapping the cache backend is a one-line binding change.
Chain cache for hot reads. Stock Doctrine ships ChainCache. If this package wraps it, use ArrayCache (request-scoped) in front of Redis so a single request that reads the same value ten times hits the network once. Bind the chain as the singleton and the individual providers as named bindings.
Namespace your keys aggressively. Every key should carry an app/domain prefix (oc:users:{id}:profile). This is the single cheapest protection against a shared Redis instance and against a second package writing to the same keyspace.
Invalidation via versioned keys, not delete. For collections (list of users, product catalogue), keep a version counter and embed it in the key. On write, bump the counter; the old keys age out by TTL. This avoids trying to deleteMultiple an unbounded key set.
Warmers as Artisan commands. Any "build the cache" logic belongs in a php artisan cache:warm command so deploys and cron can prime the cache instead of users eating the cold-start latency.
Test with the Array/Null provider. Bind ArrayCache (or NullCache) in the test environment so the suite never touches Redis and cache behaviour is deterministic per test. Add one integration test against the real backend.
symfony/cache or Laravel's own Cache facade is a swap of one class. Don't let the package's types leak into domain signatures.false, not null. If you cache a legitimate false (a boolean flag, a "not found" marker), fetch() is indistinguishable from a miss. Wrap booleans in an array or use contains() alongside fetch().0 means "forever" in Doctrine Cache. Passing 0 from a config default silently creates immortal keys. Always pass a positive int or explicitly document "no expiry".PackageManifest service provider. Register the binding yourself; don't expect php artisan vendor:publish to do anything.RedisCache/MemcachedCache serialize values. If you cache Eloquent models, you're serializing a lot more than you think (and rehydrating detached instances). Cache arrays/scalars or DTOs, never models.md5($key) loses debuggability, so prefer substr plus a short hash suffix and log the original key on write.getStats() is your first debugging tool. If a key "isn't being set", check stats before blaming TTL: it tells you hit/miss counts and whether a write ever landed.deleteAll(). deleteAll() on a namespace can be O(n) or blocked on some backends (Redis scans). In production prefer the versioned-key pattern from above; reserve deleteAll() for dev and tests.SETNX lock around expensive resolvers.composer.json and read the diff before bumping.How can I help you explore Laravel packages today?