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

FEATURE: Introduce catchup hook for DocumentUriPath projection #4309

Merged
merged 1 commit into from
Jul 3, 2023
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
45 changes: 45 additions & 0 deletions Neos.Neos/Classes/FrontendRouting/Projection/DocumentNodeInfos.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Neos.Neos package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

declare(strict_types=1);

namespace Neos\Neos\FrontendRouting\Projection;

use Traversable;

/**
* @implements \IteratorAggregate<DocumentNodeInfo>
*/
final class DocumentNodeInfos implements \IteratorAggregate
{
/**
* @param array<DocumentNodeInfo> $documentNodeInfos
*/
private function __construct(
private readonly array $documentNodeInfos
) {
}

/**
* @param array<DocumentNodeInfo> $documentNodeInfos
* @return static
*/
public static function create(array $documentNodeInfos): static
{
return new static($documentNodeInfos);
}

public function getIterator(): Traversable
{
return new \ArrayIterator($this->documentNodeInfos);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function getByIdAndDimensionSpacePointHash(
NodeAggregateId $nodeAggregateId,
string $dimensionSpacePointHash
): DocumentNodeInfo {
$cacheKey = $nodeAggregateId->value . '#' . $dimensionSpacePointHash;
$cacheKey = $this->calculateCacheKey($nodeAggregateId, $dimensionSpacePointHash);
if ($this->cacheEnabled && isset($this->getByIdAndDimensionSpacePointHashCache[$cacheKey])) {
return $this->getByIdAndDimensionSpacePointHashCache[$cacheKey];
}
Expand Down Expand Up @@ -257,9 +257,69 @@ private function fetchSingle(string $where, array $parameters): DocumentNodeInfo
return DocumentNodeInfo::fromDatabaseRow($row);
}

/**
* @param string $where
* @param array<string> $parameters
* @return DocumentNodeInfos
*/
private function fetchMultiple(string $where, array $parameters): DocumentNodeInfos
{
try {
$rows = $this->dbal->fetchAllAssociative(
'SELECT * FROM ' . $this->tableNamePrefix . '_uri
WHERE ' . $where,
$parameters,
DocumentUriPathProjection::COLUMN_TYPES_DOCUMENT_URIS
);
} catch (DBALException $e) {
throw new \RuntimeException(sprintf(
'Failed to load node for query "%s": %s',
$where,
$e->getMessage()
), 1683808640, $e);
}

return DocumentNodeInfos::create(
array_map(DocumentNodeInfo::fromDatabaseRow(...), $rows)
);
}

private function calculateCacheKey(NodeAggregateId $nodeAggregateId, string $dimensionSpacePointHash): string
{
return $nodeAggregateId->value . '#' . $dimensionSpacePointHash;
}

public function purgeCacheFor(DocumentNodeInfo $nodeInfo): void
{
if ($this->cacheEnabled) {
$cacheKey = $this->calculateCacheKey($nodeInfo->getNodeAggregateId(), $nodeInfo->getDimensionSpacePointHash());
unset($this->getByIdAndDimensionSpacePointHashCache[$cacheKey]);
}
}

public function disableCache(): void
{
$this->cacheEnabled = false;
$this->getByIdAndDimensionSpacePointHashCache = [];
}

/**
dlubitz marked this conversation as resolved.
Show resolved Hide resolved
* Returns the DocumentNodeInfos of all descendants of a given node.
* Note: This will not exclude *disabled* nodes in order to allow the calling side
* to make a distinction (e.g. in order to display a custom error)
*
* @param DocumentNodeInfo $node
* @return DocumentNodeInfos
*/
public function getDescendantsOfNode(DocumentNodeInfo $node): DocumentNodeInfos
{
return $this->fetchMultiple(
'dimensionSpacePointHash = :dimensionSpacePointHash
AND nodeAggregateIdPath LIKE :childNodeAggregateIdPathPrefix',
[
'dimensionSpacePointHash' => $node->getDimensionSpacePointHash(),
'childNodeAggregateIdPathPrefix' => $node->getNodeAggregateIdPath() . '/%',
]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
use Neos\Flow\Annotations as Flow;
use Neos\Neos\Domain\Model\SiteNodeName;
use Neos\Neos\FrontendRouting\Exception\NodeNotFoundException;
use Neos\ContentRepository\Core\Projection\CatchUpHookFactoryInterface;
use Neos\ContentRepository\Core\Projection\CatchUpHookInterface;

/**
* @implements ProjectionInterface<DocumentUriPathFinder>
Expand All @@ -66,6 +68,7 @@ public function __construct(
private readonly NodeTypeManager $nodeTypeManager,
private readonly Connection $dbal,
private readonly string $tableNamePrefix,
private readonly CatchUpHookFactoryInterface $catchUpHookFactory
) {
$this->checkpointStorage = new DoctrineCheckpointStorage(
$this->dbal,
Expand Down Expand Up @@ -141,17 +144,25 @@ public function canHandle(Event $event): bool

public function catchUp(EventStreamInterface $eventStream, ContentRepository $contentRepository): void
{
$catchUp = CatchUp::create($this->apply(...), $this->checkpointStorage);
$catchUpHook = $this->catchUpHookFactory->build($contentRepository);
$catchUpHook->onBeforeCatchUp();
$catchUp = CatchUp::create(
fn(EventEnvelope $eventEnvelope) => $this->apply($eventEnvelope, $catchUpHook),
$this->checkpointStorage
);
$catchUp = $catchUp->withOnBeforeBatchCompleted(fn() => $catchUpHook->onBeforeBatchCompleted());
$catchUp->run($eventStream);
$catchUpHook->onAfterCatchUp();
}

private function apply(\Neos\EventStore\Model\EventEnvelope $eventEnvelope): void
private function apply(\Neos\EventStore\Model\EventEnvelope $eventEnvelope, CatchUpHookInterface $catchUpHook): void
{
if (!$this->canHandle($eventEnvelope->event)) {
return;
}

$eventInstance = $this->eventNormalizer->denormalize($eventEnvelope->event);
$catchUpHook->onBeforeEvent($eventInstance, $eventEnvelope);

$this->dbal->beginTransaction();

Expand All @@ -177,6 +188,7 @@ private function apply(\Neos\EventStore\Model\EventEnvelope $eventEnvelope): voi

try {
$this->dbal->commit();
$catchUpHook->onAfterEvent($eventInstance, $eventEnvelope);
} catch (ConnectionException $e) {
throw new \RuntimeException(sprintf(
'Failed to commit transaction in %s: %s',
Expand Down Expand Up @@ -536,6 +548,7 @@ private function whenNodeAggregateWasRemoved(NodeAggregateWasRemoved $event): vo
'nodeAggregateId' => $node->getNodeAggregateId()->value,
'childNodeAggregateIdPathPrefix' => $node->getNodeAggregateIdPath() . '/%',
]);
$this->getState()->purgeCacheFor($node);
}
}

Expand Down Expand Up @@ -604,6 +617,8 @@ private function whenNodePropertiesWereSet(NodePropertiesWereSet $event, EventEn
'childNodeAggregateIdPathPrefix' => $node->getNodeAggregateIdPath() . '/%',
]
);
$this->getState()->purgeCacheFor($node);

$this->emitDocumentUriPathChanged($oldUriPath, $newUriPath, $event, $eventEnvelope);
}
}
Expand Down Expand Up @@ -639,6 +654,8 @@ private function whenNodeAggregateWasMoved(NodeAggregateWasMoved $event): void
null
),
};

$this->getState()->purgeCacheFor($node);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public function build(
$projectionFactoryDependencies->eventNormalizer,
$projectionFactoryDependencies->nodeTypeManager,
$this->dbal,
self::projectionTableNamePrefix($projectionFactoryDependencies->contentRepositoryId)
self::projectionTableNamePrefix($projectionFactoryDependencies->contentRepositoryId),
$catchUpHookFactory
);
}
}
16 changes: 0 additions & 16 deletions Neos.Neos/Classes/Package.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,6 @@ function (string $oldUriPath, string $newUriPath, $_, EventEnvelope $eventEnvelo
/** @var RouterCachingService $routerCachingService */
$routerCachingService = $bootstrap->getObjectManager()->get(RouterCachingService::class);
$routerCachingService->flushCachesForUriPath($oldUriPath);

if (class_exists(RedirectStorageInterface::class)) {
if (!$bootstrap->getObjectManager()->isRegistered(RedirectStorageInterface::class)) {
return;
}
/** @var RedirectStorageInterface $redirectStorage */
$redirectStorage = $bootstrap->getObjectManager()->get(RedirectStorageInterface::class);
$redirectStorage->addRedirect(
$oldUriPath,
$newUriPath,
301,
[],
(string)$eventEnvelope->event->metadata->get('initiatingUserId'),
'via DocumentUriPathProjector'
);
}
dlubitz marked this conversation as resolved.
Show resolved Hide resolved
}
);

Expand Down