Skip to content

Commit

Permalink
Merge pull request #5122 from dlubitz/90/bugfix/asset-cache-flush-wit…
Browse files Browse the repository at this point in the history
…hout-workspace

BUGFIX: Flush node content caches without workspace name
  • Loading branch information
dlubitz authored Jun 14, 2024
2 parents f145afd + 2b4890c commit baf35c3
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 60 deletions.
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
);
}
}
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

0 comments on commit baf35c3

Please sign in to comment.