diff --git a/Neos.Neos/Classes/Fusion/Cache/CacheTag.php b/Neos.Neos/Classes/Fusion/Cache/CacheTag.php index 69814d621ef..40510f233ba 100644 --- a/Neos.Neos/Classes/Fusion/Cache/CacheTag.php +++ b/Neos.Neos/Classes/Fusion/Cache/CacheTag.php @@ -18,6 +18,11 @@ class CacheTag { protected const PATTERN = '/^[a-zA-Z0-9_%\-&]{1,250}$/'; + protected const PREFIX_NODE = 'Node'; + protected const PREFIX_DESCENDANT_OF = 'DescendantOf'; + protected const PREFIX_ANCESTOR = 'Ancestor'; + protected const PREFIX_NODE_TYPE = 'NodeType'; + protected const PREFIX_DYNAMIC_NODE_TAG = 'DynamicNodeTag'; private function __construct( public readonly string $value @@ -30,15 +35,20 @@ private function __construct( } } + private static function fromSegments(string ...$segments): self + { + return new self(implode('_', $segments)); + } + final public static function forNodeAggregate( ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, NodeAggregateId $nodeAggregateId, ): self { - return new self( - 'Node_' - . self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId) - . '_' . $nodeAggregateId->value + return self::fromSegments( + self::PREFIX_NODE, + self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId), + $nodeAggregateId->value, ); } @@ -53,13 +63,13 @@ final public static function forNodeAggregateFromNode(Node $node): self final public static function forDescendantOfNode( ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, NodeAggregateId $nodeAggregateId, ): self { - return new self( - 'DescendantOf_' - . self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId) - . '_' . $nodeAggregateId->value + return self::fromSegments( + self::PREFIX_DESCENDANT_OF, + self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId), + $nodeAggregateId->value, ); } @@ -74,13 +84,13 @@ final public static function forDescendantOfNodeFromNode(Node $node): self final public static function forAncestorNode( ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, NodeAggregateId $nodeAggregateId, ): self { - return new self( - 'Ancestor_' - . self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId) - . '_' . $nodeAggregateId->value + return self::fromSegments( + self::PREFIX_ANCESTOR, + self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId), + $nodeAggregateId->value, ); } @@ -95,25 +105,25 @@ final public static function forAncestorNodeFromNode(Node $node): self final public static function forNodeTypeName( ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, NodeTypeName $nodeTypeName, ): self { - return new self( - 'NodeType_' - . self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId) - . '_' . \strtr($nodeTypeName->value, '.:', '_-') + return self::fromSegments( + self::PREFIX_NODE_TYPE, + self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId), + \strtr($nodeTypeName->value, '.:', '_-'), ); } final public static function forDynamicNodeAggregate( ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, NodeAggregateId $nodeAggregateId, ): self { - return new self( - 'DynamicNodeTag_' - . self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId) - . '_' . $nodeAggregateId->value + return self::fromSegments( + self::PREFIX_DYNAMIC_NODE_TAG, + self::getHashForWorkspaceNameAndContentRepositoryId($workspaceName, $contentRepositoryId), + $nodeAggregateId->value, ); } @@ -123,9 +133,11 @@ final public static function fromString(string $string): self } protected static function getHashForWorkspaceNameAndContentRepositoryId( - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, ContentRepositoryId $contentRepositoryId, ): string { - return sha1($workspaceName->value . '@' . $contentRepositoryId->value); + return sha1( + $workspaceName === CacheTagWorkspaceName::ANY ? $contentRepositoryId->value : $workspaceName->value . '@' . $contentRepositoryId->value + ); } } diff --git a/Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php b/Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php index b620b062470..e96bfd769cb 100644 --- a/Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php +++ b/Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php @@ -38,8 +38,19 @@ public static function forDescendantOfNodesFromNodes( Nodes $nodes ): self { return new self(...array_map( - fn (Node $node): CacheTag => CacheTag::forDescendantOfNodeFromNode( - $node + CacheTag::forDescendantOfNodeFromNode(...), + iterator_to_array($nodes), + )); + } + + public static function forDescendantOfNodesFromNodesWithoutWorkspace( + Nodes $nodes, + ): self { + return new self(...array_map( + static fn (Node $node) => CacheTag::forDescendantOfNode( + $node->contentRepositoryId, + CacheTagWorkspaceName::ANY, + $node->aggregateId, ), iterator_to_array($nodes) )); @@ -49,21 +60,31 @@ public static function forNodeAggregatesFromNodes( Nodes $nodes ): self { return new self(...array_map( - fn (Node $node): CacheTag => CacheTag::forNodeAggregateFromNode( - $node - ), + CacheTag::forNodeAggregateFromNode(...), iterator_to_array($nodes) )); } + public static function forNodeAggregatesFromNodesWithoutWorkspace( + Nodes $nodes + ): self { + return new self(...array_map( + static fn (Node $node) => CacheTag::forNodeAggregate( + $node->contentRepositoryId, + CacheTagWorkspaceName::ANY, + $node->aggregateId + ), + iterator_to_array($nodes), + )); + } public static function forNodeTypeNames( ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, NodeTypeNames $nodeTypeNames ): self { return new self(...array_map( - fn (NodeTypeName $nodeTypeName): CacheTag => CacheTag::forNodeTypeName( + static fn (NodeTypeName $nodeTypeName): CacheTag => CacheTag::forNodeTypeName( $contentRepositoryId, $workspaceName, $nodeTypeName @@ -86,7 +107,7 @@ public function add(CacheTag $cacheTag): self public function toStringArray(): array { return array_map( - fn (CacheTag $tag): string => $tag->value, + static fn (CacheTag $tag): string => $tag->value, array_values($this->tags) ); } diff --git a/Neos.Neos/Classes/Fusion/Cache/CacheTagWorkspaceName.php b/Neos.Neos/Classes/Fusion/Cache/CacheTagWorkspaceName.php new file mode 100644 index 00000000000..2aaa79b476e --- /dev/null +++ b/Neos.Neos/Classes/Fusion/Cache/CacheTagWorkspaceName.php @@ -0,0 +1,13 @@ +collectTagsForChangeOnNodeAggregate($contentRepository, $workspaceName, $nodeAggregateId), + $this->collectTagsForChangeOnNodeAggregate($contentRepository, $workspaceName, $nodeAggregateId, false), $tagsToFlush ); @@ -80,12 +80,16 @@ public function flushNodeAggregate( } /** + * @param bool $anyWorkspace This is needed to flush nodes on asset changes, as the asset can get rendered in all workspaces, but lives + * usually only in live workspace. + * * @return array */ private function collectTagsForChangeOnNodeAggregate( ContentRepository $contentRepository, WorkspaceName $workspaceName, - NodeAggregateId $nodeAggregateId + NodeAggregateId $nodeAggregateId, + bool $anyWorkspace, ): array { $contentGraph = $contentRepository->getContentGraph($workspaceName); @@ -96,12 +100,13 @@ private function collectTagsForChangeOnNodeAggregate( // Node Aggregate was removed in the meantime, so no need to clear caches on this one anymore. return []; } - $tagsToFlush = $this->collectTagsForChangeOnNodeIdentifier($contentRepository->id, $workspaceName, $nodeAggregateId); + $workspaceNameToFlush = $anyWorkspace ? CacheTagWorkspaceName::ANY : $workspaceName; + $tagsToFlush = $this->collectTagsForChangeOnNodeIdentifier($contentRepository->id, $workspaceNameToFlush, $nodeAggregateId); $tagsToFlush = array_merge($this->collectTagsForChangeOnNodeType( $nodeAggregate->nodeTypeName, $contentRepository->id, - $workspaceName, + $workspaceNameToFlush, $nodeAggregateId, $contentRepository ), $tagsToFlush); @@ -159,7 +164,7 @@ private function collectTagsForChangeOnNodeAggregate( */ private function collectTagsForChangeOnNodeIdentifier( ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, NodeAggregateId $nodeAggregateId, ): array { $tagsToFlush = []; @@ -192,7 +197,7 @@ private function collectTagsForChangeOnNodeIdentifier( private function collectTagsForChangeOnNodeType( NodeTypeName $nodeTypeName, ContentRepositoryId $contentRepositoryId, - WorkspaceName $workspaceName, + WorkspaceName|CacheTagWorkspaceName $workspaceName, ?NodeAggregateId $referenceNodeIdentifier, ContentRepository $contentRepository ): array { @@ -305,7 +310,8 @@ public function registerAssetChange(AssetInterface $asset): void $this->collectTagsForChangeOnNodeAggregate( $contentRepository, $workspaceName, - $usage->nodeAggregateId + $usage->nodeAggregateId, + true ), $tagsToFlush ); diff --git a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php index 5901939576b..11e56aa3d26 100644 --- a/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php +++ b/Neos.Neos/Classes/Fusion/Helper/CachingHelper.php @@ -25,6 +25,7 @@ use Neos\Neos\Domain\Model\NodeCacheEntryIdentifier; use Neos\Neos\Fusion\Cache\CacheTag; use Neos\Neos\Fusion\Cache\CacheTagSet; +use Neos\Neos\Fusion\Cache\CacheTagWorkspaceName; /** * Caching helper to make cache tag generation easier. @@ -53,7 +54,10 @@ public function nodeTag(iterable|Node $nodes): array $nodes = iterator_to_array($nodes); } - return CacheTagSet::forNodeAggregatesFromNodes(Nodes::fromArray($nodes))->toStringArray(); + return array_merge( + CacheTagSet::forNodeAggregatesFromNodes(Nodes::fromArray($nodes))->toStringArray(), + CacheTagSet::forNodeAggregatesFromNodesWithoutWorkspace(Nodes::fromArray($nodes))->toStringArray(), + ); } public function entryIdentifierForNode(Node $node): NodeCacheEntryIdentifier @@ -94,11 +98,18 @@ public function nodeTypeTag(string|iterable $nodeTypes, Node $contextNode): arra $nodeTypes = iterator_to_array($nodeTypes); } - return CacheTagSet::forNodeTypeNames( - $contextNode->contentRepositoryId, - $contextNode->workspaceName, - NodeTypeNames::fromStringArray($nodeTypes) - )->toStringArray(); + return array_merge( + CacheTagSet::forNodeTypeNames( + $contextNode->contentRepositoryId, + $contextNode->workspaceName, + NodeTypeNames::fromStringArray($nodeTypes) + )->toStringArray(), + CacheTagSet::forNodeTypeNames( + $contextNode->contentRepositoryId, + CacheTagWorkspaceName::ANY, + NodeTypeNames::fromStringArray($nodeTypes) + )->toStringArray(), + ); } /** @@ -118,9 +129,10 @@ public function descendantOfTag(iterable|Node $nodes): array $nodes = iterator_to_array($nodes); } - return CacheTagSet::forDescendantOfNodesFromNodes( - Nodes::fromArray($nodes) - )->toStringArray(); + return array_merge( + CacheTagSet::forDescendantOfNodesFromNodes(Nodes::fromArray($nodes))->toStringArray(), + CacheTagSet::forDescendantOfNodesFromNodesWithoutWorkspace(Nodes::fromArray($nodes))->toStringArray(), + ); } /** diff --git a/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php b/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php index 2f9916b6399..3b9992926f9 100644 --- a/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php +++ b/Neos.Neos/Tests/Unit/Fusion/Helper/CachingHelperTest.php @@ -57,12 +57,20 @@ public function nodeTypeTagDataProvider() $nodeTypeName3 = 'Neos.Neos:Moo'; return [ - [$nodeTypeName1, ['NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Foo']], + [$nodeTypeName1, + [ + 'NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Foo', + 'NodeType_7505d64a54e061b7acd54ccd58b49dc43500b635_Neos_Neos-Foo', + ] + ], [[$nodeTypeName1, $nodeTypeName2, $nodeTypeName3], [ 'NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Foo', 'NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Bar', 'NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Moo', + 'NodeType_7505d64a54e061b7acd54ccd58b49dc43500b635_Neos_Neos-Foo', + 'NodeType_7505d64a54e061b7acd54ccd58b49dc43500b635_Neos_Neos-Bar', + 'NodeType_7505d64a54e061b7acd54ccd58b49dc43500b635_Neos_Neos-Moo', ] ], [(new \ArrayObject([$nodeTypeName1, $nodeTypeName2, $nodeTypeName3])), @@ -70,6 +78,9 @@ public function nodeTypeTagDataProvider() 'NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Foo', 'NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Bar', 'NodeType_364cfc8e70b2baa23dbd14503d2bd00e063829e7_Neos_Neos-Moo', + 'NodeType_7505d64a54e061b7acd54ccd58b49dc43500b635_Neos_Neos-Foo', + 'NodeType_7505d64a54e061b7acd54ccd58b49dc43500b635_Neos_Neos-Bar', + 'NodeType_7505d64a54e061b7acd54ccd58b49dc43500b635_Neos_Neos-Moo', ] ], ]; @@ -103,15 +114,25 @@ public function nodeDataProvider() $node2 = $this->createNode(NodeAggregateId::fromString($nodeIdentifier2)); return [ - [$node1, ['Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], - [[$node1], ['Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], + [$node1, [ + 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'Node_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + ]], + [[$node1], [ + 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'Node_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + ]], [[$node1, $node2], [ 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', - 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a' + 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a', + 'Node_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'Node_7505d64a54e061b7acd54ccd58b49dc43500b635_7005c7cf-4d19-ce36-0873-476b6cadb71a', ]], [(new \ArrayObject([$node1, $node2])), [ 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', - 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a' + 'Node_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a', + 'Node_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'Node_7505d64a54e061b7acd54ccd58b49dc43500b635_7005c7cf-4d19-ce36-0873-476b6cadb71a', ]] ]; } @@ -169,15 +190,25 @@ public function descendantOfDataProvider() return [ - [$node1, ['DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], - [[$node1], ['DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306']], + [$node1, [ + 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'DescendantOf_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + ]], + [[$node1], [ + 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'DescendantOf_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + ]], [[$node1, $node2], [ 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', - 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a' + 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a', + 'DescendantOf_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'DescendantOf_7505d64a54e061b7acd54ccd58b49dc43500b635_7005c7cf-4d19-ce36-0873-476b6cadb71a', ]], [(new \ArrayObject([$node1, $node2])), [ 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_ca511a55-c5c0-f7d7-8d71-8edeffc75306', - 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a' + 'DescendantOf_364cfc8e70b2baa23dbd14503d2bd00e063829e7_7005c7cf-4d19-ce36-0873-476b6cadb71a', + 'DescendantOf_7505d64a54e061b7acd54ccd58b49dc43500b635_ca511a55-c5c0-f7d7-8d71-8edeffc75306', + 'DescendantOf_7505d64a54e061b7acd54ccd58b49dc43500b635_7005c7cf-4d19-ce36-0873-476b6cadb71a', ]] ]; }