Skip to content

Commit

Permalink
Merge pull request #19 from borschphp/optims
Browse files Browse the repository at this point in the history
Rework on error handling
  • Loading branch information
debuss committed Apr 13, 2024
2 parents ec77496 + b20434e commit 6cc262d
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 141 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Created by https://www.toptal.com/developers/gitignore/api/phpstorm,phpunit,composer,dotenv
# Edit at https://www.toptal.com/developers/gitignore?templates=phpstorm,phpunit,composer,dotenv

index.php
./index.php

### Composer ###
composer.phar
/vendor/

### Cache ###
/storage/cache/views/*.php
/storage/cache/logs/*.log

# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
Expand Down
2 changes: 1 addition & 1 deletion .lando.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Lando configuration file, used to spin up a local dev server.
# It uses:
# - PHP 8.0
# - PHP 8.1
# - MariaDb (default to 10.3)
# - PHPMyAdmin
# For more information about Lando, see https://docs.lando.dev
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
"borschphp/chef": "^1",
"laminas/laminas-diactoros": "^3",
"monolog/monolog": "^2",
"vlucas/phpdotenv": "^v5.1"
"vlucas/phpdotenv": "^v5.1",
"filp/whoops": "^2",
"borschphp/template": "^0",
"latte/latte": "^3"
},
"require-dev": {
"pestphp/pest": "^2.6"
Expand Down
21 changes: 8 additions & 13 deletions config/container.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<?php

use App\{Formatter\HtmlFormatter,
Handler\PeoplesHandler,
use App\{Handler\PeoplesHandler,
Listener\MonologListener,
Middleware\ErrorHandlerMiddleware,
Repository\PeopleRepositoryInterface,
Repository\SQLitePeopleRepository,
Template\BasicTemplateEngine};
Template\LatteEngine};
use Borsch\{Application\App,
Application\ApplicationInterface,
Container\Container,
RequestHandler\ApplicationRequestHandlerInterface,
RequestHandler\RequestHandler,
Router\FastRouteRouter,
Router\RouterInterface};
Router\RouterInterface,
Template\TemplateRendererInterface};
use Laminas\Diactoros\ServerRequestFactory;
use Monolog\{Handler\StreamHandler, Logger};
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -60,8 +60,7 @@

$container
->set(ErrorHandlerMiddleware::class)
->addMethod('addListener', [$container->get(MonologListener::class)])
->addMethod('setFormatter', [$container->get(HtmlFormatter::class)]);
->addMethod('addListener', [$container->get(MonologListener::class)]);

/*
* Routes Handlers definitions
Expand Down Expand Up @@ -105,12 +104,8 @@
* Template engine
*/

$container->set(
BasicTemplateEngine::class,
fn() => (new BasicTemplateEngine())
->setTemplateDir(storage_path('views'))
->setCacheDir(cache_path('views'))
->useCache(isProduction())
)->cache(true);
$container
->set(TemplateRendererInterface::class, LatteEngine::class)
->cache(true);

return $container;
5 changes: 4 additions & 1 deletion config/routes.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

use App\Handler\{HomeHandler, PeoplesHandler};
use App\Handler\{HealthCheckHandler, HomeHandler, PeoplesHandler};
use Borsch\Application\App;

