Skip to content

Commit 52cdc91

Browse files
authored
Merge pull request #3631 from Sebobo/feature/flush-tags
FEATURE: Pass tags to be flushed to content cache backend
2 parents 18e16b8 + d5f8563 commit 52cdc91

File tree

5 files changed

+66
-63
lines changed

5 files changed

+66
-63
lines changed

Neos.Fusion/Classes/Core/Cache/ContentCache.php

+11
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,17 @@ public function flushByTag($tag)
382382
return $this->cache->flushByTag($this->sanitizeTag($tag));
383383
}
384384

385+
/**
386+
* Flush content cache entries by tags
387+
*
388+
* @param array<string> $tags values that were assigned to a cache entry in Fusion, for example "Everything", "Node_[…]", "NodeType_[…]", "DescendantOf_[…]" whereas "…" is the node identifier or node type respectively
389+
* @return integer The number of cache entries which actually have been flushed
390+
*/
391+
public function flushByTags(array $tags): int
392+
{
393+
return $this->cache->flushByTags($this->sanitizeTags($tags));
394+
}
395+
385396
/**
386397
* Flush all content cache entries
387398
*

Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php

+48-59
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Neos\ContentRepository\Domain\Model\Workspace;
1515
use Neos\ContentRepository\Domain\Repository\WorkspaceRepository;
16+
use Neos\ContentRepository\Exception\NodeTypeNotFoundException;
1617
use Neos\Flow\Annotations as Flow;
1718
use Neos\Flow\Log\Utility\LogEnvironment;
1819
use Neos\Flow\Persistence\PersistenceManagerInterface;
@@ -54,7 +55,7 @@ class ContentCacheFlusher
5455
protected $systemLogger;
5556

5657
/**
57-
* @var array
58+
* @var array<string, string>
5859
*/
5960
protected $tagsToFlush = [];
6061

@@ -109,18 +110,22 @@ class ContentCacheFlusher
109110
*/
110111
protected $securityContext;
111112

113+
/**
114+
* @Flow\InjectConfiguration(path="fusion.contentCacheDebugMode")
115+
* @var bool
116+
*/
117+
protected $debugMode;
118+
112119
/**
113120
* Register a node change for a later cache flush. This method is triggered by a signal sent via ContentRepository's Node
114121
* model or the Neos Publishing Service.
115122
*
116123
* @param NodeInterface $node The node which has changed in some way
117-
* @param Workspace $targetWorkspace An optional workspace to flush
118-
* @return void
119-
* @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
124+
* @param Workspace|null $targetWorkspace An optional workspace to flush
120125
*/
121126
public function registerNodeChange(NodeInterface $node, Workspace $targetWorkspace = null): void
122127
{
123-
$this->tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".';
128+
$this->addTagToFlush(ContentCache::TAG_EVERYTHING, 'which were tagged with "Everything".');
124129

125130
if (empty($this->workspacesToFlush[$node->getWorkspace()->getName()])) {
126131
$this->resolveWorkspaceChain($node->getWorkspace());
@@ -140,10 +145,11 @@ public function registerNodeChange(NodeInterface $node, Workspace $targetWorkspa
140145
}
141146
}
142147

143-
/**
144-
* @param NodeInterface $node
145-
* @param Workspace $workspace
146-
*/
148+
protected function addTagToFlush(string $tag, string $message = ''): void
149+
{
150+
$this->tagsToFlush[$tag] = $this->debugMode ? $message : '';
151+
}
152+
147153
protected function registerAllTagsToFlushForNodeInWorkspace(NodeInterface $node, Workspace $workspace): void
148154
{
149155
$nodeIdentifier = $node->getIdentifier();
@@ -168,32 +174,23 @@ protected function registerAllTagsToFlushForNodeInWorkspace(NodeInterface $node,
168174
break;
169175
}
170176
$tagName = 'DescendantOf_' . $workspaceHash . '_' . $nodeInWorkspace->getIdentifier();
171-
$this->tagsToFlush[$tagName] = sprintf('which were tagged with "%s" because node "%s" has changed.', $tagName, $node->getPath());
177+
$this->addTagToFlush($tagName, sprintf('which were tagged with "%s" because node "%s" has changed.', $tagName, $node->getPath()));
172178

173179
$legacyTagName = 'DescendantOf_' . $nodeInWorkspace->getIdentifier();
174-
$this->tagsToFlush[$legacyTagName] = sprintf('which were tagged with legacy "%s" because node "%s" has changed.', $legacyTagName, $node->getPath());
180+
$this->addTagToFlush($legacyTagName, sprintf('which were tagged with legacy "%s" because node "%s" has changed.', $legacyTagName, $node->getPath()));
175181
}
176182
}
177183
}
178184

