AttributeCollector::getRoutes()   B
last analyzed

Complexity

Conditions 8
Paths 36

Size

Total Lines 100
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 59
dl 0
loc 100
rs 7.6501
c 1
b 0
f 0
cc 8
nc 36
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Http\Routing\Collector;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use ReflectionException;
18
use Valkyrja\Attribute\Attributes;
19
use Valkyrja\Attribute\Contract\Attributes as AttributeContract;
20
use Valkyrja\Http\Middleware\Contract\RouteDispatchedMiddleware;
21
use Valkyrja\Http\Middleware\Contract\RouteMatchedMiddleware;
22
use Valkyrja\Http\Middleware\Contract\SendingResponseMiddleware;
23
use Valkyrja\Http\Middleware\Contract\TerminatedMiddleware;
24
use Valkyrja\Http\Middleware\Contract\ThrowableCaughtMiddleware;
25
use Valkyrja\Http\Routing\Attribute\Parameter;
26
use Valkyrja\Http\Routing\Attribute\Route;
27
use Valkyrja\Http\Routing\Attribute\Route\Middleware;
28
use Valkyrja\Http\Routing\Attribute\Route\RequestMethod;
29
use Valkyrja\Http\Routing\Attribute\Route\RequestStruct;
30
use Valkyrja\Http\Routing\Attribute\Route\ResponseStruct;
31
use Valkyrja\Http\Routing\Collector\Contract\Collector as Contract;
32
use Valkyrja\Http\Routing\Data\Contract\Route as RouteContract;
33
use Valkyrja\Http\Routing\Exception\InvalidArgumentException;
34
use Valkyrja\Http\Routing\Processor\Contract\Processor as ProcessorContract;
35
use Valkyrja\Http\Routing\Processor\Processor;
36
use Valkyrja\Http\Struct\Request\Contract\RequestStruct as RequestStructContract;
37
use Valkyrja\Http\Struct\Response\Contract\ResponseStruct as ResponseStructContract;
38
use Valkyrja\Reflection\Contract\Reflection as ReflectionContract;
39
use Valkyrja\Reflection\Reflection;
40
41
use function array_column;
42
43
/**
44
 * Class AttributeCollector.
45
 *
46
 * @author Melech Mizrachi
47
 */
