vendor/pimcore/pimcore/lib/Cache/Core/CoreCacheHandler.php line 310

Open in your IDE?
  1. <?php
  2. /**
  3. * Pimcore
  4. *
  5. * This source file is available under two different licenses:
  6. * - GNU General Public License version 3 (GPLv3)
  7. * - Pimcore Commercial License (PCL)
  8. * Full copyright and license information is available in
  9. * LICENSE.md which is distributed with this source code.
  10. *
  11. * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12. * @license http://www.pimcore.org/license GPLv3 and PCL
  13. */
  14. namespace Pimcore\Cache\Core;
  15. use DeepCopy\TypeMatcher\TypeMatcher;
  16. use Pimcore\Event\CoreCacheEvents;
  17. use Pimcore\Model\Document\Hardlink\Wrapper\WrapperInterface;
  18. use Pimcore\Model\Element\ElementDumpStateInterface;
  19. use Pimcore\Model\Element\ElementInterface;
  20. use Pimcore\Model\Element\Service;
  21. use Pimcore\Model\Version\SetDumpStateFilter;
  22. use Psr\Log\LoggerAwareInterface;
  23. use Psr\Log\LoggerAwareTrait;
  24. use Psr\Log\LoggerInterface;
  25. use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
  26. use Symfony\Component\Cache\CacheItem;
  27. use Symfony\Contracts\EventDispatcher\Event;
  28. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  29. /**
  30. * Core pimcore cache handler with logic handling deferred save on shutdown (specialized for internal pimcore use). This
  31. * explicitely does not expose a PSR-6 API but is intended for internal use from Pimcore\Cache or directly. Actual
  32. * cache calls are forwarded to a PSR-6 cache implementation though.
  33. *
  34. * Use Pimcore\Cache static interface, do not use this handler directly
  35. *
  36. * @internal
  37. */
  38. class CoreCacheHandler implements LoggerAwareInterface
  39. {
  40. use LoggerAwareTrait;
  41. /**
  42. * @var EventDispatcherInterface
  43. */
  44. protected $dispatcher;
  45. /**
  46. * @var TagAwareAdapterInterface
  47. */
  48. protected $pool;
  49. /**
  50. * @var WriteLock
  51. */
  52. protected $writeLock;
  53. /**
  54. * Actually write/load to/from cache?
  55. *
  56. * @var bool
  57. */
  58. protected $enabled = true;
  59. /**
  60. * Is the cache handled in CLI mode?
  61. *
  62. * @var bool
  63. */
  64. protected $handleCli = false;
  65. /**
  66. * Contains the items which should be written to the cache on shutdown
  67. *
  68. * @var CacheQueueItem[]
  69. */
  70. protected $saveQueue = [];
  71. /**
  72. * Tags which were already cleared
  73. *
  74. * @var array
  75. */
  76. protected $clearedTags = [];
  77. /**
  78. * Items having one of the tags in this list are not saved
  79. *
  80. * @var array
  81. */
  82. protected $tagsIgnoredOnSave = [];
  83. /**
  84. * Items having one of the tags in this list are not cleared when calling clearTags
  85. *
  86. * @var array
  87. */
  88. protected $tagsIgnoredOnClear = [];
  89. /**
  90. * Items having tags which are in this array are cleared on shutdown. This is especially for the output-cache.
  91. *
  92. * @var array
  93. */
  94. protected $tagsClearedOnShutdown = [];
  95. /**
  96. * State variable which is set to true after the cache was cleared - prevent new items being
  97. * written to cache after a clear.
  98. *
  99. * @var bool
  100. */
  101. protected $cacheCleared = false;
  102. /**
  103. * Tags in this list are shifted to the clearTagsOnShutdown list when scheduled via clearTags. See comment on normalizeClearTags
  104. * method why this exists.
  105. *
  106. * @var array
  107. */
  108. protected $shutdownTags = ['output'];
  109. /**
  110. * If set to true items are directly written into the cache, and do not get into the queue
  111. *
  112. * @var bool
  113. */
  114. protected $forceImmediateWrite = false;
  115. /**
  116. * How many items should stored to the cache within one process
  117. *
  118. * @var int
  119. */
  120. protected $maxWriteToCacheItems = 50;
  121. /**
  122. * @var bool
  123. */
  124. protected $writeInProgress = false;
  125. /**
  126. * @var \Closure
  127. */
  128. protected $emptyCacheItemClosure;
  129. /**
  130. * @param TagAwareAdapterInterface $adapter
  131. * @param WriteLock $writeLock
  132. * @param EventDispatcherInterface $dispatcher
  133. */
  134. public function __construct(TagAwareAdapterInterface $adapter, WriteLock $writeLock, EventDispatcherInterface $dispatcher)
  135. {
  136. $this->pool = $adapter;
  137. $this->dispatcher = $dispatcher;
  138. $this->writeLock = $writeLock;
  139. }
  140. /**
  141. * @internal
  142. *
  143. * @param TagAwareAdapterInterface $pool
  144. */
  145. public function setPool(TagAwareAdapterInterface $pool): void
  146. {
  147. $this->pool = $pool;
  148. }
  149. /**
  150. * @return WriteLock
  151. */
  152. public function getWriteLock()
  153. {
  154. return $this->writeLock;
  155. }
  156. /**
  157. * @codeCoverageIgnore
  158. *
  159. * @return LoggerInterface
  160. */
  161. public function getLogger()
  162. {
  163. return $this->logger;
  164. }
  165. /**
  166. * {@inheritdoc}
  167. */
  168. public function enable()
  169. {
  170. $this->enabled = true;
  171. $this->dispatchStatusEvent();
  172. return $this;
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. public function disable()
  178. {
  179. $this->enabled = false;
  180. $this->dispatchStatusEvent();
  181. return $this;
  182. }
  183. /**
  184. * @return bool
  185. */
  186. public function isEnabled()
  187. {
  188. return $this->enabled;
  189. }
  190. protected function dispatchStatusEvent()
  191. {
  192. $this->dispatcher->dispatch(new Event(),
  193. $this->isEnabled()
  194. ? CoreCacheEvents::ENABLE
  195. : CoreCacheEvents::DISABLE
  196. );
  197. }
  198. /**
  199. * @codeCoverageIgnore
  200. *
  201. * @return bool
  202. */
  203. public function getHandleCli()
  204. {
  205. return $this->handleCli;
  206. }
  207. /**
  208. * @codeCoverageIgnore
  209. *
  210. * @param bool $handleCli
  211. *
  212. * @return $this
  213. */
  214. public function setHandleCli($handleCli)
  215. {
  216. $this->handleCli = (bool)$handleCli;
  217. return $this;
  218. }
  219. /**
  220. * @codeCoverageIgnore
  221. *
  222. * @return bool
  223. */
  224. public function getForceImmediateWrite()
  225. {
  226. return $this->forceImmediateWrite;
  227. }
  228. /**
  229. * @codeCoverageIgnore
  230. *
  231. * @param bool $forceImmediateWrite
  232. *
  233. * @return $this
  234. */
  235. public function setForceImmediateWrite($forceImmediateWrite)
  236. {
  237. $this->forceImmediateWrite = (bool)$forceImmediateWrite;
  238. return $this;
  239. }
  240. /**
  241. * @param int $maxWriteToCacheItems
  242. *
  243. * @return $this
  244. */
  245. public function setMaxWriteToCacheItems($maxWriteToCacheItems)
  246. {
  247. $this->maxWriteToCacheItems = (int)$maxWriteToCacheItems;
  248. return $this;
  249. }
  250. /**
  251. * Load data from cache (retrieves data from cache item)
  252. *
  253. * @param string $key
  254. *
  255. * @return bool|mixed
  256. */
  257. public function load($key)
  258. {
  259. if (!$this->enabled) {
  260. $this->logger->debug('Not loading object {key} from cache (deactivated)', ['key' => $key]);
  261. return false;
  262. }
  263. $item = $this->getItem($key);
  264. if ($item->isHit()) {
  265. $data = $item->get();
  266. if (is_object($data)) {
  267. $data->____pimcore_cache_item__ = $key; // TODO where is this used?
  268. }
  269. return $data;
  270. }
  271. return false;
  272. }
  273. /**
  274. * Get PSR-6 cache item
  275. *
  276. * @param string $key
  277. *
  278. * @return CacheItem
  279. */
  280. public function getItem($key)
  281. {
  282. $item = $this->pool->getItem($key);
  283. if ($item->isHit()) {
  284. $this->logger->debug('Successfully got data for key {key} from cache', ['key' => $key]);
  285. } else {
  286. $this->logger->debug('Key {key} doesn\'t exist in cache', ['key' => $key]);
  287. }
  288. return $item;
  289. }
  290. /**
  291. * Save data to cache
  292. *
  293. * @param string $key
  294. * @param mixed $data
  295. * @param array $tags
  296. * @param int|\DateInterval|null $lifetime
  297. * @param int|null $priority
  298. * @param bool $force
  299. *
  300. * @return bool
  301. */
  302. public function save($key, $data, array $tags = [], $lifetime = null, $priority = 0, $force = false)
  303. {
  304. if ($this->writeInProgress) {
  305. return false;
  306. }
  307. CacheItem::validateKey($key);
  308. if (!$this->enabled) {
  309. $this->logger->debug('Not saving object {key} to cache (deactivated)', ['key' => $key]);
  310. return false;
  311. }
  312. if ($this->isCli()) {
  313. if (!$this->handleCli && !$force) {
  314. $this->logger->debug(
  315. 'Not saving {key} to cache as process is running in CLI mode (pass force to override or set handleCli to true)',
  316. ['key' => $key]
  317. );
  318. return false;
  319. }
  320. }
  321. if ($force || $this->forceImmediateWrite) {
  322. $data = $this->prepareCacheData($data);
  323. if (null === $data) {
  324. // logging is done in prepare method if item could not be created
  325. return false;
  326. }
  327. // add cache tags to item
  328. $tags = $this->prepareCacheTags($key, $data, $tags);
  329. if (null === $tags) {
  330. return false;
  331. }
  332. return $this->storeCacheData($key, $data, $tags, $lifetime, $force);
  333. } else {
  334. $cacheQueueItem = new CacheQueueItem($key, $data, $tags, $lifetime, $priority, $force);
  335. return $this->addToSaveQueue($cacheQueueItem);
  336. }
  337. }
  338. /**
  339. * Add item to save queue, respecting maxWriteToCacheItems setting
  340. *
  341. * @param CacheQueueItem $item
  342. *
  343. * @return bool
  344. */
  345. protected function addToSaveQueue(CacheQueueItem $item)
  346. {
  347. $data = $this->prepareCacheData($item->getData());
  348. if ($data) {
  349. $this->saveQueue[$item->getKey()] = $item;
  350. if (count($this->saveQueue) > ($this->maxWriteToCacheItems*3)) {
  351. $this->cleanupQueue();
  352. }
  353. return true;
  354. }
  355. return false;
  356. }
  357. /**
  358. * @internal
  359. */
  360. public function cleanupQueue(): void
  361. {
  362. // order by priority
  363. uasort($this->saveQueue, function (CacheQueueItem $a, CacheQueueItem $b) {
  364. return $b->getPriority() <=> $a->getPriority();
  365. });
  366. // remove overrun
  367. array_splice($this->saveQueue, $this->maxWriteToCacheItems);
  368. }
  369. /**
  370. * Prepare data for cache item and handle items we don't want to save (e.g. hardlinks)
  371. *
  372. * @param mixed $data
  373. *
  374. * @return mixed
  375. */
  376. protected function prepareCacheData($data)
  377. {
  378. // do not cache hardlink-wrappers
  379. if ($data instanceof WrapperInterface) {
  380. return null;
  381. }
  382. // clean up and prepare models
  383. if ($data instanceof ElementInterface) {
  384. // check for corrupt data
  385. if (!$data->getId()) {
  386. return null;
  387. }
  388. }
  389. return $data;
  390. }
  391. /**
  392. * Create tags for cache item - do this as late as possible as this is potentially expensive (nested items, dependencies)
  393. *
  394. * @param string $key
  395. * @param mixed $data
  396. * @param array $tags
  397. *
  398. * @return null|string[]
  399. */
  400. protected function prepareCacheTags(string $key, $data, array $tags = [])
  401. {
  402. // clean up and prepare models
  403. if ($data instanceof ElementInterface) {
  404. // get tags for this element
  405. $tags = $data->getCacheTags($tags);
  406. $this->logger->debug(
  407. 'Prepared {class} {id} for data cache',
  408. [
  409. 'class' => get_class($data),
  410. 'id' => $data->getId(),
  411. 'tags' => $tags,
  412. ]
  413. );
  414. }
  415. // array_values() because the tags from \Element_Interface and some others are associative eg. array("object_123" => "object_123")
  416. $tags = array_values($tags);
  417. $tags = array_unique($tags);
  418. // check if any of our tags is in cleared tags or tags ignored on save lists
  419. foreach ($tags as $tag) {
  420. if (isset($this->clearedTags[$tag])) {
  421. $this->logger->debug('Aborted caching for key {key} because tag {tag} is in the cleared tags list', [
  422. 'key' => $key,
  423. 'tag' => $tag,
  424. ]);
  425. return null;
  426. }
  427. if (in_array($tag, $this->tagsIgnoredOnSave)) {
  428. $this->logger->debug('Aborted caching for key {key} because tag {tag} is in the ignored tags on save list', [
  429. 'key' => $key,
  430. 'tag' => $tag,
  431. 'tags' => $tags,
  432. 'tagsIgnoredOnSave' => $this->tagsIgnoredOnSave,
  433. ]);
  434. return null;
  435. }
  436. }
  437. return $tags;
  438. }
  439. /**
  440. * @param string $key
  441. * @param mixed $data
  442. * @param array $tags
  443. * @param int|\DateInterval|null $lifetime
  444. * @param bool $force
  445. *
  446. * @return bool
  447. */
  448. protected function storeCacheData(string $key, $data, array $tags = [], $lifetime = null, $force = false)
  449. {
  450. if ($this->writeInProgress) {
  451. return false;
  452. }
  453. if (!$this->enabled) {
  454. // TODO return true here as the noop (not storing anything) is basically successful?
  455. return false;
  456. }
  457. // don't put anything into the cache, when cache is cleared
  458. if ($this->cacheCleared && !$force) {
  459. return false;
  460. }
  461. $this->writeInProgress = true;
  462. if ($data instanceof ElementInterface) {
  463. // fetch a fresh copy
  464. $type = Service::getElementType($data);
  465. $data = Service::getElementById($type, $data->getId(), true);
  466. if (!$data->__isBasedOnLatestData()) {
  467. $this->logger->warning('Not saving {key} to cache as element is not based on latest data', [
  468. 'key' => $key,
  469. ]);
  470. $this->writeInProgress = false;
  471. return false;
  472. }
  473. // dump state is used to trigger a full serialized dump in __sleep eg. in Document, AbstractObject
  474. $data->setInDumpState(false);
  475. $context = [
  476. 'source' => __METHOD__,
  477. 'conversion' => false,
  478. ];
  479. $copier = Service::getDeepCopyInstance($data, $context);
  480. $copier->addFilter(new SetDumpStateFilter(false), new \DeepCopy\Matcher\PropertyMatcher(ElementDumpStateInterface::class, ElementDumpStateInterface::DUMP_STATE_PROPERTY_NAME));
  481. $copier->addTypeFilter(
  482. new \DeepCopy\TypeFilter\ReplaceFilter(
  483. function ($currentValue) {
  484. if ($currentValue instanceof CacheMarshallerInterface) {
  485. $marshalledValue = $currentValue->marshalForCache();
  486. return $marshalledValue;
  487. }
  488. return $currentValue;
  489. }
  490. ),
  491. new TypeMatcher(CacheMarshallerInterface::class)
  492. );
  493. $data = $copier->copy($data);
  494. }
  495. $item = $this->pool->getItem($key);
  496. $item->set($data);
  497. $item->expiresAfter($lifetime);
  498. $item->tag($tags);
  499. $item->tag($key);
  500. $result = $this->pool->save($item);
  501. if ($result) {
  502. $this->logger->debug('Added entry {key} to cache', ['key' => $item->getKey()]);
  503. } else {
  504. $this->logger->error(
  505. 'Failed to add entry {key} to cache. Item size was {itemSize}',
  506. [
  507. 'key' => $item->getKey(),
  508. 'itemSize' => formatBytes(strlen($item->get())),
  509. ]
  510. );
  511. }
  512. $this->writeInProgress = false;
  513. return $result;
  514. }
  515. /**
  516. * Remove a cache item
  517. *
  518. * @param string $key
  519. *
  520. * @return bool
  521. */
  522. public function remove($key)
  523. {
  524. CacheItem::validateKey($key);
  525. $this->writeLock->lock();
  526. return $this->pool->deleteItem($key);
  527. }
  528. /**
  529. * Empty the cache
  530. *
  531. * @return bool
  532. */
  533. public function clearAll()
  534. {
  535. $this->writeLock->lock();
  536. $this->logger->info('Clearing the whole cache');
  537. $result = $this->pool->clear();
  538. // immediately acquire the write lock again (force), because the lock is in the cache too
  539. $this->writeLock->lock(true);
  540. // set state to cache cleared - prevents new items being written to cache
  541. $this->cacheCleared = true;
  542. return $result;
  543. }
  544. /**
  545. * @param string $tag
  546. *
  547. * @return bool
  548. */
  549. public function clearTag($tag)
  550. {
  551. return $this->clearTags([$tag]);
  552. }
  553. /**
  554. * @param string[] $tags
  555. *
  556. * @return bool
  557. */
  558. public function clearTags(array $tags): bool
  559. {
  560. $this->writeLock->lock();
  561. $originalTags = $tags;
  562. $this->logger->debug(
  563. 'Clearing cache tags',
  564. ['tags' => $tags]
  565. );
  566. $tags = $this->normalizeClearTags($tags);
  567. if (count($tags) > 0) {
  568. $result = $this->pool->invalidateTags($tags);
  569. $this->addClearedTags($tags);
  570. return $result;
  571. }
  572. $this->logger->warning(
  573. 'Could not clear tags as tag list is empty after normalization',
  574. [
  575. 'tags' => $tags,
  576. 'originalTags' => $originalTags,
  577. ]
  578. );
  579. return false;
  580. }
  581. /**
  582. * Clears all tags stored in tagsClearedOnShutdown, this function is executed during Pimcore shutdown
  583. *
  584. * @return bool
  585. */
  586. public function clearTagsOnShutdown()
  587. {
  588. if (empty($this->tagsClearedOnShutdown)) {
  589. return true;
  590. }
  591. $this->logger->debug('Clearing shutdown cache tags', ['tags' => $this->tagsClearedOnShutdown]);
  592. $result = $this->pool->invalidateTags($this->tagsClearedOnShutdown);
  593. $this->addClearedTags($this->tagsClearedOnShutdown);
  594. $this->tagsClearedOnShutdown = [];
  595. return $result;
  596. }
  597. /**
  598. * Normalize (unique) clear tags and shift special tags to shutdown (e.g. output)
  599. *
  600. * @param array $tags
  601. *
  602. * @return array
  603. */
  604. protected function normalizeClearTags(array $tags)
  605. {
  606. $blacklist = $this->tagsIgnoredOnClear;
  607. // Shutdown tags are special tags being shifted to shutdown when scheduled to clear via clearTags. Explanation for
  608. // the "output" tag:
  609. // check for the tag output, because items with this tags are only cleared after the process is finished
  610. // the reason is that eg. long running importers will clean the output-cache on every save/update, that's not necessary,
  611. // only cleaning the output-cache on shutdown should be enough
  612. foreach ($this->shutdownTags as $shutdownTag) {
  613. if (in_array($shutdownTag, $tags)) {
  614. $this->addTagClearedOnShutdown($shutdownTag);
  615. $blacklist[] = $shutdownTag;
  616. }
  617. }
  618. // ensure that every tag is unique
  619. $tags = array_unique($tags);
  620. // don't clear tags in ignore array
  621. $tags = array_filter($tags, function ($tag) use ($blacklist) {
  622. return !in_array($tag, $blacklist);
  623. });
  624. return $tags;
  625. }
  626. /**
  627. * Add tag to list of cleared tags (internal use only)
  628. *
  629. * @param string|array $tags
  630. *
  631. * @return $this
  632. */
  633. protected function addClearedTags($tags)
  634. {
  635. if (!is_array($tags)) {
  636. $tags = [$tags];
  637. }
  638. foreach ($tags as $tag) {
  639. $this->clearedTags[$tag] = true;
  640. }
  641. return $this;
  642. }
  643. /**
  644. * Adds a tag to the shutdown queue, see clearTagsOnShutdown
  645. *
  646. * @internal
  647. *
  648. * @param string $tag
  649. *
  650. * @return $this
  651. */
  652. public function addTagClearedOnShutdown($tag)
  653. {
  654. $this->writeLock->lock();
  655. $this->tagsClearedOnShutdown[] = $tag;
  656. $this->tagsClearedOnShutdown = array_unique($this->tagsClearedOnShutdown);
  657. return $this;
  658. }
  659. /**
  660. * @internal
  661. *
  662. * @param string $tag
  663. *
  664. * @return $this
  665. */
  666. public function addTagIgnoredOnSave($tag)
  667. {
  668. $this->tagsIgnoredOnSave[] = $tag;
  669. $this->tagsIgnoredOnSave = array_unique($this->tagsIgnoredOnSave);
  670. return $this;
  671. }
  672. /**
  673. * @internal
  674. *
  675. * @param string $tag
  676. *
  677. * @return $this
  678. */
  679. public function removeTagIgnoredOnSave($tag)
  680. {
  681. $this->tagsIgnoredOnSave = array_filter($this->tagsIgnoredOnSave, function ($t) use ($tag) {
  682. return $t !== $tag;
  683. });
  684. return $this;
  685. }
  686. /**
  687. * @internal
  688. *
  689. * @param string $tag
  690. *
  691. * @return $this
  692. */
  693. public function addTagIgnoredOnClear($tag)
  694. {
  695. $this->tagsIgnoredOnClear[] = $tag;
  696. $this->tagsIgnoredOnClear = array_unique($this->tagsIgnoredOnClear);
  697. return $this;
  698. }
  699. /**
  700. * @internal
  701. *
  702. * @param string $tag
  703. *
  704. * @return $this
  705. */
  706. public function removeTagIgnoredOnClear($tag)
  707. {
  708. $this->tagsIgnoredOnClear = array_filter($this->tagsIgnoredOnClear, function ($t) use ($tag) {
  709. return $t !== $tag;
  710. });
  711. return $this;
  712. }
  713. /**
  714. * Writes save queue to the cache
  715. *
  716. * @internal
  717. *
  718. * @return bool
  719. */
  720. public function writeSaveQueue()
  721. {
  722. $totalResult = true;
  723. if ($this->writeLock->hasLock()) {
  724. if (count($this->saveQueue) > 0) {
  725. $this->logger->debug(
  726. 'Not writing save queue as there\'s an active write lock. Save queue contains {saveQueueCount} items.',
  727. ['saveQueueCount' => count($this->saveQueue)]
  728. );
  729. }
  730. return false;
  731. }
  732. $this->cleanupQueue();
  733. $processedKeys = [];
  734. foreach ($this->saveQueue as $queueItem) {
  735. $key = $queueItem->getKey();
  736. // check if key was already processed and don't save it again
  737. if (in_array($key, $processedKeys)) {
  738. $this->logger->warning('Not writing item as key {key} was already processed', ['key' => $key]);
  739. continue;
  740. }
  741. $tags = $this->prepareCacheTags($queueItem->getKey(), $queueItem->getData(), $queueItem->getTags());
  742. if (null === $tags) {
  743. $result = false;
  744. // item shouldn't go to the cache (either because it's tags are ignored or were cleared within this process) -> see $this->prepareCacheTags();
  745. } else {
  746. $result = $this->storeCacheData($queueItem->getKey(), $queueItem->getData(), $tags, $queueItem->getLifetime(), $queueItem->isForce());
  747. }
  748. $processedKeys[] = $key;
  749. $totalResult = $totalResult && $result;
  750. }
  751. // reset
  752. $this->saveQueue = [];
  753. return $totalResult;
  754. }
  755. /**
  756. * Shut down pimcore - write cache entries and clean up
  757. *
  758. * @internal
  759. *
  760. * @param bool $forceWrite
  761. *
  762. * @return $this
  763. */
  764. public function shutdown($forceWrite = false)
  765. {
  766. // clear tags scheduled for the shutdown
  767. $this->clearTagsOnShutdown();
  768. $doWrite = true;
  769. // writes make only sense for HTTP(S)
  770. // CLI are normally longer running scripts that tend to produce race conditions
  771. // so CLI scripts are not writing to the cache at all
  772. if ($this->isCli()) {
  773. if (!($this->handleCli || $forceWrite)) {
  774. $doWrite = false;
  775. $queueCount = count($this->saveQueue);
  776. if ($queueCount > 0) {
  777. $this->logger->debug(
  778. 'Not writing save queue to cache as process is running in CLI mode. Save queue contains {saveQueueCount} items.',
  779. ['saveQueueCount' => count($this->saveQueue)]
  780. );
  781. }
  782. }
  783. }
  784. // write collected items to cache backend
  785. if ($doWrite) {
  786. $this->writeSaveQueue();
  787. }
  788. // remove the write lock
  789. $this->writeLock->removeLock();
  790. return $this;
  791. }
  792. /**
  793. * @codeCoverageIgnore
  794. *
  795. * @return bool
  796. */
  797. protected function isCli()
  798. {
  799. return php_sapi_name() === 'cli';
  800. }
  801. }