/**
Expand All @@ -16,4 +16,7 @@
PeoplesHandler::class,
'peoples'
);

// Health checks
$app->get('/healthcheck', HealthCheckHandler::class, 'healthcheck');
};
7 changes: 7 additions & 0 deletions src/Exception/ProblemDetailsException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace App\Exception;

use Exception;

class ProblemDetailsException extends Exception {}
35 changes: 27 additions & 8 deletions src/Formatter/HtmlFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace App\Formatter;

use App\Template\LatteEngine;
use Psr\Http\Message\ResponseInterface;
use Throwable;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run as Whoops;

/**
* Class HtmlFormatter
Expand All @@ -12,18 +15,34 @@
class HtmlFormatter
{

public function __construct()
{
}

public function __invoke(ResponseInterface $response, Throwable $throwable): ResponseInterface
{
$response->getBody()->write(sprintf(
'<strong>On:</strong> %s<br><strong>Response:</strong> %s %s<br>%s<br><br>%s',
date('c'),
$response->getStatusCode(),
$response->getReasonPhrase(),
!isProduction() ? ('<strong>Error:</strong> '.$throwable->getMessage()) : '',
!isProduction() ? ('<strong>Stacktrace:</strong><br>'.nl2br($throwable->getTraceAsString())) : ''
));
$body = isProduction() ?
$this->getTemplateEngineHandledException($throwable) :
$this->getWhoopsHandledException($throwable);

$response->getBody()->write($body);

return $response
->withHeader('Content-Type', 'text/html');
}

protected function getTemplateEngineHandledException(Throwable $throwable): string
{
return (new LatteEngine())->render('500.tpl');
}

protected function getWhoopsHandledException(Throwable $throwable): string
{
$whoops = new Whoops();
$whoops->allowQuit(false);
$whoops->writeToOutput(false);
$whoops->pushHandler(new PrettyPageHandler);

return $whoops->handleException($throwable);
}
}
65 changes: 54 additions & 11 deletions src/Formatter/JsonFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,72 @@

namespace App\Formatter;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;

/**
* Class JsonFormatter
*
* Returns an RFC 7807 ProblemDetails-like response.
*
* @package App\Formatter
* @see https://datatracker.ietf.org/doc/html/rfc7807
*/
class JsonFormatter
{

public function __invoke(ResponseInterface $response, Throwable $throwable): ResponseInterface
public function __invoke(ResponseInterface $response, Throwable $throwable, RequestInterface $request): ResponseInterface
{
$response->getBody()->write(json_encode(array_filter(
[
'status' => $response->getStatusCode(),
'reason' => $response->getReasonPhrase(),
'message' => $throwable->getMessage(),
'date' => date('c')
],
fn($key) => (!isProduction()) || $key != 'message',
ARRAY_FILTER_USE_KEY
)));
$response
->getBody()
->write(
json_encode(
array_filter([
'type' => $this->getRFCSection($response->getStatusCode()),
'title' => $response->getReasonPhrase(),
'status' => $response->getStatusCode(),
'detail' => $throwable->getMessage(),
'instance' => $request->getUri()->getPath(),
'traceId' => $request->hasHeader('X-Trace-ID') ?
$request->getHeader('X-Trace-ID')[0] :
null
])
)
);

return $response->withHeader('Content-Type', 'application/json');
}

protected function getRFCSection(int $status_code): string
{
$uri = 'https://datatracker.ietf.org/doc/html/rfc7231#section-6';

$uri .= match ($status_code) {
400 => '.5.1',
402 => '.5.2',
403 => '.5.3',
404 => '.5.4',
405 => '.5.5',
406 => '.5.6',
408 => '.5.7',
409 => '.5.8',
410 => '.5.9',
411 => '.5.10',
413 => '.5.11',
414 => '.5.12',
415 => '.5.13',
417 => '.5.14',
426 => '.5.15',
500 => '.6.1',
501 => '.6.2',
502 => '.6.3',
503 => '.6.4',
504 => '.6.5',
505 => '.6.6',
default => ''
};

return $uri;
}
}
24 changes: 24 additions & 0 deletions src/Handler/HealthCheckHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Handler;

use Laminas\Diactoros\Response;
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
use Psr\Http\Server\RequestHandlerInterface;

/**
* Class HealthCheckHandler
* @package App\Handler
*/
class HealthCheckHandler implements RequestHandlerInterface
{

/**
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
return new Response();
}
}
6 changes: 3 additions & 3 deletions src/Handler/HomeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace App\Handler;

use App\Template\BasicTemplateEngine;
use Borsch\Router\RouterInterface;
use Borsch\Template\TemplateRendererInterface;
use Laminas\Diactoros\Response\HtmlResponse;
use Psr\Http\{Message\ResponseInterface, Message\ServerRequestInterface, Server\RequestHandlerInterface};

Expand All @@ -16,11 +16,11 @@ class HomeHandler implements RequestHandlerInterface

/**
* @param RouterInterface $router
* @param BasicTemplateEngine $engine
* @param TemplateRendererInterface $engine
*/
public function __construct(
protected RouterInterface $router,
protected BasicTemplateEngine $engine
protected TemplateRendererInterface $engine
) {}

/**
Expand Down
7 changes: 6 additions & 1 deletion src/Handler/PeoplesHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Handler;

use App\Exception\ProblemDetailsException;
use App\Repository\PeopleRepositoryInterface;
use Laminas\Diactoros\{Response, Response\JsonResponse};
use PDO;
Expand Down Expand Up @@ -45,6 +46,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
*
* @param int|null $id
* @return ResponseInterface
* @throws ProblemDetailsException
*/
protected function getPeoples(?int $id = null): ResponseInterface
{
Expand All @@ -53,7 +55,10 @@ protected function getPeoples(?int $id = null): ResponseInterface
$this->people_repository->getAll();

if (!$peoples) {
return new Response(status: 404);
throw new ProblemDetailsException(
sprintf('Unable to retrieve Peoples with id: %s', $id),
404
);
}

return new JsonResponse($peoples);
Expand Down
16 changes: 9 additions & 7 deletions src/Middleware/ErrorHandlerMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace App\Middleware;

use App\Formatter\HtmlFormatter;
use App\Formatter\JsonFormatter;
use ErrorException;
use Laminas\Diactoros\Response;
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
Expand Down Expand Up @@ -35,9 +37,14 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
} catch (Throwable $throwable) {
$this->callListeners($throwable, $request);

$response = ($this->formatter)(
$formatter = str_starts_with($request->getUri()->getPath(), '/api/') ?
new JsonFormatter() : // ProblemDetails
new HtmlFormatter(); // Whoops

$response = $formatter(
$this->getResponseWithStatusCode($throwable),
$throwable
$throwable,
$request
);
}

Expand All @@ -57,11 +64,6 @@ public function addListener(callable $listener): void
}
}

public function setFormatter(callable $formatter): void
{
$this->formatter = $formatter;
}

/**
* @return void
* @throws ErrorException
Expand Down
Loading

0 comments on commit 6cc262d

Please sign in to comment.