Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.

Commit 8884f41

Browse files
committed
Add error handling
1 parent 8dd3c73 commit 8884f41

File tree

8 files changed

+167
-22
lines changed

8 files changed

+167
-22
lines changed

apps/backoffice/backend/config/services.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ services:
4747
tags:
4848
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
4949

50+
CodelyTv\Shared\Infrastructure\Symfony\ApiExceptionListener:
51+
tags:
52+
- { name: kernel.event_listener, event: kernel.exception, method: onException }
53+
5054
CodelyTv\Shared\Infrastructure\Symfony\BasicHttpAuthMiddleware:
5155
tags:
5256
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

apps/backoffice/frontend/config/services.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ services:
4747
CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections:
4848
arguments: [!tagged codely.database_connection]
4949

50+
CodelyTv\Shared\Infrastructure\Symfony\ApiExceptionListener:
51+
tags:
52+
- { name: kernel.event_listener, event: kernel.exception, method: onException }
53+
5054
CodelyTv\Shared\Infrastructure\Symfony\AddJsonBodyToRequestListener:
5155
tags:
5256
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

apps/mooc/backend/config/services.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ services:
5050
tags:
5151
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
5252

53+
CodelyTv\Shared\Infrastructure\Symfony\ApiExceptionListener:
54+
tags:
55+
- { name: kernel.event_listener, event: kernel.exception, method: onException }
56+
5357

5458
# -- APP DEFINITIONS --
5559
# Command/Query Handlers

