Important
You are browsing the documentation for version 4.2 of OroCommerce, OroCRM and OroPlatform, which is no longer maintained. Read version 5.1 (the latest LTS version) of the Oro documentation to get up-to-date information.
See our Release Process documentation for more information on the currently supported and upcoming releases.
OroCacheBundle¶
OroCacheBundle introduces the configuration of the application data cache storage used by the application bundles for different cache types.
Abstract Cache Services¶
There are two abstract services you can use as a parent for your cache services:
oro.cache.abstract
- this cache should be used for caching data that need to be shared between nodes in a web farmoro.cache.abstract.without_memory_cache
- the same asoro.cache.abstract
but without using additional in-memory caching, it can be used to avoid unnecessary memory usage and performance penalties if in-memory caching is not needed, e.g. you implemented some more efficient in-memory caching strategy around your cache service
The following example shows how these services can be used:
services:
acme.test.cache:
public: false
parent: oro.cache.abstract
calls:
- [ setNamespace, [ 'acme_test' ] ]
Also the oro.cache.abstract
service can be re-declared
in the application configuration file, for example:
services:
oro.cache.abstract:
abstract: true
class: Oro\Bundle\CacheBundle\Provider\PhpFileCache
arguments: [%kernel.cache_dir%/oro_data]
The oro.cache.abstract.without_memory_cache service
is always declared automatically based on
oro.cache.abstract
service.
Caching Static Configuration¶
A static configuration is defined in the configuration files and does not depend on the application data. Usually such configuration is loaded from configuration files located in different bundles, e.g. from Resources/config/oro/my_config.yml files that can be located in any bundle. There are several possible ways to store the collected configuration to avoid loading and merging it on each request:
As a parameter in the dependency injection container. The disadvantage of this approach is not very good DX (Developer Experience) because each time when the configuration is changed the whole container should be rebuilt.
As a data file in the system cache. This approach has better DX as this is the only file that needs rebuilding after the configuration is changed. However, the disadvantage is that data should be deserialized every time it is requested.
As a PHP file in the system cache. It has the same DX as the previous approach but with two important additional advantages: the deserialization of the data is not needed and the loaded data is cached by OPcache.
To implement 3rd approach for your configuration, you need to take the following steps:
Create PHP class that defines the schema of your configuration and validation and merging rules for it. E.g.:
namespace Acme\Bundle\AcmeBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class MyConfiguration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('my_config');
// build the configuration tree here
return $treeBuilder;
}
}
Create the configuration provider PHP class that you will use to get the configuration data. E.g.:
namespace Acme\Bundle\AcmeBundle\Provider;
use Acme\Bundle\AcmeBundle\DependencyInjection\MyConfiguration;
use Oro\Component\Config\Cache\PhpArrayConfigProvider;
use Oro\Component\Config\Loader\CumulativeConfigLoader;
use Oro\Component\Config\Loader\CumulativeConfigProcessorUtil;
use Oro\Component\Config\Loader\YamlCumulativeFileLoader;
use Oro\Component\Config\ResourcesContainerInterface;
class MyConfigurationProvider extends PhpArrayConfigProvider
{
private const CONFIG_FILE = 'Resources/config/oro/my_config.yml';
public function getConfiguration(): array
{
return $this->doGetConfig();
}
/**
* {@inheritdoc}
*/
protected function doLoadConfig(ResourcesContainerInterface $resourcesContainer)
{
$configs = [];
$configLoader = new CumulativeConfigLoader(
'my_config',
new YamlCumulativeFileLoader(self::CONFIG_FILE)
);
$resources = $configLoader->load($resourcesContainer);
foreach ($resources as $resource) {
$configs[] = $resource->data;
}
return CumulativeConfigProcessorUtil::processConfiguration(
self::CONFIG_FILE,
new MyConfiguration(),
$configs
);
}
}
Register the created configuration provider as a service using
oro.static_config_provider.abstract
service as the parent one. E.g.:
services:
acme.my_configuration_provider:
class: Acme\Bundle\AcmeBundle\Provider\MyConfigurationProvider
public: false
parent: oro.static_config_provider.abstract
arguments:
- '%kernel.cache_dir%/oro/my_config.php'
- '%kernel.debug%'
The cache warmer is registered automatically with the priority 200
. This priority adds the warmer at the begin
of the warmers chain that prevents double warmup in case some Application cache depends on the static config cache.
The warmer service ID is the configuration provider service ID prefixed with .warmer
. If you want to change
the priority or use your own warmer, you can register the service following these naming conventions.
In this case a default warmer will not be registered for your configuration provider.
An example of a custom warmer:
services:
acme.my_configuration_provider.warmer:
class: Oro\Component\Config\Cache\ConfigCacheWarmer
public: false
arguments:
- '@acme.my_configuration_provider'
tags:
- { name: kernel.cache_warmer }
If your Application cache depends on your configuration, use isCacheFresh($timestamp)
and getCacheTimestamp()
methods of the configuration provider to check if the Application cache needs to be rebuilt.
Here is an example how to use these methods:
private function ensureConfigLoaded()
{
if (null !== $this->configuration) {
return;
}
$config = $this->fetchConfigFromCache();
if (null === $config) {
$config = $this->loadConfig();
$this->saveConfigToCache($config);
}
$this->configuration = $config;
}
private function fetchConfigFromCache(): ?array
{
$config = null;
$cachedData = $this->cache->fetch(self::CACHE_KEY);
if (false !== $cachedData) {
list($timestamp, $value) = $cachedData;
if ($this->configProvider->isCacheFresh($timestamp)) {
$config = $value;
}
}
return $config;
}
private function saveConfigToCache(array $config): void
{
$this->cache->save(self::CACHE_KEY, [$this->configProvider->getCacheTimestamp(), $config]);
}
private function loadConfig(): array
{
$config = $this->configProvider->getConfiguration();
// add some additional processing of the configuration here
return $config;
}
Caching Symfony Validation Rules¶
By default, rules for Symfony Validation Component are cached using oro.cache.abstract
service,
but you can change this to make validation caching suit some custom requirements. To do this, you need
to redefine the oro_cache.provider.validation
service.
Memory Based Cache¶
One of the most important things when dealing with caches is proper cache invalidation. When using memory based cache, we need to make sure that we do not keep old values in the memory. Consider this example:
class LocalizationManager
{
private \Doctrine\Common\Cache\ArrayCache $cacheProvider;
public function getLocalization($id)
{
$localization = $this->cacheProvider->fetch($id);
// ... all other operations, fetch from DB if cache is empty
// ... save in cache data from DB
return $localization;
}
}
Since $cacheProvider
in our example is an implementation of memory ArrayCache
, we will keep the data
there until the process ends. With HTTP request this would work perfectly well, but when our
LocalizationManager
is used in some long-running CLI processes, we have to manually clear memory cache
after every change with Localizations. Missing cache clearing for any of these cases leads
to outdated data in LocalizationManager
.
Default Cache Implementation¶
There are two abstract services you can use as a parent for your cache services. Default implementations are following:
for CLI requests: MemoryCacheChain with only FilesystemCache as a cache provider
for other requests: MemoryCacheChain with ArrayCache on the top of FilesystemCache
For services based on oro.cache.abstract.without_memory_cache
the MemoryCacheChain is not used.
Caching Data based on Complex Objects¶
Cache Hit Ratio is an important measure of cache efficiency. Choosing the right Cache Key might be a complex task, especially when the cache key should depend on the data stored in a Complex Object. Cache key generation strategy can vary in different cases and rely on different fields of the same object. To configure cache metadata for different scopes, use Resources/config/oro/cache_metadata.yml files that can be located in any bundle.
Here is an example of such configuration:
Oro\Bundle\OrderBundle\Entity\OrderAddress:
attributes:
id:
groups: ['shipping_context']
country:
groups: ['shipping_context', 'promotion']
Data from this configuration is used by the oro.cache.generator.object_cache_key
service to provide cache keys for the
given object and scope.
Sometimes there is a need to configure cache key generation metadata for objects that cannot be normalized by
the default normalizer GetSetMethodNormalizer
. In such case new normalizer should be created and registered as a service
tagged with oro_cache_generator_normalizer
.
acme_cache.serializer.normalizer:
class: AcmeBundle\Normalizer\CustomObjectNormalizer
tags:
- { name: 'oro_cache_generator_normalizer' }