• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

strictlyPHP / domantra / #4

19 Nov 2025 12:34PM UTC coverage: 96.629% (+0.6%) from 96.032%
#4

push

web-flow
Merge pull request #16 from strictlyPHP/add-dto-cache-handling

add dto item query handling

24 of 24 new or added lines in 6 files covered. (100.0%)

1 existing line in 1 file now uncovered.

258 of 267 relevant lines covered (96.63%)

2.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.12
/src/Query/QueryBus.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace StrictlyPHP\Domantra\Query;
6

7
use StrictlyPHP\Domantra\Domain\AbstractAggregateRoot;
8
use StrictlyPHP\Domantra\Query\Exception\ItemNotFoundException;
9
use StrictlyPHP\Domantra\Query\Handlers\DtoHandlerHandlerInterface;
10
use StrictlyPHP\Domantra\Query\Handlers\PaginatedHandlerInterface;
11
use StrictlyPHP\Domantra\Query\Handlers\SingleHandlerInterface;
12
use StrictlyPHP\Domantra\Query\Response\ModelResponse;
13
use StrictlyPHP\Domantra\Query\Response\PaginatedModelResponse;
14
use StrictlyPHP\Domantra\Query\Response\ResponseInterface;
15

16
class QueryBus implements QueryBusInterface
17
{
18
    /**
19
     * @var array<class-string, SingleHandlerInterface|PaginatedHandlerInterface<mixed>>
20
     */
21
    private array $handlers = [];
22

23
    public function __construct(
24
        private AggregateRootHandler $aggregateRootHandler,
25
        private CachedDtoHandler $cachedDtoHandler
26
    ) {
27
    }
7✔
28

29
    /**
30
     * @param class-string $queryClass
31
     */
32
    public function registerHandler(string $queryClass, SingleHandlerInterface|PaginatedHandlerInterface|DtoHandlerHandlerInterface $handler): void
33
    {
34
        $this->handlers[$queryClass] = $handler;
5✔
35
    }
36

37
    /**
38
     * @throws ItemNotFoundException
39
     */
40
    public function handle(object $query): ResponseInterface
41
    {
42
        $class = get_class($query);
6✔
43
        if (! isset($this->handlers[$class])) {
6✔
44
            throw new \RuntimeException("No handler registered for query: $class");
1✔
45
        }
46
        $handler = $this->handlers[$class];
5✔
47

48
        if ($handler instanceof SingleHandlerInterface) {
5✔
49
            if ($query instanceof \Stringable) {
4✔
50
                return new ModelResponse($this->expandDto($this->aggregateRootHandler->handle($query, $handler)));
3✔
51
            } else {
52
                throw new \RuntimeException(sprintf('Query must implement %s when the return type is %s', \Stringable::class, AbstractAggregateRoot::class));
1✔
53
            }
54
        } elseif ($handler instanceof PaginatedHandlerInterface) {
1✔
55
            $paginatedCollection = $handler->__invoke($query);
1✔
56
            $items = [];
1✔
57
            foreach ($paginatedCollection as $id) {
1✔
58
                $idClass = get_class($id);
1✔
59
                /** @var SingleHandlerInterface $idHandler */
60
                $idHandler = $this->handlers[$idClass];
1✔
61
                $items[] = $this->expandDto(
1✔
62
                    $this->aggregateRootHandler->handle($id, $idHandler)
1✔
63
                );
1✔
64
            }
65

66
            return new PaginatedModelResponse(
1✔
67
                $items,
1✔
68
                $paginatedCollection->getPage(),
1✔
69
                $paginatedCollection->getPerPage(),
1✔
70
                $paginatedCollection->getTotalItems()
1✔
71
            );
1✔
72
        } else {
73
            // We should never reach here. We are doing this to future-proof the code
74
            throw new \RuntimeException(sprintf('Handling failed. handler %s must be an instance of %s or %s', get_class($handler), SingleHandlerInterface::class, PaginatedHandlerInterface::class));
×
75
        }
76
    }
77

78
    protected function expandDto(object $dto): object
79
    {
80
        $expanded = (object) [];
5✔
81

82
        foreach (get_object_vars($dto) as $property => $value) {
5✔
83
            if (is_object($value) && $property !== 'id') {
5✔
84
                $class = get_class($value);
3✔
85
                if (isset($this->handlers[$class])) {
3✔
86
                    $handler = $this->handlers[$class];
3✔
87
                    try {
88
                        if ($handler instanceof DtoHandlerHandlerInterface) {
3✔
89
                            $value = $this->cachedDtoHandler->handle($value, $handler);
1✔
90
                        } elseif ($handler instanceof SingleHandlerInterface) {
2✔
91
                            $value = $this->aggregateRootHandler->handle($value, $handler);
1✔
92
                        } else {
93
                            throw new \RuntimeException(sprintf('Handler %s must be an instance of %s or %s', $class, DtoHandlerHandlerInterface::class, SingleHandlerInterface::class));
3✔
94
                        }
95
                    } catch (ItemNotFoundException $e) {
1✔
UNCOV
96
                        $value = null;
×
97
                    }
98
                }
99
            }
100
            $expanded->$property = $value;
4✔
101
        }
102

103
        return $expanded;
4✔
104
    }
105
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc