Skip to content

Commit

Permalink
Merge pull request #64 from msmakouz/feature/errors-handling-improvem…
Browse files Browse the repository at this point in the history
…ents

Adding ExceptionHandlerBootloader, ViewRenderer, error views
  • Loading branch information
butschster authored Jun 23, 2022
2 parents b818c58 + 99a8bca commit 9ad3d54
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 28 deletions.
4 changes: 4 additions & 0 deletions app/locale/ru/messages.en.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
'Welcome to Spiral Framework' => 'Вас приветствует Spiral Framework',
'This view file is located in' => 'Данный шаблон находится в файле',
'and rendered by' => 'и вызван контроллером',
'Access Forbidden' => 'Доступ запрещен',
'Page not found' => 'Страница не найдена',
'Something went wrong' => 'Что-то пошло не так',
'Error code' => 'Код ошибки',
];
23 changes: 15 additions & 8 deletions app/src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App;

use App\Bootloader;
use Spiral\Boot\Bootloader\CoreBootloader;
use Spiral\Bootloader as Framework;
use Spiral\DotEnv\Bootloader as DotEnv;
use Spiral\Framework\Kernel;
Expand All @@ -16,31 +17,38 @@
use Spiral\Stempler\Bootloader as Stempler;
use Spiral\Cycle\Bootloader as CycleBridge;
use Spiral\RoadRunnerBridge\Bootloader as RoadRunnerBridge;
use Spiral\Tokenizer\Bootloader\TokenizerBootloader;
use Spiral\Validation\Bootloader\ValidationBootloader;
use Spiral\Views\Bootloader\ViewsBootloader;

