Lines 100.00% 27 / 27
Methods 100.00% 6 / 6
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 scanMessageDirectory 100.00% 6 / 6 100.00% 1 / 1 5
 fromMessageClasses 100.00% 17 / 17 100.00% 1 / 1 3
 findTagForClass 100.00% 1 / 1 100.00% 1 / 1 1
 findClassForTag 100.00% 1 / 1 100.00% 1 / 1 1
 listMessageClasses 100.00% 1 / 1 100.00% 1 / 1 1
15final readonly class IpcMessageCatalog
16{
17    private const string MESSAGE_DIRECTORY = __DIR__ . '/../Message';
18
19    private const string MESSAGE_NAMESPACE = 'Stewart\\Runtime\\Ipc\\Message\\';
20
21    /**
22     * @param array<class-string<BrokerMessage|WorkerMessage>, string> $tagsByClass
23     * @param array<string, class-string<BrokerMessage|WorkerMessage>> $classesByTag
24     */
25    private function __construct(
26        private array $tagsByClass,
27        private array $classesByTag,
28    ) {}
29
30    /** @throws TransportException */
31    public static function scanMessageDirectory(): self
32    {
33        $messageClasses = [];
34
35        foreach (Finder::create()->files()->in(self::MESSAGE_DIRECTORY)->depth(0)->name('*.php')->sortByName() as $file) {
36            $class = self::MESSAGE_NAMESPACE . $file->getBasename('.php');
37
38            if (!interface_exists($class) && (is_subclass_of($class, BrokerMessage::class) || is_subclass_of($class, WorkerMessage::class))) {
39                $messageClasses[] = $class;
40            }
41        }
42
43        return self::fromMessageClasses($messageClasses);
44    }
45
46    /**
47     * @param iterable<class-string<BrokerMessage|WorkerMessage>> $messageClasses
48     *
49     * @throws TransportException
50     */
51    public static function fromMessageClasses(iterable $messageClasses): self
52    {
53        $classesByTag = [];
54
55        foreach ($messageClasses as $class) {
56            $attribute = new ReflectionClass($class)->getAttributes(IpcMessage::class)[0] ?? throw TransportException::createForReason(
57                TransportError::MessageTagMissing,
58                messageClass: $class,
59            );
60            $tag = $attribute->newInstance()->tag;
61
62            if (isset($classesByTag[$tag])) {
63                throw TransportException::createForReason(
64                    TransportError::MessageTagDuplicated,
65                    tag: $tag,
66                    firstClass: $classesByTag[$tag],
67                    secondClass: $class,
68                );
69            }
70
71            $classesByTag[$tag] = $class;
72        }
73
74        ksort($classesByTag);
75
76        return new self(array_flip($classesByTag), $classesByTag);
77    }
78
79    public function findTagForClass(string $messageClass): ?string
80    {
81        return $this->tagsByClass[$messageClass] ?? null;
82    }
83
84    /** @return class-string<BrokerMessage|WorkerMessage>|null */
85    public function findClassForTag(string $tag): ?string
86    {
87        return $this->classesByTag[$tag] ?? null;
88    }
89
90    /** @return list<class-string<BrokerMessage|WorkerMessage>> */
91    public function listMessageClasses(): array
92    {
93        return array_values($this->classesByTag);
94    }
95}