48
class AttributeCollector implements Contract
49
{
50
    public function __construct(
51
        protected AttributeContract $attributes = new Attributes(),
52
        protected ReflectionContract $reflection = new Reflection(),
53
        protected ProcessorContract $processor = new Processor()
54
    ) {
55
    }
56
57
    /**
58
     * @inheritDoc
59
     *
60
     * @throws ReflectionException
61
     */
62
    #[Override]
63
    public function getRoutes(string ...$classes): array
64
    {
65
        $routes     = [];
66
        $attributes = [];
67
68
        foreach ($classes as $class) {
69
            /** @var Route[] $memberAttributes */
70
            $memberAttributes = $this->attributes->forClassMembers($class, Route::class);
71
72
            // Iterate through all the members' attributes
73
            foreach ($memberAttributes as $routeAttribute) {
74
                $routeDispatch = $routeAttribute->getDispatch();
75
76
                $method              = $routeDispatch->getMethod();
77
                $routeParameters     = $this->attributes->forMethod($class, $method, Parameter::class);
78
                $routeMiddleware     = $this->attributes->forMethod($class, $method, Middleware::class);
79
                $routeRequestStruct  = $this->attributes->forMethod($class, $method, RequestStruct::class);
80
                $routeResponseStruct = $this->attributes->forMethod($class, $method, ResponseStruct::class);
81
                $requestMethods      = $this->attributes->forMethod($class, $method, RequestMethod::class);
82
83
                /** @var class-string[] $middlewareClasses */
84
                $middlewareClasses = array_column(
85
                    $routeMiddleware,
86
                    'name'
87
                );
88
89
                foreach ($middlewareClasses as $middlewareClass) {
90
                    $routeAttribute = match (true) {
91
                        is_a($middlewareClass, RouteMatchedMiddleware::class, true)    => $routeAttribute->withAddedRouteMatchedMiddleware(
92
                            $middlewareClass
93
                        ),
94
                        is_a($middlewareClass, RouteDispatchedMiddleware::class, true) => $routeAttribute->withAddedRouteDispatchedMiddleware(
95
                            $middlewareClass
96
                        ),
97
                        is_a($middlewareClass, ThrowableCaughtMiddleware::class, true) => $routeAttribute->withAddedThrowableCaughtMiddleware(
98
                            $middlewareClass
99
                        ),
100
                        is_a($middlewareClass, SendingResponseMiddleware::class, true) => $routeAttribute->withAddedSendingResponseMiddleware(
101
                            $middlewareClass
102
                        ),
103
                        is_a($middlewareClass, TerminatedMiddleware::class, true)      => $routeAttribute->withAddedTerminatedMiddleware(
104
                            $middlewareClass
105
                        ),
106
                        default                                                        => throw new InvalidArgumentException(
107
                            "Unsupported middleware class `$middlewareClass`"
108
                        ),
109
                    };
110
                }
111
112
                foreach ($requestMethods as $requestMethod) {
113
                    /** @psalm-suppress MixedArgument Unsure why Psalm doesn't realize that the requestMethods property is an array of RequestMethod enums */
114
                    $routeAttribute = $routeAttribute->withAddedRequestMethods(...$requestMethod->requestMethods);
115
                }
116
117
                /** @var class-string<RequestStructContract>[] $requestStruct */
118
                $requestStruct = array_column($routeRequestStruct, 'name');
119
120
                if ($requestStruct !== []) {
121
                    $routeAttribute = $routeAttribute->withRequestStruct($requestStruct[0]);
122
                }
123
124
                /** @var class-string<ResponseStructContract>[] $responseStruct */
125
                $responseStruct = array_column($routeResponseStruct, 'name');
126
127
                if ($responseStruct !== []) {
128
                    $routeAttribute = $routeAttribute->withResponseStruct($responseStruct[0]);
129
                }
130
131
                $routeAttribute = $routeAttribute->withParameters(
132
                    ...$this->attributes->forMethodParameters($class, $method, Parameter::class),
133
                    ...$routeParameters
134
                );
135
136
                // And set a new route with the controller defined annotation additions
137
                $attributes[] = $routeAttribute;
138
            }
139
        }
140
141
        foreach ($attributes as $attribute) {
142
            $attribute = $this->setRouteProperties($attribute);
143
144
            $routes[] = new \Valkyrja\Http\Routing\Data\Route(
145
                path: $attribute->getPath(),
146
                name: $attribute->getName(),
147
                dispatch: $attribute->getDispatch(),
148
                requestMethods: $attribute->getRequestMethods(),
149
                regex: $attribute->getRegex(),
150
                parameters: $attribute->getParameters(),
151
                routeMatchedMiddleware: $attribute->getRouteMatchedMiddleware(),
152
                routeDispatchedMiddleware: $attribute->getRouteDispatchedMiddleware(),
153
                throwableCaughtMiddleware: $attribute->getThrowableCaughtMiddleware(),
154
                sendingResponseMiddleware: $attribute->getSendingResponseMiddleware(),
155
                terminatedMiddleware: $attribute->getTerminatedMiddleware(),
156
                requestStruct: $attribute->getRequestStruct(),
157
                responseStruct: $attribute->getResponseStruct()
158
            );
159
        }
160
161
        return $routes;
162
    }
163
164
    /**
165
     * Set the route properties from arguments.
166
     *
167
     * @param RouteContract $route
168
     *
169
     * @throws ReflectionException
170
     *
171
     * @return RouteContract
172
     */
173
    protected function setRouteProperties(RouteContract $route): RouteContract
174
    {
175
        $dispatch = $route->getDispatch();
176
177
        $methodReflection = $this->reflection->forClassMethod($dispatch->getClass(), $dispatch->getMethod());
178
        // Set the dependencies
179
        $route = $route->withDispatch(
180
            $dispatch->withDependencies($this->reflection->getDependencies($methodReflection))
181
        );
182
183
        return $this->processor->route($route);
184
    }
185
}
186