Skip to content

Commit

Permalink
Merge pull request #4717 from mhsdesign/bugfix/graceFullErrorHandling…
Browse files Browse the repository at this point in the history
…IfSetupNotCompletet

BUGFIX: Graceful error handling if setup not completed
  • Loading branch information
mhsdesign authored Feb 16, 2024
2 parents 669ba6d + deb815e commit 116b292
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 95 deletions.
15 changes: 10 additions & 5 deletions Neos.Fusion/Classes/View/FusionView.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,13 @@ protected function getMergedFusionObjectTree(): FusionConfiguration
*/
public function getFusionPathPatterns(): array
{
$packageKey = $this->getPackageKey();
$fusionPathPatterns = array_map(
function ($fusionPathPattern) use ($packageKey) {
return str_replace('@package', $packageKey, $fusionPathPattern);
function ($fusionPathPattern) {
if (!str_contains($fusionPathPattern, '@package')) {
return $fusionPathPattern;
}

return str_replace('@package', $this->getPackageKey(), $fusionPathPattern);
},
$this->getOption('fusionPathPatterns')
);
Expand All @@ -247,8 +250,10 @@ protected function getPackageKey()
if ($packageKey !== null) {
return $packageKey;
} else {
/** @var $request ActionRequest */
$request = $this->controllerContext->getRequest();
$request = $this->controllerContext?->getRequest();
if (!$request) {
throw new \RuntimeException(sprintf('To resolve the @package in all fusionPathPatterns, either packageKey has to be specified, or the current request be available.'));
}
return $request->getControllerPackageKey();
}
}
Expand Down
13 changes: 6 additions & 7 deletions Neos.Neos/Classes/Controller/Frontend/NodeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,18 @@ public function showAction(string $node): void
VisibilityConstraints::frontend()
);

$nodeInstance = $subgraph->findNodeById($nodeAddress->nodeAggregateId);
if ($nodeInstance === null) {
throw new NodeNotFoundException(sprintf('The cached node address for this uri could not be resolved. Possibly you have to flush the "Flow_Mvc_Routing_Route" cache. %s', $nodeAddress), 1707300738);
}

$site = $subgraph->findClosestNode($nodeAddress->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE));
if ($site === null) {
throw new NodeNotFoundException("TODO: SITE NOT FOUND; should not happen (for address " . $nodeAddress);
throw new NodeNotFoundException(sprintf('The site node of %s could not be resolved.', $nodeAddress), 1707300861);
}

$this->fillCacheWithContentNodes($nodeAddress->nodeAggregateId, $subgraph);

$nodeInstance = $subgraph->findNodeById($nodeAddress->nodeAggregateId);

if ($nodeInstance === null) {
throw new NodeNotFoundException('The requested node does not exist', 1596191460);
}

if ($this->getNodeType($nodeInstance)->isOfType(NodeTypeNameFactory::NAME_SHORTCUT)) {
$this->handleShortcutNode($nodeAddress, $contentRepository);
}
Expand Down
10 changes: 9 additions & 1 deletion Neos.Neos/Classes/Domain/Service/SiteService.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,15 @@ public function pruneSite(Site $site): void
$site->getConfiguration()->contentRepositoryId,
new SiteServiceInternalsFactory()
);
$siteServiceInternals->removeSiteNode($site->getNodeName());

try {
$siteServiceInternals->removeSiteNode($site->getNodeName());
} catch (\Doctrine\DBAL\Exception $exception) {
throw new \RuntimeException(sprintf(
'Could not remove site nodes for site "%s", please ensure the content repository is setup.',
$site->getName()
), 1707302419, $exception);
}

$site->setPrimaryDomain(null);
$this->siteRepository->update($site);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use Neos\Neos\FrontendRouting\DimensionResolution\DelegatingResolver;
use Neos\Neos\FrontendRouting\DimensionResolution\RequestToDimensionSpacePointContext;
use Neos\Neos\FrontendRouting\DimensionResolution\DimensionResolverInterface;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionFailedException;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionMiddleware;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
use Psr\Http\Message\UriInterface;
Expand Down Expand Up @@ -175,7 +176,11 @@ public function matchWithParameters(&$requestPath, RouteParameters $parameters)
return false;
}

$siteDetectionResult = SiteDetectionResult::fromRouteParameters($parameters);
try {
$siteDetectionResult = SiteDetectionResult::fromRouteParameters($parameters);
} catch (SiteDetectionFailedException) {
return false;
}
$resolvedSite = $this->siteRepository->findOneByNodeName($siteDetectionResult->siteNodeName);

