Move error handling to dedicated controller instead of middleware
This commit is contained in:
parent
bedbb845ee
commit
0c6fdbe12b
5 changed files with 135 additions and 40 deletions
|
@ -9,6 +9,7 @@
|
||||||
use Shaarli\Config\ConfigManager;
|
use Shaarli\Config\ConfigManager;
|
||||||
use Shaarli\Feed\FeedBuilder;
|
use Shaarli\Feed\FeedBuilder;
|
||||||
use Shaarli\Formatter\FormatterFactory;
|
use Shaarli\Formatter\FormatterFactory;
|
||||||
|
use Shaarli\Front\Controller\Visitor\ErrorController;
|
||||||
use Shaarli\History;
|
use Shaarli\History;
|
||||||
use Shaarli\Http\HttpAccess;
|
use Shaarli\Http\HttpAccess;
|
||||||
use Shaarli\Netscape\NetscapeBookmarkUtils;
|
use Shaarli\Netscape\NetscapeBookmarkUtils;
|
||||||
|
@ -148,6 +149,10 @@ public function build(): ShaarliContainer
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$container['errorHandler'] = function (ShaarliContainer $container): ErrorController {
|
||||||
|
return new ErrorController($container);
|
||||||
|
};
|
||||||
|
|
||||||
return $container;
|
return $container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
namespace Shaarli\Front;
|
namespace Shaarli\Front;
|
||||||
|
|
||||||
use Shaarli\Container\ShaarliContainer;
|
use Shaarli\Container\ShaarliContainer;
|
||||||
use Shaarli\Front\Exception\ShaarliFrontException;
|
|
||||||
use Shaarli\Front\Exception\UnauthorizedException;
|
use Shaarli\Front\Exception\UnauthorizedException;
|
||||||
use Slim\Http\Request;
|
use Slim\Http\Request;
|
||||||
use Slim\Http\Response;
|
use Slim\Http\Response;
|
||||||
|
@ -53,35 +52,12 @@ public function __invoke(Request $request, Response $response, callable $next):
|
||||||
$this->checkOpenShaarli($request, $response, $next);
|
$this->checkOpenShaarli($request, $response, $next);
|
||||||
|
|
||||||
return $next($request, $response);
|
return $next($request, $response);
|
||||||
} catch (ShaarliFrontException $e) {
|
|
||||||
// Possible functional error
|
|
||||||
$this->container->pageBuilder->reset();
|
|
||||||
$this->container->pageBuilder->assign('message', nl2br($e->getMessage()));
|
|
||||||
|
|
||||||
$response = $response->withStatus($e->getCode());
|
|
||||||
|
|
||||||
return $response->write($this->container->pageBuilder->render('error', $this->container->basePath));
|
|
||||||
} catch (UnauthorizedException $e) {
|
} catch (UnauthorizedException $e) {
|
||||||
$returnUrl = urlencode($this->container->environment['REQUEST_URI']);
|
$returnUrl = urlencode($this->container->environment['REQUEST_URI']);
|
||||||
|
|
||||||
return $response->withRedirect($this->container->basePath . '/login?returnurl=' . $returnUrl);
|
return $response->withRedirect($this->container->basePath . '/login?returnurl=' . $returnUrl);
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Unknown error encountered
|
|
||||||
$this->container->pageBuilder->reset();
|
|
||||||
if ($this->container->conf->get('dev.debug', false)) {
|
|
||||||
$this->container->pageBuilder->assign('message', $e->getMessage());
|
|
||||||
$this->container->pageBuilder->assign(
|
|
||||||
'stacktrace',
|
|
||||||
nl2br(get_class($e) .': '. PHP_EOL . $e->getTraceAsString())
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->container->pageBuilder->assign('message', t('An unexpected error occurred.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = $response->withStatus(500);
|
|
||||||
|
|
||||||
return $response->write($this->container->pageBuilder->render('error', $this->container->basePath));
|
|
||||||
}
|
}
|
||||||
|
// Other exceptions are handled by ErrorController
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
45
application/front/controller/visitor/ErrorController.php
Normal file
45
application/front/controller/visitor/ErrorController.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shaarli\Front\Controller\Visitor;
|
||||||
|
|
||||||
|
use Shaarli\Front\Exception\ShaarliFrontException;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller used to render the error page, with a provided exception.
|
||||||
|
* It is actually used as a Slim error handler.
|
||||||
|
*/
|
||||||
|
class ErrorController extends ShaarliVisitorController
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, Response $response, \Throwable $throwable): Response
|
||||||
|
{
|
||||||
|
// Unknown error encountered
|
||||||
|
$this->container->pageBuilder->reset();
|
||||||
|
|
||||||
|
if ($throwable instanceof ShaarliFrontException) {
|
||||||
|
// Functional error
|
||||||
|
$this->assignView('message', nl2br($throwable->getMessage()));
|
||||||
|
|
||||||
|
$response = $response->withStatus($throwable->getCode());
|
||||||
|
} else {
|
||||||
|
// Internal error (any other Throwable)
|
||||||
|
if ($this->container->conf->get('dev.debug', false)) {
|
||||||
|
$this->assignView('message', $throwable->getMessage());
|
||||||
|
$this->assignView(
|
||||||
|
'stacktrace',
|
||||||
|
nl2br(get_class($throwable) .': '. PHP_EOL . $throwable->getTraceAsString())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->assignView('message', t('An unexpected error occurred.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $response->withStatus(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $response->write($this->render('error'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,7 +74,8 @@ public function testMiddlewareExecution(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test middleware execution with controller throwing a known front exception
|
* Test middleware execution with controller throwing a known front exception.
|
||||||
|
* The exception should be thrown to be later handled by the error handler.
|
||||||
*/
|
*/
|
||||||
public function testMiddlewareExecutionWithFrontException(): void
|
public function testMiddlewareExecutionWithFrontException(): void
|
||||||
{
|
{
|
||||||
|
@ -99,16 +100,14 @@ public function testMiddlewareExecutionWithFrontException(): void
|
||||||
});
|
});
|
||||||
$this->container->pageBuilder = $pageBuilder;
|
$this->container->pageBuilder = $pageBuilder;
|
||||||
|
|
||||||
/** @var Response $result */
|
$this->expectException(LoginBannedException::class);
|
||||||
$result = $this->middleware->__invoke($request, $response, $controller);
|
|
||||||
|
|
||||||
static::assertInstanceOf(Response::class, $result);
|
$this->middleware->__invoke($request, $response, $controller);
|
||||||
static::assertSame(401, $result->getStatusCode());
|
|
||||||
static::assertContains('error', (string) $result->getBody());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test middleware execution with controller throwing a not authorized exception
|
* Test middleware execution with controller throwing a not authorized exception
|
||||||
|
* The middle should send a redirection response to the login page.
|
||||||
*/
|
*/
|
||||||
public function testMiddlewareExecutionWithUnauthorizedException(): void
|
public function testMiddlewareExecutionWithUnauthorizedException(): void
|
||||||
{
|
{
|
||||||
|
@ -136,9 +135,10 @@ public function testMiddlewareExecutionWithUnauthorizedException(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test middleware execution with controller throwing a not authorized exception
|
* Test middleware execution with controller throwing a not authorized exception.
|
||||||
|
* The exception should be thrown to be later handled by the error handler.
|
||||||
*/
|
*/
|
||||||
public function testMiddlewareExecutionWithServerExceptionWith(): void
|
public function testMiddlewareExecutionWithServerException(): void
|
||||||
{
|
{
|
||||||
$request = $this->createMock(Request::class);
|
$request = $this->createMock(Request::class);
|
||||||
$request->method('getUri')->willReturnCallback(function (): Uri {
|
$request->method('getUri')->willReturnCallback(function (): Uri {
|
||||||
|
@ -148,9 +148,11 @@ public function testMiddlewareExecutionWithServerExceptionWith(): void
|
||||||
return $uri;
|
return $uri;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$dummyException = new class() extends \Exception {};
|
||||||
|
|
||||||
$response = new Response();
|
$response = new Response();
|
||||||
$controller = function (): void {
|
$controller = function () use ($dummyException): void {
|
||||||
throw new \Exception();
|
throw $dummyException;
|
||||||
};
|
};
|
||||||
|
|
||||||
$parameters = [];
|
$parameters = [];
|
||||||
|
@ -165,12 +167,9 @@ public function testMiddlewareExecutionWithServerExceptionWith(): void
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
|
|
||||||
/** @var Response $result */
|
$this->expectException(get_class($dummyException));
|
||||||
$result = $this->middleware->__invoke($request, $response, $controller);
|
|
||||||
|
|
||||||
static::assertSame(500, $result->getStatusCode());
|
$this->middleware->__invoke($request, $response, $controller);
|
||||||
static::assertContains('error', (string) $result->getBody());
|
|
||||||
static::assertSame('An unexpected error occurred.', $parameters['message']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMiddlewareExecutionWithUpdates(): void
|
public function testMiddlewareExecutionWithUpdates(): void
|
||||||
|
|
70
tests/front/controller/visitor/ErrorControllerTest.php
Normal file
70
tests/front/controller/visitor/ErrorControllerTest.php
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shaarli\Front\Controller\Visitor;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Shaarli\Front\Exception\ShaarliFrontException;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
class ErrorControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use FrontControllerMockHelper;
|
||||||
|
|
||||||
|
/** @var ErrorController */
|
||||||
|
protected $controller;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$this->createContainer();
|
||||||
|
|
||||||
|
$this->controller = new ErrorController($this->container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying error with a ShaarliFrontException: display exception message and use its code for HTTTP code
|
||||||
|
*/
|
||||||
|
public function testDisplayFrontExceptionError(): void
|
||||||
|
{
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$message = 'error message';
|
||||||
|
$errorCode = 418;
|
||||||
|
|
||||||
|
// Save RainTPL assigned variables
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$result = ($this->controller)(
|
||||||
|
$request,
|
||||||
|
$response,
|
||||||
|
new class($message, $errorCode) extends ShaarliFrontException {}
|
||||||
|
);
|
||||||
|
|
||||||
|
static::assertSame($errorCode, $result->getStatusCode());
|
||||||
|
static::assertSame($message, $assignedVariables['message']);
|
||||||
|
static::assertArrayNotHasKey('stacktrace', $assignedVariables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test displaying error with any exception (no debug): only display an error occurred with HTTP 500.
|
||||||
|
*/
|
||||||
|
public function testDisplayAnyExceptionErrorNoDebug(): void
|
||||||
|
{
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
// Save RainTPL assigned variables
|
||||||
|
$assignedVariables = [];
|
||||||
|
$this->assignTemplateVars($assignedVariables);
|
||||||
|
|
||||||
|
$result = ($this->controller)($request, $response, new \Exception('abc'));
|
||||||
|
|
||||||
|
static::assertSame(500, $result->getStatusCode());
|
||||||
|
static::assertSame('An unexpected error occurred.', $assignedVariables['message']);
|
||||||
|
static::assertArrayNotHasKey('stacktrace', $assignedVariables);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue