Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
ReturnBookHandler
100.00% covered (success)
100.00%
27 / 27
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%
26 / 26
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\Loan\Domain\Event\LoanReturnedEvent;
13use AppDemo\Loan\Domain\Exception\LoanNotOwnedByUserException;
14use AppDemo\Loan\Domain\LoanRepository;
15use Override;
16use Phexium\Application\Command\AbstractCommandHandler;
17use Phexium\Application\Command\CommandHandlerInterface;
18use Phexium\Application\Command\CommandInterface;
19use Phexium\Domain\TypeGuard;
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 class ReturnBookHandler extends AbstractCommandHandler implements CommandHandlerInterface
26{
27    public function __construct(
28        private readonly LoanRepository $loanRepository,
29        private readonly EventBusInterface $eventBus,
30        private readonly ClockInterface $clock,
31        private readonly IdGeneratorInterface $idGenerator,
32        private readonly LoggerInterface $logger,
33    ) {}
34
35    #[Override]
36    public function handle(CommandInterface $command): void
37    {
38        TypeGuard::that($command)->isInstanceOf(ReturnBookCommand::class);
39
40        $this->logger->info('ReturnBookHandler: Processing command', [
41            'loanId' => $command->loanId->getValue(),
42            'userId' => $command->userId->getValue(),
43        ]);
44
45        // Get loan
46        $loan = $this->loanRepository->getById($command->loanId);
47
48        // Verify loan belongs to the user
49        if (!$loan->getUserId()->equals($command->userId)) {
50            $this->logger->warning('ReturnBookHandler: Loan not owned by user', [
51                'loanId' => $command->loanId->getValue(),
52                'userId' => $command->userId->getValue(),
53                'loanOwnerId' => $loan->getUserId()->getValue(),
54            ]);
55            throw LoanNotOwnedByUserException::forLoanAndUser($command->loanId, $command->userId);
56        }
57
58        // Mark loan as returned
59        $loan->markAsReturned($command->returnedAt);
60
61        $this->loanRepository->save($loan);
62
63        // Dispatch event to update book status
64        $this->eventBus->dispatch(
65            new LoanReturnedEvent(
66                $this->idGenerator->generate(),
67                $this->clock->now(),
68                $loan
69            )
70        );
71
72        $this->logger->info('ReturnBookHandler: Loan returned successfully', [
73            'loanId' => $loan->getId()->getValue(),
74            'bookId' => $loan->getBookId()->getValue(),
75        ]);
76    }
77}