src/Shared/Domain/DomainError.php

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace CodelyTv\Shared\Domain;
6+
7+
use DomainException;
8+
9+
abstract class DomainError extends DomainException
10+
{
11+
public function __construct()
12+
{
13+
parent::__construct($this->errorMessage());
14+
}
15+
16+
abstract public function errorCode(): string;
17+
18+
abstract protected function errorMessage(): string;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace CodelyTv\Shared\Infrastructure\Symfony;
6+
7+
use CodelyTv\Shared\Domain\Bus\Command\Command;
8+
use CodelyTv\Shared\Domain\Bus\Command\CommandBus;
9+
use CodelyTv\Shared\Domain\Bus\Query\Query;
10+
use CodelyTv\Shared\Domain\Bus\Query\QueryBus;
11+
use CodelyTv\Shared\Domain\Bus\Query\Response;
12+
use function Lambdish\Phunctional\each;
13+
14+
abstract class ApiController
15+
{
16+
private $queryBus;
17+
private $commandBus;
18+
private $exceptionHandler;
19+
20+
public function __construct(
21+
QueryBus $queryBus,
22+
CommandBus $commandBus,
23+
ApiExceptionsHttpStatusCodeMapping $exceptionHandler
24+
) {
25+
$this->queryBus = $queryBus;
26+
$this->commandBus = $commandBus;
27+
$this->exceptionHandler = $exceptionHandler;
28+
29+
each($this->exceptionRegistrar(), $this->exceptions());
30+
}
31+
32+
abstract protected function exceptions(): array;
33+
34+
protected function ask(Query $query): ?Response
35+
{
36+
return $this->queryBus->ask($query);
37+
}
38+
39+
protected function dispatch(Command $command): void
40+
{
41+
$this->commandBus->dispatch($command);
42+
}
43+
44+
private function exceptionRegistrar(): callable
45+
{
46+
return function ($httpCode, $exception): void {
47+
$this->exceptionHandler->register($exception, $httpCode);
48+
};
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace CodelyTv\Shared\Infrastructure\Symfony;
6+
7+
use CodelyTv\Shared\Domain\DomainError;
8+
use CodelyTv\Shared\Domain\Utils;
9+
use Exception;
10+
use Symfony\Component\HttpFoundation\JsonResponse;
11+
use Symfony\Component\HttpKernel\Event\RequestEvent;
12+
13+
final class ApiExceptionListener
14+
{
15+
private $exceptionHandler;
16+
17+
public function __construct(ApiExceptionsHttpStatusCodeMapping $exceptionHandler)
18+
{
19+
$this->exceptionHandler = $exceptionHandler;
20+
}
21+
22+
public function onException(RequestEvent $event): void
23+
{
24+
$exception = $event->getException();
25+
26+
$event->setResponse(
27+
new JsonResponse(
28+
[
29+
'code' => $this->exceptionCodeFor($exception),
30+
'message' => $exception->getMessage(),
31+
],
32+
$this->exceptionHandler->statusCodeFor(get_class($exception))
33+
)
34+
);
35+
}
36+
37+
private function exceptionCodeFor(Exception $error)
38+
{
39+
$domainErrorClass = DomainError::class;
40+
41+
return $error instanceof $domainErrorClass ? $error->errorCode() : Utils::toSnakeCase(class_basename($error));
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace CodelyTv\Shared\Infrastructure\Symfony;
6+
7+
use InvalidArgumentException;
8+
use Symfony\Component\HttpFoundation\Response;
9+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
10+
use function Lambdish\Phunctional\get;
11+
12+
final class ApiExceptionsHttpStatusCodeMapping
13+
{
14+
private const DEFAULT_STATUS_CODE = Response::HTTP_INTERNAL_SERVER_ERROR;
15+
16+
private $exceptions = [
17+
InvalidArgumentException::class => Response::HTTP_BAD_REQUEST,
18+
NotFoundHttpException::class => Response::HTTP_NOT_FOUND,
19+
];
20+
21+
public function register($exceptionClass, $statusCode): void
22+
{
23+
$this->exceptions[$exceptionClass] = $statusCode;
24+
}
25+
26+
public function exists($exceptionClass): bool
27+
{
28+
return array_key_exists($exceptionClass, $this->exceptions);
29+
}
30+
31+
public function statusCodeFor($exceptionClass): int
32+
{
33+
return get($exceptionClass, $this->exceptions, self::DEFAULT_STATUS_CODE);
34+
}
35+
}

src/Shared/Infrastructure/Symfony/WebController.php

+8-22
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44

55
namespace CodelyTv\Shared\Infrastructure\Symfony;
66

7-
use CodelyTv\Shared\Domain\Bus\Command\Command;
87
use CodelyTv\Shared\Domain\Bus\Command\CommandBus;
9-
use CodelyTv\Shared\Domain\Bus\Query\Query;
108
use CodelyTv\Shared\Domain\Bus\Query\QueryBus;
11-
use CodelyTv\Shared\Domain\Bus\Query\Response;
129
use Symfony\Component\HttpFoundation\RedirectResponse;
1310
use Symfony\Component\HttpFoundation\Request;
1411
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
@@ -17,26 +14,25 @@
1714
use Symfony\Component\Validator\ConstraintViolationListInterface;
1815
use Twig\Environment;
1916

20-
abstract class WebController
17+
abstract class WebController extends ApiController
2118
{
2219
private $twig;
2320
private $router;
2421
private $session;
25-
private $queryBus;
26-
private $commandBus;
2722

2823
public function __construct(
2924
Environment $twig,
3025
RouterInterface $router,
3126
SessionInterface $session,
3227
QueryBus $queryBus,
33-
CommandBus $commandBus
28+
CommandBus $commandBus,
29+
ApiExceptionsHttpStatusCodeMapping $exceptionHandler
3430
) {
35-
$this->twig = $twig;
36-
$this->router = $router;
37-
$this->session = $session;
38-
$this->queryBus = $queryBus;
39-
$this->commandBus = $commandBus;
31+
parent::__construct($queryBus, $commandBus, $exceptionHandler);
32+
33+
$this->twig = $twig;
34+
$this->router = $router;
35+
$this->session = $session;
4036
}
4137

4238
public function render(string $templatePath, array $arguments = []): SymfonyResponse
@@ -67,16 +63,6 @@ public function redirectWithErrors(
6763
return new RedirectResponse($this->router->generate($routeName), 302);
6864
}
6965

70-
public function ask(Query $query): ?Response
71-
{
72-
return $this->queryBus->ask($query);
73-
}
74-
75-
public function dispatch(Command $command): void
76-
{
77-
$this->commandBus->dispatch($command);
78-
}
79-
8066
private function formatFlashErrors(ConstraintViolationListInterface $violations): array
8167
{
8268
$errors = [];

0 commit comments

Comments
 (0)