179-
/**
180-
* @param Workspace $workspace
181-
* @return void
182-
*/
183-
protected function resolveWorkspaceChain(Workspace $workspace)
185+
protected function resolveWorkspaceChain(Workspace $workspace): void
184186
{
185187
$cachingHelper = $this->getCachingHelper();
186188

187189
$this->workspacesToFlush[$workspace->getName()][$workspace->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($workspace->getName());
188190
$this->resolveTagsForChildWorkspaces($workspace, $workspace->getName());
189191
}
190192

191-
/**
192-
* @param Workspace $workspace
193-
* @param string $startingPoint
194-
* @return void
195-
*/
196-
protected function resolveTagsForChildWorkspaces(Workspace $workspace, string $startingPoint)
193+
protected function resolveTagsForChildWorkspaces(Workspace $workspace, string $startingPoint): void
197194
{
198195
$cachingHelper = $this->getCachingHelper();
199196
$this->workspacesToFlush[$startingPoint][$workspace->getName()] = $cachingHelper->renderWorkspaceTagForContextNode($workspace->getName());
@@ -210,54 +207,46 @@ protected function resolveTagsForChildWorkspaces(Workspace $workspace, string $s
210207
* Pleas use registerNodeChange() if possible. This method is a low-level api. If you do use this method make sure
211208
* that $cacheIdentifier contains the workspacehash as well as the node identifier: $workspaceHash .'_'. $nodeIdentifier
212209
* The workspacehash can be received via $this->getCachingHelper()->renderWorkspaceTagForContextNode($workpsacename)
213-
*
214-
* @param string $cacheIdentifier
215210
*/
216-
public function registerChangeOnNodeIdentifier($cacheIdentifier)
211+
public function registerChangeOnNodeIdentifier(string $cacheIdentifier): void
217212
{
218-
$this->tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".';
219-
$this->tagsToFlush['Node_' . $cacheIdentifier] = sprintf('which were tagged with "Node_%s" because that identifier has changed.', $cacheIdentifier);
220-
$this->tagsToFlush['NodeDynamicTag_' . $cacheIdentifier] = sprintf('which were tagged with "NodeDynamicTag_%s" because that identifier has changed.', $cacheIdentifier);
213+
$this->addTagToFlush(ContentCache::TAG_EVERYTHING, 'which were tagged with "Everything".');
214+
$this->addTagToFlush('Node_' . $cacheIdentifier, sprintf('which were tagged with "Node_%s" because that identifier has changed.', $cacheIdentifier));
215+
$this->addTagToFlush('NodeDynamicTag_' . $cacheIdentifier, sprintf('which were tagged with "NodeDynamicTag_%s" because that identifier has changed.', $cacheIdentifier));
221216

222217
// Note, as we don't have a node here we cannot go up the structure.
223218
$tagName = 'DescendantOf_' . $cacheIdentifier;
224-
$this->tagsToFlush[$tagName] = sprintf('which were tagged with "%s" because node "%s" has changed.', $tagName, $cacheIdentifier);
219+
$this->addTagToFlush($tagName, sprintf('which were tagged with "%s" because node "%s" has changed.', $tagName, $cacheIdentifier));
225220
}
226221

227222
/**
228223
* This is a low-level api. Please use registerNodeChange() if possible. Otherwise make sure that $nodeTypePrefix
229224
* is set up correctly and contains the workspacehash wich can be received via
230225
* $this->getCachingHelper()->renderWorkspaceTagForContextNode($workpsacename)
231226
*
232-
* @param string $nodeTypeName
233-
* @param string $referenceNodeIdentifier
234-
* @param string $nodeTypePrefix
235-
*
236-
* @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
227+
* @throws NodeTypeNotFoundException
237228
*/
238-
public function registerChangeOnNodeType($nodeTypeName, $referenceNodeIdentifier = null, $nodeTypePrefix = '')
229+
public function registerChangeOnNodeType(string $nodeTypeName, string $referenceNodeIdentifier = null, string $nodeTypePrefix = ''): void
239230
{
240-
$this->tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".';
231+
$this->addTagToFlush(ContentCache::TAG_EVERYTHING, 'which were tagged with "Everything".');
241232

242233
$nodeTypesToFlush = $this->getAllImplementedNodeTypeNames($this->nodeTypeManager->getNodeType($nodeTypeName));
243234

244-
if (strlen($nodeTypePrefix) > 0) {
235+
if ($nodeTypePrefix !== '') {
245236
$nodeTypePrefix = rtrim($nodeTypePrefix, '_') . '_';
246237
}
247238

248239
foreach ($nodeTypesToFlush as $nodeTypeNameToFlush) {
249-
$this->tagsToFlush['NodeType_' . $nodeTypePrefix . $nodeTypeNameToFlush] = sprintf('which were tagged with "NodeType_%s" because node "%s" has changed and was of type "%s".', $nodeTypeNameToFlush, ($referenceNodeIdentifier ? $referenceNodeIdentifier : ''), $nodeTypeName);
240+
$this->addTagToFlush('NodeType_' . $nodeTypePrefix . $nodeTypeNameToFlush, sprintf('which were tagged with "NodeType_%s" because node "%s" has changed and was of type "%s".', $nodeTypeNameToFlush, ($referenceNodeIdentifier ? $referenceNodeIdentifier : ''), $nodeTypeName));
250241
}
251242
}
252243

253244
/**
254245
* Fetches possible usages of the asset and registers nodes that use the asset as changed.
255246
*
256-
* @param AssetInterface $asset
257-
* @return void
258-
* @throws \Neos\ContentRepository\Exception\NodeTypeNotFoundException
247+
* @throws NodeTypeNotFoundException
259248
*/
260-
public function registerAssetChange(AssetInterface $asset)
249+
public function registerAssetChange(AssetInterface $asset): void
261250
{
262251
// In Nodes only assets are referenced, never asset variants directly. When an asset
263252
// variant is updated, it is passed as $asset, but since it is never "used" by any node
@@ -293,31 +282,35 @@ public function registerAssetChange(AssetInterface $asset)
293282
$assetIdentifier = $this->persistenceManager->getIdentifierByObject($asset);
294283
// @see RuntimeContentCache.addTag
295284
$tagName = 'AssetDynamicTag_' . $workspaceHash . '_' . $assetIdentifier;
296-
$this->tagsToFlush[$tagName] = sprintf('which were tagged with "%s" because asset "%s" has changed.', $tagName, $assetIdentifier);
285+
$this->addTagToFlush($tagName, sprintf('which were tagged with "%s" because asset "%s" has changed.', $tagName, $assetIdentifier));
297286
}
298287
}
299288

289+
public function shutdownObject(): void
290+
{
291+
$this->commit();
292+
}
293+
300294
/**
301295
* Flush caches according to the previously registered node changes.
302-
*
303-
* @return void
304296
*/
305-
public function shutdownObject()
297+
protected function commit(): void
306298
{
307299
if ($this->tagsToFlush !== []) {
308-
foreach ($this->tagsToFlush as $tag => $logMessage) {
309-
$affectedEntries = $this->contentCache->flushByTag($tag);
310-
if ($affectedEntries > 0) {
311-
$this->systemLogger->debug(sprintf('Content cache: Removed %s entries %s', $affectedEntries, $logMessage));
300+
if ($this->debugMode) {
301+
foreach ($this->tagsToFlush as $tag => $logMessage) {
302+
$affectedEntries = $this->contentCache->flushByTag($tag);
303+
if ($affectedEntries > 0) {
304+
$this->systemLogger->debug(sprintf('Content cache: Removed %s entries %s', $affectedEntries, $logMessage));
305+
}
312306
}
307+
} else {
308+
$affectedEntries = $this->contentCache->flushByTags(array_keys($this->tagsToFlush));
309+
$this->systemLogger->debug(sprintf('Content cache: Removed %s entries', $affectedEntries));
313310
}
314311
}
315312
}
316313

317-
/**
318-
* @param AssetUsageInNodeProperties $assetUsage
319-
* @return ContentContext
320-
*/
321314
protected function getContextForReference(AssetUsageInNodeProperties $assetUsage): ContentContext
322315
{
323316
$hash = md5(sprintf('%s-%s', $assetUsage->getWorkspaceName(), json_encode($assetUsage->getDimensionValues())));
@@ -334,10 +327,9 @@ protected function getContextForReference(AssetUsageInNodeProperties $assetUsage
334327
}
335328

336329
/**
337-
* @param NodeType $nodeType
338330
* @return array<string>
339331
*/
340-
protected function getAllImplementedNodeTypeNames(NodeType $nodeType)
332+
protected function getAllImplementedNodeTypeNames(NodeType $nodeType): array
341333
{
342334
$self = $this;
343335
$types = array_reduce($nodeType->getDeclaredSuperTypes(), function (array $types, NodeType $superType) use ($self) {
@@ -348,9 +340,6 @@ protected function getAllImplementedNodeTypeNames(NodeType $nodeType)
348340
return $types;
349341
}
350342

351-
/**
352-
* @return CachingHelper
353-
*/
354343
protected function getCachingHelper(): CachingHelper
355344
{
356345
if (!$this->cachingHelper instanceof CachingHelper) {

Neos.Neos/Classes/Routing/Cache/RouteCacheFlusher.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ public function registerBaseWorkspaceChange(Workspace $workspace, Workspace $old
7777
*/
7878
public function commit()
7979
{
80-
foreach ($this->tagsToFlush as $tag) {
81-
$this->routeCachingService->flushCachesByTag($tag);
82-
}
80+
$this->routeCachingService->flushCachesByTags($this->tagsToFlush);
8381
$this->tagsToFlush = [];
8482
}
8583

Neos.Neos/Configuration/Settings.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ Neos:
2121
# - in Production context
2222
enableObjectTreeCache: false
2323

24+
# If set to true, content cache flushes will be done on a per-tag basis and generate additional log output
25+
# which allows understanding why flushed entries were flushed. This is useful for debugging but will
26+
# hurt performance and should not be used in production.
27+
contentCacheDebugMode: false
28+
2429
# Packages can now register with this setting to get their Fusion in the path:
2530
# resources://MyVendor.MyPackageKey/Private/Fusion/Root.fusion
2631
# included automatically.

Neos.Neos/Tests/Unit/Fusion/Cache/ContentCacheFlusherTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Neos\ContentRepository\Domain\Model\Workspace;
1717
use Neos\Flow\Tests\UnitTestCase;
1818
use Neos\Neos\Fusion\Cache\ContentCacheFlusher;
19-
use Neos\Neos\Fusion\Helper\CachingHelper;
2019

2120
/**
2221
* Tests the CachingHelper
@@ -44,6 +43,7 @@ public function theWorkspaceChainWillOnlyEvaluatedIfNeeded()
4443
$nodeMock = $this->getMockBuilder(NodeInterface::class)->disableOriginalConstructor()->getMock();
4544
$nodeMock->expects(self::any())->method('getWorkspace')->willReturn($workspace);
4645
$nodeMock->expects(self::any())->method('getNodeType')->willReturn($nodeType);
46+
$nodeMock->expects(self::any())->method('getIdentifier')->willReturn('some-node-identifier');
4747

4848
$contentCacheFlusher->registerNodeChange($nodeMock);
4949
}

0 commit comments

Comments
 (0)