Skip to content

Instantly share code, notes, and snippets.

@bmack
Last active June 4, 2021 12:53
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save bmack/06bb00bfee09b7c5b79fd981c64fa9c9 to your computer and use it in GitHub Desktop.
Save bmack/06bb00bfee09b7c5b79fd981c64fa9c9 to your computer and use it in GitHub Desktop.
Local Error Handler for TYPO3
<?php
namespace B13\AnyProject\PageErrorHandler;
/*
* This file is part of a b13 extension.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Error\PageErrorHandler\PageContentErrorHandler;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
use TYPO3\CMS\Core\LinkHandling\LinkService;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
use TYPO3\CMS\Core\Service\DependencyOrderingService;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Http\RequestHandler;
/**
* Does not execute a CURL request for internal pages. Just set it up in an extension
* and refer to it in your Site Configuration (PHP Error Handler)
*/
class LocalPageErrorHandler extends PageContentErrorHandler
{
/**
* @param ServerRequestInterface $request
* @param string $message
* @param array $reasons
* @return ResponseInterface
* @throws \RuntimeException
*/
public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
{
$targetDetails = $this->resolveDetails($this->errorHandlerConfiguration['errorContentSource']);
if ($targetDetails['type'] !== 'page' && $targetDetails['type'] !== 'url') {
throw new \InvalidArgumentException('PageContentErrorHandler can only handle TYPO3 urls of types "page" or "url"', 1522826609);
}
if ($targetDetails['type'] === 'page') {
$response = $this->buildSubRequest($request, (int)$targetDetails['pageuid']);
return $response->withStatusCode($this->statusCode);
}
if ($targetDetails['type'] !== 'url') {
return new HtmlResponse('Big fail', $this->statusCode);
}
$resolvedUrl = $targetDetails['url'];
try {
$content = null;
$report = [];
if ($resolvedUrl !== (string)$request->getUri()) {
$content = GeneralUtility::getUrl($resolvedUrl, 0, null, $report);
if ($content === false && ((int)$report['error'] === -1 || (int)$report['error'] > 200)) {
throw new \RuntimeException('Error handler could not fetch error page "' . $resolvedUrl . '", reason: ' . $report['message'], 1544172838);
}
}
} catch (InvalidRouteArgumentsException | SiteNotFoundException $e) {
$content = 'Invalid error handler configuration: ' . $this->errorHandlerConfiguration['errorContentSource'];
}
return new HtmlResponse($content, $this->statusCode);
}
/**
* @param ServerRequestInterface $request
* @param int $pageId
* @return ResponseInterface
* @throws SiteNotFoundException
* @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
* @throws \TYPO3\CMS\Core\Exception
*/
protected function buildSubRequest(ServerRequestInterface $request, int $pageId): ResponseInterface
{
$site = $request->getAttribute('site', null);
if (!$site instanceof Site) {
$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
$request = $request->withAttribute('site', $site);
}
$request = $request->withQueryParams(['id' => $pageId]);
$dispatcher = $this->buildDispatcher();
return $dispatcher->handle($request);
}
/**
* @param string $typoLinkUrl
* @return array
*/
protected function resolveDetails(string $typoLinkUrl): array
{
$linkService = GeneralUtility::makeInstance(LinkService::class);
return $linkService->resolve($typoLinkUrl);
}
/**
* @return MiddlewareDispatcher
* @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException
* @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
* @throws \TYPO3\CMS\Core\Exception
*/
protected function buildDispatcher()
{
$requestHandler = GeneralUtility::makeInstance(RequestHandler::class);
$resolver = new MiddlewareStackResolver(
GeneralUtility::makeInstance(PackageManager::class),
GeneralUtility::makeInstance(DependencyOrderingService::class),
GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core')
);
$middlewares = $resolver->resolve('frontend');
return new MiddlewareDispatcher($requestHandler, $middlewares);
}
}
@josefglatz
Copy link

josefglatz commented Apr 16, 2020

Open updated version with support for TYPO3 10.4 LTS (based on the revision 3 from @bmack) [❤️ x-marks-the-spot ❤️]

@dogawaf
Copy link

dogawaf commented Sep 22, 2020

Fetching the error page with a subrequest should be integrated to core's PageContentErrorHandler.
GeneralUtility must be avoided in the error handling process, because it can quickly saturate php-fpm pool.

@josefglatz
Copy link

Fetching the error page with a subrequest should be integrated to core's PageContentErrorHandler.
GeneralUtility must be avoided in the error handling process, because it can quickly saturate php-fpm pool.

@dogawf do you have another example like you described it?

@dogawaf
Copy link

dogawaf commented Nov 5, 2020

@josefglatz I'm not sure I get correctly your question, but I will try to answer.

GeneralUtility::getUrl makes a curl (or file_get_content) request, which will open a new child process on the website's php-fpm pool.
If the request takes time to fulfill (because of many reasons), the php-fpm pool can be quickly saturated.
In the case of the PageContentErrorHandler, fetching the 404 page should be done in the same php process.

@sypets
Copy link

sypets commented Nov 18, 2020

@dogawaf

So you are saying the core PageContentErrorHandler should be fixed to not use GeneralUtility::getUrl() too - as in the code above? Looks like a good idea.

@dogawaf
Copy link

dogawaf commented Nov 21, 2020

Yes, GeneralUtility::getUrl() should not be used to fetch a local page.

@TheJotob
Copy link

TheJotob commented Jun 4, 2021

I get an endless loop with this error handler in the following configuration:

  1. I have a default language "English" and a second language "English (US)" with a fallbackMode: fallback
  2. I have a page which is disabled in the default language and has no translation for the US-Language.

Expected behavior:
When I open the en-us Version of the page I get a 404 error and the page shows the content of my 404-Page.

Actual behavior:
The page takes ages to load and I get a "Tried to allocate xxx bytes of memory ..."-Error.

I couldn't fully break down the problem or fix it. But I found out, that the Error Handler gets called over and over again. I guess the Request, that is dispatched by the error handler calls the same page again and again.

I'm using TYPO3 10.4.16

@bmack Otherwise thanks a lot for this error handler. It's way more elegant, than the core solution IMHO 🥇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment