vendor/ezsystems/ezpublish-kernel/eZ/Publish/Core/Persistence/Cache/AbstractInMemoryHandler.php line 89

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) eZ Systems AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. namespace eZ\Publish\Core\Persistence\Cache;
  7. use eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface;
  8. use eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache;
  9. /**
  10.  * Abstract handler for use in other SPI Handlers.
  11.  *
  12.  * @internal Can be used in external handlers, but be aware this should be regarded as experimental feature.
  13.  *           As in, method signatures and behaviour might change in the future.
  14.  */
  15. abstract class AbstractInMemoryHandler
  16. {
  17.     /**
  18.      * NOTE: Instance of this must be TransactionalInMemoryCacheAdapter in order for cache clearing to affect in-memory cache.
  19.      *
  20.      * @var \eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface
  21.      */
  22.     protected $cache;
  23.     /** @var \eZ\Publish\Core\Persistence\Cache\PersistenceLogger */
  24.     protected $logger;
  25.     /**
  26.      * NOTE: On purpose private as it's only supposed to be interacted with in tandem with symfony cache here,
  27.      *       hence the cache decorator and the reusable methods here.
  28.      *
  29.      * @var \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache
  30.      */
  31.     private $inMemory;
  32.     /**
  33.      * Setups current handler with everything needed.
  34.      *
  35.      * @param \eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface $cache
  36.      * @param \eZ\Publish\Core\Persistence\Cache\PersistenceLogger $logger
  37.      * @param \eZ\Publish\Core\Persistence\Cache\InMemory\InMemoryCache $inMemory
  38.      */
  39.     public function __construct(
  40.         TransactionAwareAdapterInterface $cache,
  41.         PersistenceLogger $logger,
  42.         InMemoryCache $inMemory
  43.     ) {
  44.         $this->cache $cache;
  45.         $this->logger $logger;
  46.         $this->inMemory $inMemory;
  47.     }
  48.     /**
  49.      * Load one cache item from cache and loggs the hits / misses.
  50.      *
  51.      * Load items from in-memory cache, symfony cache pool or backend in that order.
  52.      * If not cached the returned objects will be placed in cache.
  53.      *
  54.      * @param int|string $id
  55.      * @param string $keyPrefix E.g "ez-content-"
  56.      * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
  57.      *                                expects return value to be array with id as key. Missing items should be missing.
  58.      * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
  59.      * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
  60.      * @param string $keySuffix Optional, e.g "-by-identifier"
  61.      *
  62.      * @return object
  63.      */
  64.     final protected function getCacheValue(
  65.         $id,
  66.         string $keyPrefix,
  67.         callable $backendLoader,
  68.         callable $cacheTagger,
  69.         callable $cacheIndexes,
  70.         string $keySuffix '',
  71.         array $arguments = []
  72.     ) {
  73.         $key $keyPrefix $id $keySuffix;
  74.         // In-memory
  75.         if ($object $this->inMemory->get($key)) {
  76.             $this->logger->logCacheHit($arguments ?: [$id], 3true);
  77.             return $object;
  78.         }
  79.         // Cache pool
  80.         $cacheItem $this->cache->getItem($key);
  81.         if ($cacheItem->isHit()) {
  82.             $this->logger->logCacheHit($arguments ?: [$id], 3);
  83.             $this->inMemory->setMulti([$object $cacheItem->get()], $cacheIndexes);
  84.             return $object;
  85.         }
  86.         // Backend
  87.         // (log misses first in case of $backendLoader ends up throwing NotFound)
  88.         $this->logger->logCacheMiss($arguments ?: [$id], 3);
  89.         $object $backendLoader($id);
  90.         $this->inMemory->setMulti([$object], $cacheIndexes);
  91.         $this->cache->save(
  92.             $cacheItem
  93.                 ->set($object)
  94.                 ->tag($cacheTagger($object))
  95.         );
  96.         return $object;
  97.     }
  98.     /**
  99.      * Load list of objects of some type and loggs the hits / misses.
  100.      *
  101.      * Load items from in-memory cache, symfony cache pool or backend in that order.
  102.      * If not cached the returned objects will be placed in cache.
  103.      *
  104.      * @param string $key
  105.      * @param callable $backendLoader Function for loading ALL objects, value is cached as-is.
  106.      * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
  107.      * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
  108.      * @param callable $listTags Optional, global tags for the list cache.
  109.      * @param array $arguments Optional, arguments when parnt method takes arguments that key varies on.
  110.      *
  111.      * @return array
  112.      */
  113.     final protected function getListCacheValue(
  114.         string $key,
  115.         callable $backendLoader,
  116.         callable $cacheTagger,
  117.         callable $cacheIndexes,
  118.         callable $listTags null,
  119.         array $arguments = []
  120.     ) {
  121.         // In-memory
  122.         if ($objects $this->inMemory->get($key)) {
  123.             $this->logger->logCacheHit($arguments3true);
  124.             return $objects;
  125.         }
  126.         // Cache pool
  127.         $cacheItem $this->cache->getItem($key);
  128.         if ($cacheItem->isHit()) {
  129.             $this->logger->logCacheHit($arguments3);
  130.             $this->inMemory->setMulti($objects $cacheItem->get(), $cacheIndexes$key);
  131.             return $objects;
  132.         }
  133.         // Backend
  134.         // (log misses first in case of $backendLoader ends up throwing NotFound)
  135.         $this->logger->logCacheMiss($arguments3);
  136.         $objects $backendLoader();
  137.         $this->inMemory->setMulti($objects$cacheIndexes$key);
  138.         if ($listTags !== null) {
  139.             $tagSet = [$listTags()];
  140.         } else {
  141.             $tagSet = [[]];
  142.         }
  143.         foreach ($objects as $object) {
  144.             $tagSet[] = $cacheTagger($object);
  145.         }
  146.         $this->cache->save(
  147.             $cacheItem
  148.                 ->set($objects)
  149.                 ->tag(array_unique(array_merge(...$tagSet)))
  150.         );
  151.         return $objects;
  152.     }
  153.     /**
  154.      * Load several cache items from cache and loggs the hits / misses.
  155.      *
  156.      * Load items from in-memory cache, symfony cache pool or backend in that order.
  157.      * If not cached the returned objects will be placed in cache.
  158.      *
  159.      * Cache items must be stored with a key in the following format "${keyPrefix}${id}", like "ez-content-info-${id}",
  160.      * in order for this method to be able to prefix key on id's and also extract key prefix afterwards.
  161.      *
  162.      * @param array $ids
  163.      * @param string $keyPrefix E.g "ez-content-"
  164.      * @param callable $backendLoader Function for loading missing objects, gets array with missing id's as argument,
  165.      *                                expects return value to be array with id as key. Missing items should be missing.
  166.      * @param callable $cacheTagger Gets cache object as argument, return array of cache tags.
  167.      * @param callable $cacheIndexes Gets cache object as argument, return array of cache keys.
  168.      * @param string $keySuffix Optional, e.g "-by-identifier"
  169.      *
  170.      * @return array
  171.      */
  172.     final protected function getMultipleCacheValues(
  173.         array $ids,
  174.         string $keyPrefix,
  175.         callable $backendLoader,
  176.         callable $cacheTagger,
  177.         callable $cacheIndexes,
  178.         string $keySuffix '',
  179.         array $arguments = []
  180.     ): array {
  181.         if (empty($ids)) {
  182.             return [];
  183.         }
  184.         // Generate unique cache keys and check if in-memory
  185.         $list array_flip($ids);
  186.         $cacheKeys = [];
  187.         $cacheKeysToIdMap = [];
  188.         foreach (array_unique($ids) as $id) {
  189.             $key $keyPrefix $id $keySuffix;
  190.             if ($object $this->inMemory->get($key)) {
  191.                 $list[$id] = $object;
  192.             } else {
  193.                 $cacheKeys[] = $key;
  194.                 $cacheKeysToIdMap[$key] = $id;
  195.             }
  196.         }
  197.         // No in-memory misses
  198.         if (empty($cacheKeys)) {
  199.             $this->logger->logCacheHit($arguments ?: $ids3true);
  200.             return $list;
  201.         }
  202.         // Load cache items by cache keys (will contain hits and misses)
  203.         $loaded = [];
  204.         $cacheMisses = [];
  205.         foreach ($this->cache->getItems($cacheKeys) as $key => $cacheItem) {
  206.             $id $cacheKeysToIdMap[$key];
  207.             if ($cacheItem->isHit()) {
  208.                 $list[$id] = $cacheItem->get();
  209.                 $loaded[$id] = $list[$id];
  210.             } else {
  211.                 $cacheMisses[] = $id;
  212.                 $list[$id] = $cacheItem;
  213.             }
  214.         }
  215.         // No cache pool misses, cache loaded items in-memory and return
  216.         if (empty($cacheMisses)) {
  217.             $this->logger->logCacheHit($arguments ?: $ids3);
  218.             $this->inMemory->setMulti($loaded$cacheIndexes);
  219.             return $list;
  220.         }
  221.         // Load missing items, save to cache & apply to list if found
  222.         $backendLoadedList $backendLoader($cacheMisses);
  223.         foreach ($cacheMisses as $id) {
  224.             if (isset($backendLoadedList[$id])) {
  225.                 $this->cache->save(
  226.                     $list[$id]
  227.                         ->set($backendLoadedList[$id])
  228.                         ->tag($cacheTagger($backendLoadedList[$id]))
  229.                 );
  230.                 $loaded[$id] = $backendLoadedList[$id];
  231.                 $list[$id] = $backendLoadedList[$id];
  232.             } else {
  233.                 // not found
  234.                 unset($list[$id]);
  235.             }
  236.         }
  237.         // Save cache & log miss (this method expects multi backend loader to return empty array over throwing NotFound)
  238.         $this->inMemory->setMulti($loaded$cacheIndexes);
  239.         unset($loaded$backendLoadedList);
  240.         $this->logger->logCacheMiss($arguments ?: $cacheMisses3);
  241.         return $list;
  242.     }
  243.     /**
  244.      * Escape an argument for use in cache keys when needed.
  245.      *
  246.      * WARNING: Only use the result of this in cache keys, it won't work to use loading the item from backend on miss.
  247.      *
  248.      * @param string $identifier
  249.      *
  250.      * @return string
  251.      */
  252.     final protected function escapeForCacheKey(string $identifier)
  253.     {
  254.         return \str_replace(
  255.             ['_''/'':''('')''@''\\''{''}'],
  256.             ['__''_S''_C''_BO''_BC''_A''_BS''_CBO''_CBC'],
  257.             $identifier
  258.         );
  259.     }
  260. }