Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
40.00% covered (danger)
40.00%
4 / 10
42.86% covered (danger)
42.86%
3 / 7
100.00% covered (success)
100.00%
3 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
AttributesResolver
100.00% covered (success)
100.00%
23 / 23
40.00% covered (danger)
40.00%
4 / 10
42.86% covered (danger)
42.86%
3 / 7
100.00% covered (success)
100.00%
3 / 3
12.72
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 newAttributeInstance
100.00% covered (success)
100.00%
11 / 11
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Router;
6
7use Celema\Wire\Call;
8use Celema\Wire\CallableResolver;
9use Celema\Wire\Creator;
10use Psr\Container\ContainerInterface as Container;
11use ReflectionAttribute;
12use ReflectionClass;
13use ReflectionFunctionAbstract;
14use ReflectionObject;
15
16/** @internal */
17final class AttributesResolver
18{
19    /** @var list<object> */
20    private readonly array $attributes;
21
22    /** @param list<ReflectionClass<object>|ReflectionFunctionAbstract> $reflectors */
23    public function __construct(
24        array $reflectors,
25        protected readonly ?Container $container,
26    ) {
27        $reflectionAttributes = array_merge(
28            ...array_map(static fn($reflector) => $reflector->getAttributes(), $reflectors),
29        );
30
31        $this->attributes = array_map(
32            $this->newAttributeInstance(...),
33            $reflectionAttributes,
34        );
35    }
36
37    /**
38     * @template T of object
39     * @param ?class-string<T> $filter
40     * @return ($filter is null ? list<object> : list<T>)
41     */
42    public function get(?string $filter = null): array
43    {
44        if ($filter) {
45            return array_values(
46                array_filter($this->attributes, static fn($attribute) => $attribute instanceof $filter),
47            );
48        }
49
50        return $this->attributes;
51    }
52
53    private function newAttributeInstance(ReflectionAttribute $attribute): object
54    {
55        $instance = $attribute->newInstance();
56        $callAttrs = new ReflectionObject($instance)->getAttributes(Call::class);
57
58        if (count($callAttrs) > 0) {
59            $resolver = new CallableResolver(new Creator($this->container));
60
61            // See if the attribute itself has one or more Call attributes. If so,
62            // resolve/autowire the arguments of the method it states and call it.
63            foreach ($callAttrs as $callAttr) {
64                $callAttr = $callAttr->newInstance();
65                $methodToResolve = $callAttr->method;
66
67                /** @var callable $callable */
68                $callable = [$instance, $methodToResolve];
69                $args = $resolver->resolve($callable, $callAttr->args);
70                $callable(...$args);
71            }
72        }
73
74        return $instance;
75    }
76}