if ($resolvedSite === null) {
Expand All @@ -196,7 +201,6 @@ public function matchWithParameters(&$requestPath, RouteParameters $parameters)
// TODO validate dsp == complete (ContentDimensionZookeeper::getAllowedDimensionSubspace()->contains()...)
// if incomplete -> no match + log

$siteDetectionResult = SiteDetectionResult::fromRouteParameters($parameters);
$contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ private function fetchSingle(string $where, array $parameters): DocumentNodeInfo
);
} catch (DBALException $e) {
throw new \RuntimeException(sprintf(
'Failed to load node for query "%s": %s',
'Failed to fetch a node, please ensure the projection is setup. Query "%s". %s',
$where,
$e->getMessage()
), 1599664746, $e);
Expand Down Expand Up @@ -273,7 +273,7 @@ private function fetchMultiple(string $where, array $parameters): DocumentNodeIn
);
} catch (DBALException $e) {
throw new \RuntimeException(sprintf(
'Failed to load node for query "%s": %s',
'Failed to fetch multiple nodes, please ensure the projection is setup. Query "%s". %s',
$where,
$e->getMessage()
), 1683808640, $e);
Expand Down Expand Up @@ -314,7 +314,7 @@ public function disableCache(): void
public function getDescendantsOfNode(DocumentNodeInfo $node): DocumentNodeInfos
{
return $this->fetchMultiple(
'dimensionSpacePointHash = :dimensionSpacePointHash
'dimensionSpacePointHash = :dimensionSpacePointHash
AND nodeAggregateIdPath LIKE :childNodeAggregateIdPathPrefix',
[
'dimensionSpacePointHash' => $node->getDimensionSpacePointHash(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Neos\Neos\FrontendRouting\SiteDetection;

/**
* This error will be thrown in {@see SiteDetectionResult::fromRequest()}
*/
class SiteDetectionFailedException extends \RuntimeException
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
return $handler->handle($request);
}

// doctrine is running and we could fetch a site. This makes no promise if the content repository is set up.
$siteDetectionResult = SiteDetectionResult::create($site->getNodeName(), $site->getConfiguration()->contentRepositoryId);
return $handler->handle($siteDetectionResult->storeInRequest($request));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ public static function create(
* Helper to retrieve the previously resolved Site and ContentRepository instance.
*
* @param ServerRequestInterface $request
* @return static
* @throws SiteDetectionFailedException This error will be thrown if a request is passed
* where the site detection middleware did not store a site in.
* This is likely the case if the request is a mock or
* if no site entity exists because Neos was not setup.
*
* @api
*/
public static function fromRequest(ServerRequestInterface $request): self
Expand All @@ -52,19 +56,21 @@ public static function fromRequest(ServerRequestInterface $request): self
return self::fromRouteParameters($routeParameters);
}

/**
* @throws SiteDetectionFailedException
*/
public static function fromRouteParameters(RouteParameters $routeParameters): self
{
$siteNodeName = $routeParameters->getValue(self::ROUTINGPARAMETER_SITENODENAME);
$contentRepositoryId = $routeParameters->getValue(self::ROUTINGPARAMETER_CONTENTREPOSITORYID);

if ($siteNodeName === null || $contentRepositoryId === null) {
throw new \RuntimeException(
if (!is_string($siteNodeName) || !is_string($contentRepositoryId)) {
throw new SiteDetectionFailedException(
'Current site and content repository could not be extracted from the Request.'
. ' The SiteDetectionMiddleware was not able to determine the site!'
. ' The SiteDetectionMiddleware was not able to determine the site!',
1699459565
);
}
assert(is_string($siteNodeName));
assert(is_string($contentRepositoryId));
return new self(
SiteNodeName::fromString($siteNodeName),
ContentRepositoryId::fromString($contentRepositoryId)
Expand Down
24 changes: 0 additions & 24 deletions Neos.Neos/Classes/Routing/Exception/NoHomepageException.php

This file was deleted.

11 changes: 9 additions & 2 deletions Neos.Neos/Classes/Service/EditorContentStreamZookeeper.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use Neos\Flow\Security\Policy\Role;
use Neos\Neos\Domain\Model\User;
use Neos\Neos\Domain\Service\WorkspaceNameBuilder;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionFailedException;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
use Neos\Party\Domain\Service\PartyService;

Expand Down Expand Up @@ -95,7 +96,11 @@ public function relayEditorAuthentication(Authentication\TokenInterface $token):
// we might be in testing context
return;
}
$siteDetectionResult = SiteDetectionResult::fromRequest($requestHandler->getHttpRequest());
try {
$siteDetectionResult = SiteDetectionResult::fromRequest($requestHandler->getHttpRequest());
} catch (SiteDetectionFailedException) {
return;
}
$contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId);

$isEditor = false;
Expand Down Expand Up @@ -131,8 +136,10 @@ public function relayEditorAuthentication(Authentication\TokenInterface $token):
return;
}

/** @var Workspace $baseWorkspace */
$baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive());
if (!$baseWorkspace) {
return;
}
$editorsNewContentStreamId = ContentStreamId::create();
$contentRepository->handle(
CreateWorkspace::create(
Expand Down
73 changes: 46 additions & 27 deletions Neos.Neos/Classes/View/FusionExceptionView.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\FusionService;
use Neos\Neos\Domain\Service\SiteNodeUtility;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionFailedException;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;

class FusionExceptionView extends AbstractView
Expand Down Expand Up @@ -91,7 +92,7 @@ class FusionExceptionView extends AbstractView
protected DomainRepository $domainRepository;

/**
* @return string
* @return mixed
* @throws \Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException
* @throws \Neos\Fusion\Exception
* @throws \Neos\Neos\Domain\Exception
Expand All @@ -107,7 +108,12 @@ public function render()

$httpRequest = $requestHandler->getHttpRequest();

$siteDetectionResult = SiteDetectionResult::fromRequest($httpRequest);
try {
$siteDetectionResult = SiteDetectionResult::fromRequest($httpRequest);
} catch (SiteDetectionFailedException) {
return $this->renderErrorWelcomeScreen();
}

$contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId);
$fusionExceptionViewInternals = $this->contentRepositoryRegistry->buildService(
$siteDetectionResult->contentRepositoryId,
Expand All @@ -128,6 +134,10 @@ public function render()
);
}

if (!$currentSiteNode) {
return $this->renderErrorWelcomeScreen();
}

$request = ActionRequest::fromHttpRequest($httpRequest);
$request->setControllerPackageKey('Neos.Neos');
$request->setFormat('html');
Expand All @@ -144,32 +154,27 @@ public function render()
$securityContext = $this->objectManager->get(SecurityContext::class);
$securityContext->setRequest($request);

if ($currentSiteNode) {
$fusionRuntime = $this->getFusionRuntime($currentSiteNode, $controllerContext);

$this->setFallbackRuleFromDimension($dimensionSpacePoint);

$fusionRuntime->pushContextArray(array_merge(
$this->variables,
[
'node' => $currentSiteNode,
'documentNode' => $currentSiteNode,
'site' => $currentSiteNode,
'editPreviewMode' => null
]
));

try {
$output = $fusionRuntime->render('error');
return $this->extractBodyFromOutput($output);
} catch (RuntimeException $exception) {
throw $exception->getPrevious() ?: $exception;
} finally {
$fusionRuntime->popContext();
}
$fusionRuntime = $this->getFusionRuntime($currentSiteNode, $controllerContext);

$this->setFallbackRuleFromDimension($dimensionSpacePoint);

$fusionRuntime->pushContextArray(array_merge(
$this->variables,
[
'node' => $currentSiteNode,
'documentNode' => $currentSiteNode,
'site' => $currentSiteNode
]
));

try {
$output = $fusionRuntime->render('error');
return $this->extractBodyFromOutput($output);
} catch (RuntimeException $exception) {
throw $exception->getPrevious() ?: $exception;
} finally {
$fusionRuntime->popContext();
}

return '';
}

/**
Expand Down Expand Up @@ -219,4 +224,18 @@ protected function getFusionRuntime(
}
return $this->fusionRuntime;
}

private function renderErrorWelcomeScreen(): mixed
{
// in case no neos site being there or no site node we cannot continue with the fusion exception view,
// as we wouldn't know the site and cannot get the site's root.fusion
// instead we render the welcome screen directly
$view = \Neos\Fusion\View\FusionView::createWithOptions([
'fusionPath' => 'Neos/Fusion/NotFoundExceptions',
'fusionPathPatterns' => ['resource://Neos.Neos/Private/Fusion/Error/Root.fusion'],
'enableContentCache' => false,
]);
$view->assignMultiple($this->variables);
return $view->render();
}
}
3 changes: 1 addition & 2 deletions Neos.Neos/Classes/View/FusionView.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ public function render(): string|ResponseInterface
$fusionRuntime->pushContextArray([
'node' => $currentNode,
'documentNode' => $this->getClosestDocumentNode($currentNode) ?: $currentNode,
'site' => $currentSiteNode,
'editPreviewMode' => $this->variables['editPreviewMode'] ?? null
'site' => $currentSiteNode
]);
try {
$output = $fusionRuntime->render($this->fusionPath);
Expand Down
7 changes: 1 addition & 6 deletions Neos.Neos/Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -471,18 +471,13 @@ Neos:
- Neos\Flow\Persistence\Doctrine\Exception\DatabaseException
- Neos\Flow\Persistence\Doctrine\Exception\DatabaseConnectionException
- Neos\Flow\Persistence\Doctrine\Exception\DatabaseStructureException
options: &welcomeTemplate
options:
viewClassName: Neos\Fusion\View\FusionView
viewOptions:
fusionPath: 'Neos/Fusion/DatabaseConnectionExceptions'
fusionPathPatterns: ['resource://Neos.Neos/Private/Fusion/Error/Root.fusion']
enableContentCache: false
templatePathAndFilename: ~

noHomepageException:
matchingExceptionClassNames:
- Neos\Neos\Routing\Exception\NoHomepageException
options: *welcomeTemplate
debugger:
ignoredClasses:
Neos\\Neos\\Domain\\Service\\ContentContextFactory: true
Expand Down
Loading

0 comments on commit 116b292

Please sign in to comment.