Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
BorrowBookHandler
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
2 / 2
3
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handle
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
1 / 1
2
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\Loan\Application\Command;
11
12use AppDemo\Library\Domain\BookRepository;
13use AppDemo\Loan\Domain\Event\LoanCreatedEvent;
14use AppDemo\Loan\Domain\Exception\BookNotAvailableException;
15use AppDemo\Loan\Domain\Loan;
16use AppDemo\Loan\Domain\LoanRepository;
17use AppDemo\Loan\Domain\LoanStatus;
18use DateInterval;
19use Override;
20use Phexium\Application\Command\AbstractCommandHandler;
21use Phexium\Application\Command\CommandHandlerInterface;
22use Phexium\Application\Command\CommandInterface;
23use Phexium\Domain\TypeGuard;
24use Phexium\Plugin\Clock\Port\ClockInterface;
25use Phexium\Plugin\EventBus\Port\EventBusInterface;
26use Phexium\Plugin\IdGenerator\Port\IdGeneratorInterface;
27use Phexium\Plugin\Logger\Port\LoggerInterface;
28
29final class BorrowBookHandler extends AbstractCommandHandler implements CommandHandlerInterface
30{
31    public function __construct(
32        private readonly LoanRepository $loanRepository,
33        private readonly BookRepository $bookRepository,
34        private readonly ClockInterface $clock,
35        private readonly EventBusInterface $eventBus,
36        private readonly IdGeneratorInterface $idGenerator,
37        private readonly LoggerInterface $logger,
38    ) {}
39
40    #[Override]
41    public function handle(CommandInterface $command): void
42    {
43        TypeGuard::that($command)->isInstanceOf(BorrowBookCommand::class);
44
45        $this->logger->info('BorrowBookHandler: Processing command', [
46            'loanId' => $command->loanId->getValue(),
47            'bookId' => $command->bookId->getValue(),
48            'userId' => $command->userId->getValue(),
49        ]);
50
51        // Check book exists and is available
52        $book = $this->bookRepository->getById($command->bookId);
53
54        if (!$book->getStatus()->canBeBorrowed()) {
55            $this->logger->warning('BorrowBookHandler: Book not available for borrowing', [
56                'bookId' => $command->bookId->getValue(),
57                'currentStatus' => $book->getStatus()->value,
58            ]);
59            throw BookNotAvailableException::forBook($command->bookId);
60        }
61
62        // Create loan
63        $borrowedAt = $this->clock->now();
64        $dueAt = $borrowedAt->add(new DateInterval('P'.$command->loanPeriod->getValue().'D'));
65
66        $loan = new Loan(
67            $command->loanId,
68            $command->userId,
69            $command->bookId,
70            $borrowedAt,
71            $dueAt,
72            null,
73            LoanStatus::Active
74        );
75
76        $this->loanRepository->save($loan);
77
78        // Dispatch event to update book status
79        $this->eventBus->dispatch(
80            new LoanCreatedEvent(
81                $this->idGenerator->generate(),
82                $this->clock->now(),
83                $loan
84            )
85        );
86
87        $this->logger->info('BorrowBookHandler: Loan created successfully', [
88            'loanId' => $loan->getId()->getValue(),
89            'bookId' => $loan->getBookId()->getValue(),
90            'userId' => $loan->getUserId()->getValue(),
91        ]);
92    }
93}