Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: Flush node content caches without workspace name #5122

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 39 additions & 27 deletions Neos.Neos/Classes/Fusion/Cache/CacheTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
);
}

Expand All @@ -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,
);
}

Expand All @@ -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,
);
}

Expand All @@ -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,
);
}

Expand All @@ -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
dlubitz marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
37 changes: 29 additions & 8 deletions Neos.Neos/Classes/Fusion/Cache/CacheTagSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
));
Expand All @@ -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
Expand All @@ -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)
);
}
Expand Down
13 changes: 13 additions & 0 deletions Neos.Neos/Classes/Fusion/Cache/CacheTagWorkspaceName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Neos\Neos\Fusion\Cache;

/**
* A special enum to explicitly represent any workspace {@see CacheTag}
*/
enum CacheTagWorkspaceName
{
case ANY;
}
20 changes: 13 additions & 7 deletions Neos.Neos/Classes/Fusion/Cache/ContentCacheFlusher.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,24 @@ public function flushNodeAggregate(
$tagsToFlush[ContentCache::TAG_EVERYTHING] = 'which were tagged with "Everything".';

$tagsToFlush = array_merge(
$this->collectTagsForChangeOnNodeAggregate($contentRepository, $workspaceName, $nodeAggregateId),
$this->collectTagsForChangeOnNodeAggregate($contentRepository, $workspaceName, $nodeAggregateId, false),
$tagsToFlush
);

$this->flushTags($tagsToFlush);
}

/**
* @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<string,string>
*/
private function collectTagsForChangeOnNodeAggregate(
ContentRepository $contentRepository,
WorkspaceName $workspaceName,
NodeAggregateId $nodeAggregateId
NodeAggregateId $nodeAggregateId,
bool $anyWorkspace,
): array {
$contentGraph = $contentRepository->getContentGraph($workspaceName);

Expand All @@ -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);
Expand Down Expand Up @@ -159,7 +164,7 @@ private function collectTagsForChangeOnNodeAggregate(
*/
private function collectTagsForChangeOnNodeIdentifier(
ContentRepositoryId $contentRepositoryId,
WorkspaceName $workspaceName,
WorkspaceName|CacheTagWorkspaceName $workspaceName,
NodeAggregateId $nodeAggregateId,
): array {
$tagsToFlush = [];
Expand Down Expand Up @@ -192,7 +197,7 @@ private function collectTagsForChangeOnNodeIdentifier(
private function collectTagsForChangeOnNodeType(
NodeTypeName $nodeTypeName,
ContentRepositoryId $contentRepositoryId,
WorkspaceName $workspaceName,
WorkspaceName|CacheTagWorkspaceName $workspaceName,
?NodeAggregateId $referenceNodeIdentifier,
ContentRepository $contentRepository
): array {
Expand Down Expand Up @@ -305,7 +310,8 @@ public function registerAssetChange(AssetInterface $asset): void
$this->collectTagsForChangeOnNodeAggregate(
$contentRepository,
$workspaceName,
$usage->nodeAggregateId
$usage->nodeAggregateId,
true
),
$tagsToFlush
);
Expand Down
30 changes: 21 additions & 9 deletions Neos.Neos/Classes/Fusion/Helper/CachingHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
);
}

/**
Expand All @@ -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(),
);
}

/**
Expand Down
Loading
Loading