vendor/se7enxweb/legacy-bridge/bundle/Cache/PersistenceCachePurger.php line 152

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\Bundle\EzPublishLegacyBundle\Cache;
  7. use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
  8. use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
  9. use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandlerInterface;
  10. use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
  11. use Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface;
  12. /**
  13. * Class PersistenceCachePurger.
  14. */
  15. class PersistenceCachePurger implements CacheClearerInterface
  16. {
  17. private const LOCATION_IDENTIFIER = 'location';
  18. private const URL_ALIAS_LOCATION_IDENTIFIER = 'url_alias_location';
  19. private const URL_ALIAS_LOCATION_PATH_IDENTIFIER = 'url_alias_location_path';
  20. private const CONTENT_IDENTIFIER = 'content';
  21. private const CONTENT_VERSION_INFO_IDENTIFIER = 'content_version_info';
  22. private const CONTENT_VERSION_LIST_IDENTIFIER = 'content_version_list';
  23. private const CONTENT_VERSION_IDENTIFIER = 'content_version';
  24. private const TYPE_MAP_IDENTIFIER = 'type_map';
  25. private const TYPE_IDENTIFIER = 'type';
  26. private const TYPE_GROUP_IDENTIFIER = 'type_group';
  27. private const SECTION_IDENTIFIER = 'section';
  28. private const LANGUAGE_IDENTIFIER = 'language';
  29. private const USER_IDENTIFIER = 'user';
  30. use Switchable;
  31. /**
  32. * @var \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface
  33. */
  34. protected $cache;
  35. /**
  36. * @var \eZ\Publish\SPI\Persistence\Content\Location\Handler
  37. */
  38. protected $locationHandler;
  39. /**
  40. * @var \Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface
  41. */
  42. protected $cacheIdentifierGenerator;
  43. /**
  44. * Avoid clearing sub elements if all cache is already cleared, avoids redundant calls to cache.
  45. *
  46. * @var bool
  47. */
  48. protected $allCleared = false;
  49. /**
  50. * @var bool
  51. */
  52. private $clearAllSPICacheOnSymfonyClearCache;
  53. /**
  54. * @var bool
  55. */
  56. private $clearAllSPICacheFromLegacy;
  57. /**
  58. * Setups current handler with everything needed.
  59. *
  60. * @param \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface $cache
  61. * @param \eZ\Publish\SPI\Persistence\Content\Location\Handler $locationHandler (using SPI cache instance so calls are cached)
  62. * @param \Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface $cacheIdentifierGenerator
  63. */
  64. public function __construct(
  65. TagAwareAdapterInterface $cache,
  66. LocationHandlerInterface $locationHandler,
  67. CacheIdentifierGeneratorInterface $cacheIdentifierGenerator,
  68. bool $clearAllSPICacheOnSymfonyClearCache = true,
  69. bool $clearAllSPICacheFromLegacy = true
  70. ) {
  71. $this->cache = $cache;
  72. $this->locationHandler = $locationHandler;
  73. $this->cacheIdentifierGenerator = $cacheIdentifierGenerator;
  74. $this->clearAllSPICacheOnSymfonyClearCache = $clearAllSPICacheOnSymfonyClearCache;
  75. $this->clearAllSPICacheFromLegacy = $clearAllSPICacheFromLegacy;
  76. }
  77. /**
  78. * Clear all persistence cache if that is allowed by config.
  79. *
  80. * In legacy kernel used when user presses clear all cache button in admin interface.
  81. */
  82. public function all()
  83. {
  84. if ($this->clearAllSPICacheFromLegacy) {
  85. $this->flushSPICache();
  86. }
  87. }
  88. /**
  89. * Clear all persistence cache.
  90. *
  91. * Sets a internal flag 'allCleared' to avoid clearing cache several times
  92. */
  93. private function flushSPICache()
  94. {
  95. if ($this->isSwitchedOff()) {
  96. return;
  97. }
  98. $this->cache->clear();
  99. $this->allCleared = true;
  100. }
  101. /**
  102. * Returns true if all cache has been cleared already.
  103. *
  104. * Returns the internal flag 'allCleared' that avoids clearing cache several times
  105. *
  106. * @return bool
  107. */
  108. public function isAllCleared()
  109. {
  110. return $this->allCleared;
  111. }
  112. /**
  113. * Reset 'allCleared' flag.
  114. *
  115. * Resets the internal flag 'allCleared' that avoids clearing cache several times
  116. */
  117. public function resetAllCleared()
  118. {
  119. $this->allCleared = false;
  120. }
  121. /**
  122. * Clear all content persistence cache, or by locationIds (legacy content/cache mechanism is location based).
  123. *
  124. * In legacy kernel used when any kind of event triggers cache clearing for content.
  125. * If amount of accepted nodes goes over threshold, or in case where all content cache is cleared from admin
  126. * interface, argument will be empty.
  127. *
  128. * @param int|int[]|null $locationIds Ids of location we need to purge content cache for. Purges all content cache if null
  129. * @param int[]|null $contentIds Ids of content we need to purge
  130. *
  131. * @return array|int|\int[]|null
  132. *
  133. * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType On invalid $id type
  134. */
  135. public function content($locationIds = null, array $contentIds = null)
  136. {
  137. if ($this->allCleared === true || $this->isSwitchedOff()) {
  138. return $locationIds;
  139. }
  140. if ($locationIds === null) {
  141. $this->cache->clear();
  142. return $locationIds;
  143. }
  144. if (!\is_array($locationIds)) {
  145. $locationIds = [$locationIds];
  146. }
  147. if ($contentIds === null) {
  148. $contentIds = [];
  149. }
  150. $tags = [];
  151. foreach ($locationIds as $id) {
  152. if (!is_scalar($id)) {
  153. throw new InvalidArgumentType('$locationIds', 'int[]|null', $id);
  154. }
  155. $tags[] = $this->cacheIdentifierGenerator->generateTag(self::LOCATION_IDENTIFIER, [$id]);
  156. $tags[] = $this->cacheIdentifierGenerator->generateTag(self::URL_ALIAS_LOCATION_IDENTIFIER, [$id]);
  157. $tags[] = $this->cacheIdentifierGenerator->generateTag(self::URL_ALIAS_LOCATION_PATH_IDENTIFIER, [$id]);
  158. }
  159. // if caller did not provide affected content id's, then try to load location to get it
  160. if (empty($contentIds)) {
  161. $contentIds = [];
  162. foreach ($this->locationHandler->loadList($locationIds) as $location) {
  163. $contentIds[] = $location->contentId;
  164. }
  165. }
  166. foreach ($contentIds as $id) {
  167. if (!is_scalar($id)) {
  168. throw new InvalidArgumentType('$contentIds', 'int[]|null', $id);
  169. }
  170. $tags[] = $this->cacheIdentifierGenerator->generateTag(self::CONTENT_IDENTIFIER, [$id]);
  171. }
  172. $this->cache->invalidateTags($tags);
  173. return $locationIds;
  174. }
  175. /**
  176. * Clears persistence cache for given $contentId and $versionNo.
  177. *
  178. * In legacy kernel used when storing a draft.
  179. *
  180. * @param int $contentId
  181. * @param int $versionNo
  182. */
  183. public function contentVersion($contentId, $versionNo)
  184. {
  185. if ($this->allCleared === true || $this->isSwitchedOff()) {
  186. return;
  187. }
  188. // Some extra keys/tags here to make sure we cover differences between misc 7.x kernel versions
  189. $this->cache->deleteItems([
  190. $this->cacheIdentifierGenerator->generateKey(self::CONTENT_VERSION_INFO_IDENTIFIER, [$contentId], true),
  191. $this->cacheIdentifierGenerator->generateKey(self::CONTENT_VERSION_LIST_IDENTIFIER, [$contentId], true),
  192. ]);
  193. $this->cache->invalidateTags([
  194. $this->cacheIdentifierGenerator->generateTag(self::CONTENT_VERSION_LIST_IDENTIFIER, [$contentId]),
  195. $this->cacheIdentifierGenerator->generateTag(self::CONTENT_VERSION_IDENTIFIER, [$contentId, $versionNo]),
  196. ]);
  197. }
  198. /**
  199. * Clear all contentType persistence cache, or by id.
  200. *
  201. * In legacy kernel used when editing content type, in this case we get id.
  202. * Also used when clearing content type meta data cache in admin cache interface (no id).
  203. *
  204. * @param int|null $id Purges all contentType cache if null
  205. *
  206. * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType On invalid $id type
  207. */
  208. public function contentType($id = null)
  209. {
  210. if ($this->allCleared === true || $this->isSwitchedOff()) {
  211. return;
  212. }
  213. if ($id === null) {
  214. if ($this->clearAllSPICacheFromLegacy) {
  215. $this->cache->invalidateTags([
  216. $this->cacheIdentifierGenerator->generateTag(self::TYPE_MAP_IDENTIFIER),
  217. ]);
  218. }
  219. } elseif (is_scalar($id)) {
  220. $this->cache->invalidateTags([
  221. $this->cacheIdentifierGenerator->generateTag(self::TYPE_IDENTIFIER, [$id]),
  222. ]);
  223. } else {
  224. throw new InvalidArgumentType('$id', 'int|null', $id);
  225. }
  226. }
  227. /**
  228. * Clear contentTypeGroup persistence cache by id.
  229. *
  230. * In legacy kernel used when editing/removing content type group, so there is always an id.
  231. *
  232. * @param int $id
  233. *
  234. * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType On invalid $id type
  235. */
  236. public function contentTypeGroup($id)
  237. {
  238. if ($this->allCleared === true || $this->isSwitchedOff()) {
  239. return;
  240. }
  241. if (is_scalar($id)) {
  242. // @todo should also clear content type cache for items themselves in case of link/unlink changes, kernel should have a "type-all" tag for this
  243. $this->cache->invalidateTags([
  244. $this->cacheIdentifierGenerator->generateTag(self::TYPE_GROUP_IDENTIFIER, [$id]),
  245. $this->cacheIdentifierGenerator->generateTag(self::TYPE_MAP_IDENTIFIER),
  246. ]);
  247. } else {
  248. throw new InvalidArgumentType('$id', 'int|null', $id);
  249. }
  250. }
  251. /**
  252. * Clear section persistence cache by id.
  253. *
  254. * In legacy kernel used when editing section, so there is always an id.
  255. *
  256. * @param int $id
  257. *
  258. * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType On invalid $id type
  259. */
  260. public function section($id)
  261. {
  262. if ($this->allCleared === true || $this->isSwitchedOff()) {
  263. return;
  264. }
  265. if (is_scalar($id)) {
  266. $this->cache->invalidateTags([
  267. $this->cacheIdentifierGenerator->generateTag(self::SECTION_IDENTIFIER, [$id]),
  268. ]);
  269. } else {
  270. throw new InvalidArgumentType('$id', 'int|null', $id);
  271. }
  272. }
  273. /**
  274. * Clear language persistence cache by id.
  275. *
  276. * In legacy kernel used when editing language, so there is always an id.
  277. *
  278. * @param array|int $ids
  279. */
  280. public function languages($ids)
  281. {
  282. if ($this->allCleared === true || $this->isSwitchedOff()) {
  283. return;
  284. }
  285. $ids = (array)$ids;
  286. $tags = [];
  287. foreach ($ids as $id) {
  288. $tags[] = $this->cacheIdentifierGenerator->generateTag(self::LANGUAGE_IDENTIFIER, [$id]);
  289. }
  290. $this->cache->invalidateTags($tags);
  291. }
  292. /**
  293. * Clear object state assignment persistence cache by content id.
  294. *
  295. * In legacy kernel used when assigning statet to an content.
  296. *
  297. * @param int $contentId
  298. */
  299. public function stateAssign($contentId)
  300. {
  301. if ($this->allCleared === true || $this->isSwitchedOff()) {
  302. return;
  303. }
  304. $this->cache->invalidateTags([
  305. $this->cacheIdentifierGenerator->generateTag(self::CONTENT_IDENTIFIER, [$contentId]),
  306. ]);
  307. }
  308. /**
  309. * Clear meta info on users in Persistence.
  310. *
  311. * In legacy kernel used when clearing meta info cache on users in eZUser, never with id.
  312. *
  313. * @param int|null $id Purges all users cache if null
  314. *
  315. * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType On invalid $id type
  316. */
  317. public function user($id = null)
  318. {
  319. if ($this->allCleared === true || $this->isSwitchedOff()) {
  320. return;
  321. }
  322. if ($id === null) {
  323. // @todo From the looks of usage in legacy we only need to clear meta data here, and there is no such thing
  324. // in persistence so we ignore it for now.
  325. //$this->cache->clear();
  326. } elseif (is_scalar($id)) {
  327. $this->cache->invalidateTags([
  328. $this->cacheIdentifierGenerator->generateTag(self::USER_IDENTIFIER, [$id]),
  329. ]);
  330. } else {
  331. throw new InvalidArgumentType('$id', 'int|null', $id);
  332. }
  333. }
  334. /**
  335. * Clears any caches necessary.
  336. *
  337. * Used by symfony cache clear command.
  338. *
  339. * @param string $cacheDir the cache directory
  340. */
  341. public function clear($cacheDir)
  342. {
  343. if ($this->clearAllSPICacheOnSymfonyClearCache) {
  344. $this->flushSPICache();
  345. }
  346. }
  347. }