byjg/cache-engine
byjg/cache-engine is a flexible PHP caching library with a consistent API and pluggable backends like Redis, Memcached, APCu, files, and databases. It makes adding portable, fast caching to PHP and Laravel applications straightforward.
byjg/cache-engine is not a Laravel package — there's no service provider, no auto-discovery, no facade. It's a framework-agnostic PSR-6 + PSR-16 implementation that you wire up yourself. Install it and read the driver table in the README before anything else, because the driver list is the package's value proposition.
composer require byjg/cache-engine
Before you install: audit whether you actually need it. Laravel already ships file, database, Redis, Memcached, array, and DynamoDB stores. You want this package when:
Psr\Cache\CacheItemPoolInterface (PSR-6) and Laravel's Repository doesn't implement it.First use case — bind a PSR-16 instance so an injected library stops spinning its own cache:
// app/Providers/CacheEngineServiceProvider.php
use ByJG\Cache\Factory;
use Psr\SimpleCache\CacheInterface;
public function register(): void
{
$this->app->singleton(CacheInterface::class, function ($app) {
return Factory::createInstance(
config('cache-engine.driver'), // e.g. 'redis', 'filesystem', 'array'
config('cache-engine.params') // driver-specific array
);
});
}
Start with the array driver and confirm your call sites work before pointing it at Redis. The package's driver constants and parameter keys vary by version — copy them verbatim from the README rather than guessing, and pin the version in composer.json.
1. Config file per driver, not per call site. Create config/cache-engine.php with driver + params and read it only in the service provider. Every other class should type-hint the PSR interface, never the concrete pool. This is what lets you rip the package out later.
2. Adopt it into the facade via a custom Store. If you want Cache::store('cache_engine')->get(...) to work, implement Illuminate\Contracts\Cache\Store as a thin delegate to the PSR-16 instance and register it:
Cache::extend('cache_engine', function ($app) {
return Cache::repository(new Psr16Store($app->make(CacheInterface::class)));
});
Now the helper, the facade, and event/failure hooks behave normally while the backing store is the package. Watch the TTL unit conversion here — see Gotchas.
3. Namespaced key builder. PSR-16 has no tag support and no namespace concept. Inject a small CacheKeys service that prefixes app:feature:. This is your only defence against collisions when two subsystems share one Redis database.
4. One pool per subsystem when semantics differ. Use a separate filesystem path or key prefix for ephemeral caches (rendered fragments, API responses) vs. long-lived ones (settings, computed lookups), so you can flush one without nuking the other.
5. Inject the interface, swap in tests. Bind the array driver under APP_ENV=testing in phpunit.xml. Because you bound a singleton, values leak between tests — flush in setUp() or rebind per test.
6. Long-running workers (Octane, Horizon). Register as singleton so you don't reconnect per resolve, but use scoped if your driver holds per-request state. Anything holding a socket needs a plan for reconnect after Redis timeouts.
7. PSR-6 usage pattern. Always check the hit flag — getItem() returns a fresh miss item rather than null:
$item = $pool->getItem($key);
if (!$item->isHit()) {
$item->set($this->expensiveLookup())->expiresAfter(3600);
$pool->save($item);
}
return $item->get();
8. Migration tip. Run both backends in parallel behind a wrapper that reads from Laravel's cache and writes to both, then cut over. There's no shared key format, so a straight swap is a cold cache.
{ } ( ) / \ @ : are illegal in keys and the implementation is entitled to throw InvalidArgumentException. Laravel's cache is far more forgiving, so code migrated from Cache::put("user/{$id}/profile") will blow up. Hash or slug key segments.0 or negative means delete immediately. In Laravel, 0/null means forever. This is the single most common bug when adapting code — normalize in one place, ideally in your adapter store.hasItem() is a trap. Some drivers answer it without an actual read, and stale/expired entries can report inconsistently. Trust getItem()->isHit().increment/decrement are explicitly non-atomic per the spec. Don't build rate limiters or counters on them; use Redis primitives.Cache::tags(), no remember(). If your app leans on tag flushing, this package will not replace Laravel's cache — run it alongside.unserialize() failures. Bake a schema version into your key prefix and bump it on breaking changes.ext-redis / ext-memcached and the corresponding client library. Confirm which client the Redis driver expects (phpredis vs predis) — they are not interchangeable, and the mismatch surfaces as a class-not-found at runtime, not at boot.save() returns bool and nobody checks it. A full disk or evicted Memcached entry looks identical to success. Log false returns in staging at minimum.array driver first to isolate logic bugs from driver bugs, then switch drivers. If it only fails under array, it's your TTL or key handling, not the backend."byjg/cache-engine": "5.x.y"), commit the lockfile, and keep every call site behind Psr\SimpleCache\CacheInterface / Psr\Cache\CacheItemPoolInterface so replacing it with a three-line Laravel adapter is a one-file change.Factory is the only entry. To add a driver, either wrap the Factory in your own resolver that returns your implementation for unknown names, or implement CacheItemPoolInterface yourself and register it in your container alongside the package's.How can I help you explore Laravel packages today?