Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.14% covered (success)
97.14%
34 / 35
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateBookController
97.14% covered (success)
97.14%
34 / 35
75.00% covered (warning)
75.00%
3 / 4
6
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 showCreateForm
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 createBook
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
 renderFormResponse
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
2.00
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10namespace AppDemo\Library\Presentation\Http;
11
12use AppDemo\Library\Application\Command\CreateBookCommand;
13use AppDemo\Library\Application\Query\CreateBookQuery;
14use AppDemo\Library\Application\Query\CreateBookResponse;
15use AppDemo\Library\Presentation\CreateBookHtmlPresenter;
16use AppDemo\Library\Presentation\Request\CreateBookRequest;
17use AppDemo\Shared\Domain\Interface\SessionServiceInterface;
18use AppDemo\Shared\Presentation\AbstractHttpController;
19use Phexium\Plugin\CommandBus\Port\CommandBusInterface;
20use Phexium\Plugin\IdGenerator\Port\IdGeneratorInterface;
21use Phexium\Plugin\Logger\Port\LoggerInterface;
22use Phexium\Plugin\QueryBus\Port\Exception\UnexpectedQueryResponseException;
23use Phexium\Plugin\QueryBus\Port\QueryBusInterface;
24use Phexium\Presentation\ControllerInterface;
25use Phexium\Presentation\ResponseBuilderInterface;
26use Psr\Http\Message\ResponseInterface;
27use Psr\Http\Message\ServerRequestInterface;
28use Twig\Environment;
29
30final readonly class CreateBookController extends AbstractHttpController implements ControllerInterface
31{
32    public function __construct(
33        private CommandBusInterface $commandBus,
34        private QueryBusInterface $queryBus,
35        private SessionServiceInterface $sessionService,
36        private CreateBookHtmlPresenter $presenter,
37        private Environment $twig,
38        private ResponseBuilderInterface $responseBuilder,
39        private IdGeneratorInterface $idGenerator,
40        private LoggerInterface $logger,
41    ) {}
42
43    public function showCreateForm(
44        ServerRequestInterface $request, // NOSONAR - Slim Framework PSR-15 contract
45        ResponseInterface $response
46    ): ResponseInterface {
47        $this->logger->info('CreateBookController: Showing create book form');
48
49        return $this->renderFormResponse($response);
50    }
51
52    public function createBook(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
53    {
54        $this->logger->info('CreateBookController: Processing request');
55
56        $requestDto = CreateBookRequest::fromHttpRequest($request);
57        $errors = $requestDto->validate();
58
59        if ($errors === []) {
60            $bookId = $this->idGenerator->generate();
61
62            $command = new CreateBookCommand(
63                $bookId,
64                $requestDto->title,
65                $requestDto->author,
66                $requestDto->isbn
67            );
68
69            $this->commandBus->dispatch($command);
70
71            $this->sessionService->addFlashMessage('success', 'Book created successfully!');
72
73            return $this->responseBuilder
74                ->withResponse($response)
75                ->withStatus(302)
76                ->withHeader('Location', '/books/'.$bookId->getValue())
77                ->build()
78            ;
79        }
80
81        $this->logger->warning('CreateBookController: Validation errors', ['errors' => $errors]);
82
83        return $this->renderFormResponse($response, $requestDto->toArray(), $errors);
84    }
85
86    private function renderFormResponse(ResponseInterface $response, array $formValues = [], array $errors = []): ResponseInterface
87    {
88        $query = new CreateBookQuery($formValues, $errors);
89        $queryResponse = $this->queryBus->dispatch($query);
90
91        if (!$queryResponse instanceof CreateBookResponse) {
92            throw UnexpectedQueryResponseException::withExpectedType(CreateBookResponse::class, $queryResponse);
93        }
94        $viewModel = $this->presenter->present($queryResponse)->getViewModel();
95
96        $html = $this->twig->render('CreateBook.html.twig', (array) $viewModel);
97
98        return $this->responseBuilder
99            ->withResponse($response)
100            ->withHtml($html)
101            ->build()
102        ;
103    }
104}