Lines 100.00% 36 / 36
Methods 100.00% 5 / 5
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 process 100.00% 9 / 9 100.00% 1 / 1 8
 bindStoreArguments 100.00% 7 / 7 100.00% 1 / 1 5
 storeReferenceFor 100.00% 15 / 15 100.00% 1 / 1 6
 resolvePeerAppId 100.00% 3 / 3 100.00% 1 / 1 2
 isWritable 100.00% 2 / 2 100.00% 1 / 1 2
23final readonly class StoreAttributesPass implements CompilerPassInterface
24{
25    public function process(ContainerBuilder $container): void
26    {
27        foreach ($container->getDefinitions() as $definition) {
28            $class = $definition->getClass();
29
30            if (
31                $class === null
32                || !$definition->isAutowired()
33                || $definition->isAbstract()
34                || $definition->isSynthetic()
35                || $definition->getFactory() !== null
36                || !class_exists($class)
37            ) {
38                continue;
39            }
40
41            $this->bindStoreArguments($container, $definition, $class);
42        }
43    }
44
45    /** @param class-string $class */
46    private function bindStoreArguments(ContainerBuilder $container, Definition $definition, string $class): void
47    {
48        $parameters = new ReflectionClass($class)->getConstructor()?->getParameters() ?? [];
49        $arguments = $definition->getArguments();
50
51        foreach ($parameters as $parameter) {
52            // Whatever services.php or stewart.yaml set explicitly still wins.
53            if (\array_key_exists('$' . $parameter->getName(), $arguments) || \array_key_exists($parameter->getPosition(), $arguments)) {
54                continue;
55            }
56
57            $store = $this->storeReferenceFor($container, $class, $parameter);
58
59            if ($store !== null) {
60                $definition->setArgument('$' . $parameter->getName(), $store);
61            }
62        }
63    }
64
65    private function storeReferenceFor(ContainerBuilder $container, string $class, ReflectionParameter $parameter): ?Reference
66    {
67        $writable = $this->isWritable($parameter);
68
69        if ($parameter->getAttributes(GlobalStore::class) !== []) {
70            return new Reference($writable ? AppContainerBuilder::GLOBAL_STORE : AppContainerBuilder::READ_ONLY_GLOBAL_STORE);
71        }
72
73        $peer = ($parameter->getAttributes(PeerStore::class)[0] ?? null)?->newInstance();
74
75        if ($peer === null) {
76            return null;
77        }
78
79        if ($writable) {
80            throw ContainerException::createForReason(ContainerError::PeerStoreWritable, class: $class, parameter: $parameter->getName());
81        }
82
83        $peerAppId = $this->resolvePeerAppId($class, $parameter, $peer);
84        $id = AppContainerBuilder::buildPeerStoreServiceId($peerAppId);
85
86        if (!$container->hasDefinition($id)) {
87            $container->setDefinition($id, new Definition(ReadableStore::class)
88                ->setFactory([new Reference(AppContainerBuilder::STORES), 'openPeerStoreForReading'])
89                ->setArguments([$peerAppId]));
90        }
91
92        return new Reference($id);
93    }
94
95    /** @throws ContainerException */
96    private function resolvePeerAppId(string $class, ReflectionParameter $parameter, PeerStore $peer): AppId
97    {
98        try {
99            return new AppId($peer->appId);
100        } catch (IdentifierException $e) {
101            throw ContainerException::createForReason(ContainerError::PeerStoreAppIdInvalid, $e, class: $class, parameter: $parameter->getName());
102        }
103    }
104
105    private function isWritable(ReflectionParameter $parameter): bool
106    {
107        $type = $parameter->getType();
108
109        return $type instanceof ReflectionNamedType && $type->getName() === Store::class;
110    }
111}