A high-performance, multi-level caching system for Symfony applications. This bundle provides a flexible, extensible, and developer-friendly way to combine multiple cache backends (memory, Redis, file, etc.) for optimal speed and reliability.
It has two parts to it.
- The Multi Level Cache Itself.
- The Cached Service Generator
The Main way of using this package is via the Cached Service Generator (CSG), so this will be the focus of this documentations. Details can be found [todo: write an actuall in depth documentation of the MLC] here
Install via Composer from GitHub:
- Add the repository to your
composer.json:{ "repositories": [ { "type": "vcs", "url": "https://github.com/tbessenreither/multi-level-cache" } ] } - Require the package:
composer require tbessenreither/multi-level-cache
- Enable the Bundle in
config/bundles.php:Tbessenreither\MultiLevelCache\MultiLevelCacheBundle::class => ['all' => true],
- Configure Environment Variables as needed:
REDIS_DSN(if using Redis, so basically always)MLC_DISABLE_READ(optional, disables cache reads)MLC_COLLECT_ENHANCED_DATA(optional, enables enhanced data collection but has performance impact)
Its a fast multi level cache consisting, in it's standard configuration, of:
- a fast In Memory Ring Cache
- and a second Level Redis Cache
- Faster and much more Memory Efficient than the Symfony Cache (We love it, but we needed something faster)
- Cache statistics and profiling (Symfony Profiler integration)
- Beta decay and TTL randomization to prevent stampedes
- Exception handling and diagnostics
This is the main thing you hopefully will interact with as the goal of this package is to make manual cache implementations a thing of the past.
This is the main way of using the MLC. The basic idea is to implement your services without any caching logic and then wrapp them within an API Compatible Wrapper (Front Loaded Caching).
To create a cached version of a service you can use the ddev mlc-make App.Service.TestService command.
Cachable Methods are marked as such with the #[MlcCachableMethod(ttlSeconds: 300)] attribute.
Everything else is hands off.
Given this example TestService
<?php
declare(strict_types=1);
namespace Tbessenreither\Example\Service;
use Tbessenreither\Example\Entity\HotelEntity;
use Tbessenreither\MultiLevelCache\CachedServiceGenerator\Attribute\MlcCachableMethod;
readonly class TestService
{
public function getHotel(string $id): HotelEntity
{
return $this->getBrandByIri(sprintf('%s/%s', self::getApiUrl(), $id));
}
#[MlcCachableMethod(ttlSeconds: 600)]
public function getHotelByIri(string $iri): HotelEntity
{
$brand = $this->getObjectContent(
pageId: $iri,
query: [],
className: HotelEntity::class
);
return $brand;
}
// [...]
}We've added the Attribute to the Endpoint we want to cache. And now we need to run ddev mlc-make Tbessenreither.Example.Service to generate our Cache wrapper. Now the following will happen.
- It will create a
TestServiceInterfacefor theTestService. - The Generator will create a wrapper under the name
TestServiceCachedthat implements theTestServiceInterfaceto ensure compatibility - It will add the
TestServiceInterfaceto the original Class
The Cache Wrapper is stored in the same directory as your Original TestService postfixed with Cached giving you TestServiceCached.
The TestServiceInterface will be put into the appropriate Interface directory.
The Output of the make command will look somewhat like this:
You basically have two options here.
- You can either swap out the
TestServiceforTestServiceCacheddirectly as a drop in replacement - Or you can inject the
TestServiceInterfaceand configure the Version you want to use via theservices.yaml
Do whatever fits your needs best. The MLC has no opinions on how to do this.
This is nice and all. But what if i change something in my TestService? Do i need to update everything again?
No, good god no.
You just run ddev mlc-update. This command will auto detect all Cache Wrappers and update them + the interfaces with the latest methods and MlcCachableMethod annotations.
The command output will show you something like this:
If any service can't be updated (There are some reasons why this might happen) it will show this in the status collumn and print a detailed reason in the Message collumn.
MIT

