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