vendor/ezsystems/ezpublish-kernel/eZ/Publish/Core/Persistence/Cache/LocationHandler.php line 65

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\SPI\Persistence\Content\Location\Handler as LocationHandlerInterface;
  8. use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct;
  9. use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
  10. use eZ\Publish\SPI\Persistence\Content\Location;
  11. /**
  12.  * @see \eZ\Publish\SPI\Persistence\Content\Location\Handler
  13.  */
  14. class LocationHandler extends AbstractInMemoryPersistenceHandler implements LocationHandlerInterface
  15. {
  16.     /** @var callable */
  17.     private $getLocationTags;
  18.     /** @var callable */
  19.     private $getLocationKeys;
  20.     protected function init(): void
  21.     {
  22.         $this->getLocationTags = static function (Location $location) {
  23.             $tags = [
  24.                 'content-' $location->contentId,
  25.                 'location-' $location->id,
  26.             ];
  27.             foreach (\explode('/', \trim($location->pathString'/')) as $pathId) {
  28.                 $tags[] = 'location-path-' $pathId;
  29.             }
  30.             return $tags;
  31.         };
  32.         $this->getLocationKeys = function (Location $location$keySuffix '-1') {
  33.             return [
  34.                 'ez-location-' $location->id $keySuffix,
  35.                 'ez-location-remoteid-' $this->escapeForCacheKey($location->remoteId) . $keySuffix,
  36.             ];
  37.         };
  38.     }
  39.     /**
  40.      * {@inheritdoc}
  41.      */
  42.     public function load($locationId, array $translations nullbool $useAlwaysAvailable true)
  43.     {
  44.         $keySuffix '-' $this->getCacheTranslationKey($translations$useAlwaysAvailable);
  45.         $getLocationKeysFn $this->getLocationKeys;
  46.         return $this->getCacheValue(
  47.             (int) $locationId,
  48.             'ez-location-',
  49.             function ($id) use ($translations$useAlwaysAvailable) {
  50.                 return $this->persistenceHandler->locationHandler()->load($id$translations$useAlwaysAvailable);
  51.             },
  52.             $this->getLocationTags,
  53.             static function (Location $location) use ($keySuffix$getLocationKeysFn) {
  54.                 return $getLocationKeysFn($location$keySuffix);
  55.             },
  56.             $keySuffix,
  57.             ['location' => $locationId'translations' => $translations'alwaysAvailable' => $useAlwaysAvailable]
  58.         );
  59.     }
  60.     public function loadList(array $locationIds, array $translations nullbool $useAlwaysAvailable true): iterable
  61.     {
  62.         $keySuffix '-' $this->getCacheTranslationKey($translations$useAlwaysAvailable);
  63.         $getLocationKeysFn $this->getLocationKeys;
  64.         return $this->getMultipleCacheValues(
  65.             $locationIds,
  66.             'ez-location-',
  67.             function (array $ids) use ($translations$useAlwaysAvailable) {
  68.                 return $this->persistenceHandler->locationHandler()->loadList($ids$translations$useAlwaysAvailable);
  69.             },
  70.             $this->getLocationTags,
  71.             static function (Location $location) use ($keySuffix$getLocationKeysFn) {
  72.                 return $getLocationKeysFn($location$keySuffix);
  73.             },
  74.             $keySuffix,
  75.             ['location' => $locationIds'translations' => $translations'alwaysAvailable' => $useAlwaysAvailable]
  76.         );
  77.     }
  78.     /**
  79.      * {@inheritdoc}
  80.      */
  81.     public function loadSubtreeIds($locationId)
  82.     {
  83.         $cacheItem $this->cache->getItem("ez-location-subtree-${locationId}");
  84.         if ($cacheItem->isHit()) {
  85.             $this->logger->logCacheHit(['location' => $locationId]);
  86.             return $cacheItem->get();
  87.         }
  88.         $this->logger->logCacheMiss(['location' => $locationId]);
  89.         $locationIds $this->persistenceHandler->locationHandler()->loadSubtreeIds($locationId);
  90.         $cacheItem->set($locationIds);
  91.         $cacheTags = ['location-' $locationId'location-path-' $locationId];
  92.         foreach ($locationIds as $id) {
  93.             $cacheTags[] = 'location-' $id;
  94.             $cacheTags[] = 'location-path-' $id;
  95.         }
  96.         $cacheItem->tag($cacheTags);
  97.         $this->cache->save($cacheItem);
  98.         return $locationIds;
  99.     }
  100.     /**
  101.      * {@inheritdoc}
  102.      */
  103.     public function loadLocationsByContent($contentId$rootLocationId null)
  104.     {
  105.         if ($rootLocationId) {
  106.             $cacheItem $this->cache->getItem("ez-content-locations-${contentId}-root-${rootLocationId}");
  107.             $cacheTags = ['content-' $contentId'location-' $rootLocationId'location-path-' $rootLocationId];
  108.         } else {
  109.             $cacheItem $this->cache->getItem("ez-content-locations-${contentId}");
  110.             $cacheTags = ['content-' $contentId];
  111.         }
  112.         if ($cacheItem->isHit()) {
  113.             $this->logger->logCacheHit(['content' => $contentId'root' => $rootLocationId]);
  114.             return $cacheItem->get();
  115.         }
  116.         $this->logger->logCacheMiss(['content' => $contentId'root' => $rootLocationId]);
  117.         $locations $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentId$rootLocationId);
  118.         $cacheItem->set($locations);
  119.         foreach ($locations as $location) {
  120.             $cacheTags $this->getCacheTags($location$cacheTags);
  121.         }
  122.         $cacheItem->tag($cacheTags);
  123.         $this->cache->save($cacheItem);
  124.         return $locations;
  125.     }
  126.     /**
  127.      * {@inheritdoc}
  128.      */
  129.     public function loadLocationsByTrashContent(int $contentId, ?int $rootLocationId null): array
  130.     {
  131.         $this->logger->logCall(__METHOD__, ['content' => $contentId'root' => $rootLocationId]);
  132.         return $this->persistenceHandler->locationHandler()->loadLocationsByTrashContent($contentId$rootLocationId);
  133.     }
  134.     /**
  135.      * {@inheritdoc}
  136.      */
  137.     public function loadParentLocationsForDraftContent($contentId)
  138.     {
  139.         $cacheItem $this->cache->getItem("ez-content-locations-${contentId}-parentForDraft");
  140.         if ($cacheItem->isHit()) {
  141.             $this->logger->logCacheHit(['content' => $contentId]);
  142.             return $cacheItem->get();
  143.         }
  144.         $this->logger->logCacheMiss(['content' => $contentId]);
  145.         $locations $this->persistenceHandler->locationHandler()->loadParentLocationsForDraftContent($contentId);
  146.         $cacheItem->set($locations);
  147.         $cacheTags = ['content-' $contentId];
  148.         foreach ($locations as $location) {
  149.             $cacheTags $this->getCacheTags($location$cacheTags);
  150.         }
  151.         $cacheItem->tag($cacheTags);
  152.         $this->cache->save($cacheItem);
  153.         return $locations;
  154.     }
  155.     /**
  156.      * {@inheritdoc}
  157.      */
  158.     public function loadByRemoteId($remoteId, array $translations nullbool $useAlwaysAvailable true)
  159.     {
  160.         $keySuffix '-' $this->getCacheTranslationKey($translations$useAlwaysAvailable);
  161.         $getLocationKeysFn $this->getLocationKeys;
  162.         return $this->getCacheValue(
  163.             $this->escapeForCacheKey($remoteId),
  164.             'ez-location-remoteid-',
  165.             function () use ($remoteId$translations$useAlwaysAvailable) {
  166.                 return $this->persistenceHandler->locationHandler()->loadByRemoteId($remoteId$translations$useAlwaysAvailable);
  167.             },
  168.             $this->getLocationTags,
  169.             static function (Location $location) use ($keySuffix$getLocationKeysFn) {
  170.                 return $getLocationKeysFn($location$keySuffix);
  171.             },
  172.             $keySuffix,
  173.             ['location' => $remoteId'translations' => $translations'alwaysAvailable' => $useAlwaysAvailable]
  174.         );
  175.     }
  176.     /**
  177.      * {@inheritdoc}
  178.      */
  179.     public function copySubtree($sourceId$destinationParentId$newOwnerId null)
  180.     {
  181.         $this->logger->logCall(__METHOD__, [
  182.             'source' => $sourceId,
  183.             'destination' => $destinationParentId,
  184.             'newOwner' => $newOwnerId,
  185.         ]);
  186.         return $this->persistenceHandler->locationHandler()->copySubtree($sourceId$destinationParentId$newOwnerId);
  187.     }
  188.     /**
  189.      * {@inheritdoc}
  190.      */
  191.     public function move($sourceId$destinationParentId)
  192.     {
  193.         $this->logger->logCall(__METHOD__, ['source' => $sourceId'destination' => $destinationParentId]);
  194.         $return $this->persistenceHandler->locationHandler()->move($sourceId$destinationParentId);
  195.         $this->cache->invalidateTags(['location-path-' $sourceId]);
  196.         return $return;
  197.     }
  198.     /**
  199.      * {@inheritdoc}
  200.      */
  201.     public function markSubtreeModified($locationId$timestamp null)
  202.     {
  203.         $this->logger->logCall(__METHOD__, ['location' => $locationId'time' => $timestamp]);
  204.         $this->persistenceHandler->locationHandler()->markSubtreeModified($locationId$timestamp);
  205.     }
  206.     /**
  207.      * {@inheritdoc}
  208.      */
  209.     public function hide($locationId)
  210.     {
  211.         $this->logger->logCall(__METHOD__, ['location' => $locationId]);
  212.         $return $this->persistenceHandler->locationHandler()->hide($locationId);
  213.         $this->cache->invalidateTags(['location-path-' $locationId]);
  214.         return $return;
  215.     }
  216.     /**
  217.      * {@inheritdoc}
  218.      */
  219.     public function unHide($locationId)
  220.     {
  221.         $this->logger->logCall(__METHOD__, ['location' => $locationId]);
  222.         $return $this->persistenceHandler->locationHandler()->unHide($locationId);
  223.         $this->cache->invalidateTags(['location-path-' $locationId]);
  224.         return $return;
  225.     }
  226.     /**
  227.      * Sets a location + all children to invisible.
  228.      *
  229.      * @param int $id Location ID
  230.      */
  231.     public function setInvisible(int $id): void
  232.     {
  233.         $this->logger->logCall(__METHOD__, ['location' => $id]);
  234.         $this->persistenceHandler->locationHandler()->setInvisible($id);
  235.         $this->cache->invalidateTags(['location-path-' $id]);
  236.     }
  237.     /**
  238.      * Sets a location + all children to visible.
  239.      *
  240.      * @param int $id Location ID
  241.      */
  242.     public function setVisible(int $id): void
  243.     {
  244.         $this->logger->logCall(__METHOD__, ['location' => $id]);
  245.         $this->persistenceHandler->locationHandler()->setVisible($id);
  246.         $this->cache->invalidateTags(['location-path-' $id]);
  247.     }
  248.     /**
  249.      * {@inheritdoc}
  250.      */
  251.     public function swap($locationId1$locationId2)
  252.     {
  253.         $this->logger->logCall(__METHOD__, ['location1' => $locationId1'location2' => $locationId2]);
  254.         $locationHandler $this->persistenceHandler->locationHandler();
  255.         $return $locationHandler->swap($locationId1$locationId2);
  256.         $this->cache->invalidateTags(
  257.             [
  258.                 'location-' $locationId1,
  259.                 'location-' $locationId2,
  260.             ]
  261.         );
  262.         return $return;
  263.     }
  264.     /**
  265.      * {@inheritdoc}
  266.      */
  267.     public function update(UpdateStruct $struct$locationId)
  268.     {
  269.         $this->logger->logCall(__METHOD__, ['location' => $locationId'struct' => $struct]);
  270.         $this->persistenceHandler->locationHandler()->update($struct$locationId);
  271.         $this->cache->invalidateTags(['location-' $locationId]);
  272.     }
  273.     /**
  274.      * {@inheritdoc}
  275.      */
  276.     public function create(CreateStruct $locationStruct)
  277.     {
  278.         $this->logger->logCall(__METHOD__, ['struct' => $locationStruct]);
  279.         $location $this->persistenceHandler->locationHandler()->create($locationStruct);
  280.         // need to clear loadLocationsByContent and similar collections involving locations data
  281.         // also need to clear content info on main location changes
  282.         $this->cache->invalidateTags(['content-' $locationStruct->contentId'role-assignment-group-list-' $locationStruct->contentId]);
  283.         return $location;
  284.     }
  285.     /**
  286.      * {@inheritdoc}
  287.      */
  288.     public function removeSubtree($locationId)
  289.     {
  290.         $this->logger->logCall(__METHOD__, ['location' => $locationId]);
  291.         $return $this->persistenceHandler->locationHandler()->removeSubtree($locationId);
  292.         $this->cache->invalidateTags(['location-path-' $locationId]);
  293.         return $return;
  294.     }
  295.     /**
  296.      * {@inheritdoc}
  297.      */
  298.     public function setSectionForSubtree($locationId$sectionId)
  299.     {
  300.         $this->logger->logCall(__METHOD__, ['location' => $locationId'section' => $sectionId]);
  301.         $this->persistenceHandler->locationHandler()->setSectionForSubtree($locationId$sectionId);
  302.         $this->cache->invalidateTags(['location-path-' $locationId]);
  303.     }
  304.     /**
  305.      * {@inheritdoc}
  306.      */
  307.     public function changeMainLocation($contentId$locationId)
  308.     {
  309.         $this->logger->logCall(__METHOD__, ['location' => $locationId'content' => $contentId]);
  310.         $this->persistenceHandler->locationHandler()->changeMainLocation($contentId$locationId);
  311.         $this->cache->invalidateTags(['content-' $contentId]);
  312.     }
  313.     /**
  314.      * Get the total number of all existing Locations. Can be combined with loadAllLocations.
  315.      *
  316.      * @return int
  317.      */
  318.     public function countAllLocations()
  319.     {
  320.         $this->logger->logCall(__METHOD__);
  321.         return $this->persistenceHandler->locationHandler()->countAllLocations();
  322.     }
  323.     /**
  324.      * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
  325.      *
  326.      * @param int $offset
  327.      * @param int $limit
  328.      *
  329.      * @return \eZ\Publish\SPI\Persistence\Content\Location[]
  330.      */
  331.     public function loadAllLocations($offset$limit)
  332.     {
  333.         $this->logger->logCall(__METHOD__, ['offset' => $offset'limit' => $limit]);
  334.         return $this->persistenceHandler->locationHandler()->loadAllLocations($offset$limit);
  335.     }
  336.     /**
  337.      * Return relevant content and location tags so cache can be purged reliably.
  338.      *
  339.      * @param \eZ\Publish\SPI\Persistence\Content\Location $location
  340.      * @param array $tags Optional, can be used to specify additional tags.
  341.      *
  342.      * @return array
  343.      */
  344.     private function getCacheTags(Location $location$tags = [])
  345.     {
  346.         $tags[] = 'content-' $location->contentId;
  347.         $tags[] = 'location-' $location->id;
  348.         foreach (explode('/'trim($location->pathString'/')) as $pathId) {
  349.             $tags[] = 'location-path-' $pathId;
  350.         }
  351.         return $tags;
  352.     }
  353.     private function getCacheTranslationKey(array $translations nullbool $useAlwaysAvailable true): string
  354.     {
  355.         if (empty($translations)) {
  356.             return (int)$useAlwaysAvailable;
  357.         }
  358.         // Sort array as we don't care about order in location handler usage & want to optimize for cache hits.
  359.         sort($translations);
  360.         return implode('|'$translations) . '|' . (int)$useAlwaysAvailable;
  361.     }
  362. }