class App extends Kernel
{
protected const SYSTEM = [
CoreBootloader::class,
TokenizerBootloader::class,
DotEnv\DotenvBootloader::class,
];

/*
* List of components and extensions to be automatically registered
* within system container on application start.
*/
protected const LOAD = [

// Logging and exceptions handling
Monolog\MonologBootloader::class,
Bootloader\ExceptionHandlerBootloader::class,

// Application specific logs
Bootloader\LoggingBootloader::class,

// RoadRunner
RoadRunnerBridge\CacheBootloader::class,
RoadRunnerBridge\GRPCBootloader::class,
RoadRunnerBridge\HttpBootloader::class,
RoadRunnerBridge\QueueBootloader::class,
RoadRunnerBridge\RoadRunnerBootloader::class,

// Base extensions
DotEnv\DotenvBootloader::class,
Monolog\MonologBootloader::class,

// Application specific logs
Bootloader\LoggingBootloader::class,

// Core Services
Framework\SnapshotsBootloader::class,
Framework\I18nBootloader::class,
Expand All @@ -54,7 +62,6 @@ class App extends Kernel
// HTTP extensions
Nyholm\NyholmBootloader::class,
Framework\Http\RouterBootloader::class,
Framework\Http\ErrorHandlerBootloader::class,
Framework\Http\JsonPayloadsBootloader::class,
Framework\Http\CookiesBootloader::class,
Framework\Http\SessionBootloader::class,
Expand Down
48 changes: 48 additions & 0 deletions app/src/Bootloader/ExceptionHandlerBootloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace App\Bootloader;

use App\ErrorHandler\ViewRenderer;
use App\Exception\CollisionRenderer;
use Spiral\Boot\AbstractKernel;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Exceptions\ExceptionHandler;
use Spiral\Exceptions\ExceptionHandlerInterface;
use Spiral\Exceptions\Renderer\JsonRenderer;
use Spiral\Exceptions\Reporter\LoggerReporter;
use Spiral\Exceptions\Reporter\SnapshotterReporter;
use Spiral\Http\ErrorHandler\RendererInterface;
use Spiral\Http\Middleware\ErrorHandlerMiddleware\EnvSuppressErrors;
use Spiral\Http\Middleware\ErrorHandlerMiddleware\SuppressErrorsInterface;
use Spiral\Ignition\IgnitionRenderer;

final class ExceptionHandlerBootloader extends Bootloader
{
protected const BINDINGS = [
SuppressErrorsInterface::class => EnvSuppressErrors::class,
RendererInterface::class => ViewRenderer::class,
];

public function init(AbstractKernel $kernel): void
{
$kernel->running(static function (ExceptionHandler $handler): void {
// $handler->addRenderer(new ConsoleRenderer());
$handler->addRenderer(new CollisionRenderer());
$handler->addRenderer(new JsonRenderer());
});
}

public function boot(
ExceptionHandlerInterface $handler,
LoggerReporter $logger,
SnapshotterReporter $snapshotter,
IgnitionRenderer $ignition
): void {
$handler->addReporter($logger);
$handler->addReporter($snapshotter);

$handler->addRenderer($ignition);
}
}
2 changes: 1 addition & 1 deletion app/src/Bootloader/LoggingBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

class LoggingBootloader extends Bootloader
{
public function boot(MonologBootloader $monolog): void
public function init(MonologBootloader $monolog): void
{
// http level errors
$monolog->addHandler(
Expand Down
2 changes: 1 addition & 1 deletion app/src/Bootloader/RoutesBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public function __construct(
protected function globalMiddleware(): array
{
return [
LocaleSelector::class,
ErrorHandlerMiddleware::class,
JsonPayloadMiddleware::class,
HttpCollector::class,
LocaleSelector::class,
];
}

Expand Down
61 changes: 61 additions & 0 deletions app/src/ErrorHandler/ViewRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace App\ErrorHandler;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Spiral\Http\ErrorHandler\RendererInterface;
use Spiral\Http\Header\AcceptHeader;
use Spiral\Views\Exception\ViewException;
use Spiral\Views\ViewsInterface;

class ViewRenderer implements RendererInterface
{
private const GENERAL_VIEW = 'exception/error';
private const VIEW = 'exception/%s';

public function __construct(
private readonly ViewsInterface $views,
private readonly ResponseFactoryInterface $responseFactory
) {
}

public function renderException(Request $request, int $code, string $message): ResponseInterface
{
$acceptItems = AcceptHeader::fromString($request->getHeaderLine('Accept'))->getAll();
if ($acceptItems && $acceptItems[0]->getValue() === 'application/json') {
return $this->renderJson($code, $message);
}

return $this->renderView($code, $message);
}

private function renderJson(int $code, string $message): ResponseInterface
{
$response = $this->responseFactory->createResponse($code);

$response = $response->withHeader('Content-Type', 'application/json; charset=UTF-8');
$response->getBody()->write(\json_encode(['status' => $code, 'error' => $message]));

return $response;
}

private function renderView(int $code, string $message): ResponseInterface
{
$response = $this->responseFactory->createResponse($code);

try {
$view = $this->views->get(\sprintf(self::VIEW, $code));
} catch (ViewException) {
$view = $this->views->get(self::GENERAL_VIEW);
}

$content = $view->render(['code' => $code, 'error' => $message]);
$response->getBody()->write($content);

return $response;
}
}
22 changes: 5 additions & 17 deletions app/src/Exception/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,12 @@
namespace App\Exception;

use Spiral\Exceptions\ExceptionHandler;
use Spiral\Exceptions\Renderer\ConsoleRenderer;
use Spiral\Exceptions\Renderer\JsonRenderer;
use Spiral\Exceptions\Reporter\LoggerReporter;
use Spiral\Exceptions\Reporter\SnapshotterReporter;

/**
* -----------------------------------------------------
* In this file, you can modify the exception handling
* -----------------------------------------------------
*/
final class Handler extends ExceptionHandler
{
public function __construct(
SnapshotterReporter $snapshotterReporter,
LoggerReporter $loggerReporter
) {
parent::__construct();

// $this->addRenderer(new ConsoleRenderer());
$this->addRenderer(new CollisionRenderer());
$this->addRenderer(new JsonRenderer());

$this->addReporter($loggerReporter);
$this->addReporter($snapshotterReporter);
}
}
21 changes: 21 additions & 0 deletions app/views/exception/403.dark.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<extends:layout.base title="[[Access Forbidden]]"/>
<use:element path="embed/links" as="homepage:links"/>

<stack:push name="styles">
<link rel="stylesheet" href="/styles/welcome.css"/>
</stack:push>

<define:body>
<div class="wrapper">
<div class="placeholder">
<img src="/images/403.svg" alt="Error 403" width="300px"/>
<h2>[[Access Forbidden]]</h2>
<homepage:links git="https://github.com/spiral/app" style="font-weight: bold;"/>
<div style="font-size: 12px; margin-top: 10px;">
[[This view file is located in]] <b>app/views/exception/403.dark.php</b>.
</div>
</div>
</div>
</define:body>
21 changes: 21 additions & 0 deletions app/views/exception/404.dark.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<extends:layout.base title="[[Page not found]]"/>
<use:element path="embed/links" as="homepage:links"/>

<stack:push name="styles">
<link rel="stylesheet" href="/styles/welcome.css"/>
</stack:push>

<define:body>
<div class="wrapper">
<div class="placeholder">
<img src="/images/404.svg" alt="Framework Logotype" width="300px"/>
<h2>[[Page not found]]</h2>
<homepage:links git="https://github.com/spiral/app" style="font-weight: bold;"/>
<div style="font-size: 12px; margin-top: 10px;">
[[This view file is located in]] <b>app/views/exception/404.dark.php</b>.
</div>
</div>
</div>
</define:body>
21 changes: 21 additions & 0 deletions app/views/exception/500.dark.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<extends:layout.base title="[[Something went wrong]]"/>
<use:element path="embed/links" as="homepage:links"/>

<stack:push name="styles">
<link rel="stylesheet" href="/styles/welcome.css"/>
</stack:push>

<define:body>
<div class="wrapper">
<div class="placeholder">
<img src="/images/500.svg" alt="Error 500" width="300px"/>
<h2>[[Something went wrong]]</h2>
<homepage:links git="https://github.com/spiral/app" style="font-weight: bold;"/>
<div style="font-size: 12px; margin-top: 10px;">
[[This view file is located in]] <b>app/views/exception/500.dark.php</b>.
</div>
</div>
</div>
</define:body>
21 changes: 21 additions & 0 deletions app/views/exception/error.dark.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<extends:layout.base title="[[Something went wrong]]"/>
<use:element path="embed/links" as="homepage:links"/>

<stack:push name="styles">
<link rel="stylesheet" href="/styles/welcome.css"/>
</stack:push>

<define:body>
<div class="wrapper">
<div class="placeholder">
<img src="/images/logo.svg" alt="Framework Logotype" width="200px"/>
<h2>[[Something went wrong]]. [[Error code]]: {{ $code }}</h2>
<homepage:links git="https://github.com/spiral/app" style="font-weight: bold;"/>
<div style="font-size: 12px; margin-top: 10px;">
[[This view file is located in]] <b>app/views/exception/error.dark.php</b>.
</div>
</div>
</div>
</define:body>
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"require-dev": {
"nunomaduro/collision": "^6.2",
"phpunit/phpunit": "^9.5",
"spiral/testing": "^2.0"
"spiral/testing": "^2.0",
"spiral-packages/ignition-bridge": "^1.0"
},
"scripts": {
"post-create-project-cmd": [
Expand Down
3 changes: 3 additions & 0 deletions public/images/403.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/404.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9ad3d54

Please sign in to comment.