Passed
Pull Request — master (#33)
by Melech
03:22
created

Attributes::getRoutes()   C

Complexity

Conditions 13
Paths 6

Size

Total Lines 116
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 67
c 0
b 0
f 0
nc 6
nop 1
dl 0
loc 116
rs 6.0133

How to fix   Long Method    Complexity   

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\Routing\Attributes;
15
16
use InvalidArgumentException;
17
use ReflectionException;
18
use Valkyrja\Attribute\Attributes as AttributeAttributes;
19
use Valkyrja\Reflection\Reflector;
20
use Valkyrja\Routing\Attributes as Contract;
21
use Valkyrja\Routing\Exceptions\InvalidRoutePath;
22
use Valkyrja\Routing\Message as RoutingMessage;
23
use Valkyrja\Routing\Processor;
24
25
/**
26
 * Class Attributes.
27
 *
28
 * @author Melech Mizrachi
29
 */
30
class Attributes implements Contract
31
{
32
    public function __construct(
33
        protected AttributeAttributes $attributes,
34
        protected Reflector $reflector,
35
        protected Processor $processor
36
    ) {
37
    }
38
39
    /**
40
     * @inheritDoc
41
     *
42
     * @throws InvalidRoutePath
43
     * @throws ReflectionException
44
     */
45
    public function getRoutes(string ...$classes): array
46
    {
47
        $routes = [];
48
49
        foreach ($classes as $class) {
50
            /** @var Route[] $classAttributes */
51
            $classAttributes = $this->attributes->forClass($class, Route::class);
52
            /** @var Route[] $memberAttributes */
53
            $memberAttributes = $this->attributes->forClassMembers($class, Route::class);
54
55
            $finalAttributes = [];
56
57
            // If this class has attributes
58
            if (! empty($classAttributes)) {
59
                // Iterate through all the class attributes
60
                foreach ($classAttributes as $classAttribute) {
61
                    /** @var Parameter[] $mergedClassParameters */
62
                    $mergedClassParameters = [
63
                        ...$classAttribute->getParameters(),
64
                        ...$this->attributes->forClass($class, Parameter::class),
65
                    ];
66
                    $mergedClassMiddleware = [
67
                        ...($classAttribute->getMiddleware() ?? []),
68
                        ...array_column($this->attributes->forClass($class, Middleware::class), 'name'),
69
                    ];
70
                    /** @var class-string<RoutingMessage>[] $mergedClassMessages */
71
                    $mergedClassMessages = [
72
                        ...($classAttribute->getMessages() ?? []),
73
                        ...array_column($this->attributes->forClass($class, Message::class), 'name'),
74
                    ];
75
76
                    if (! empty($this->attributes->forClass($class, Secure::class))) {
77
                        $classAttribute->setSecure();
78
                    }
79
80
                    if (! empty($to = array_column($this->attributes->forClass($class, Redirect::class), 'to'))) {
81
                        $classAttribute->setTo($to[0]);
82
                    }
83
84
                    $classAttribute->setParameters($mergedClassParameters);
85
                    $classAttribute->setMiddleware($mergedClassMiddleware);
86
                    $classAttribute->setMessages($mergedClassMessages);
87
88
                    // If the class' members' had attributes
89
                    if (! empty($memberAttributes)) {
90
                        // Iterate through all the members' attributes
91
                        foreach ($memberAttributes as $routeAttribute) {
92
                            $routeParameters = [];
93
                            $routeMiddleware = [];
94
                            $routeMessages   = [];
95
                            $routeSecure     = [];
96
                            $routeRedirect   = [];
97
98
                            if ($property = $routeAttribute->getProperty()) {
99
                                $routeParameters = $this->attributes->forProperty($class, $property, Parameter::class);
100
                                $routeMiddleware = $this->attributes->forProperty($class, $property, Middleware::class);
101
                                $routeMessages   = $this->attributes->forProperty($class, $property, Message::class);
102
                                $routeSecure     = $this->attributes->forProperty($class, $property, Secure::class);
103
                                $routeRedirect   = $this->attributes->forProperty($class, $property, Redirect::class);
104
                            } elseif ($method = $routeAttribute->getMethod()) {
105
                                $routeParameters = $this->attributes->forMethod($class, $method, Parameter::class);
106
                                $routeMiddleware = $this->attributes->forMethod($class, $method, Middleware::class);
107
                                $routeMessages   = $this->attributes->forMethod($class, $method, Message::class);
108
                                $routeSecure     = $this->attributes->forMethod($class, $method, Secure::class);
109
                                $routeRedirect   = $this->attributes->forMethod($class, $method, Redirect::class);
110
                            }
111
112
                            /** @var Parameter[] $mergedPropertyParameters */
113
                            $mergedPropertyParameters = [
114
                                ...$routeAttribute->getParameters(),
115
                                ...$routeParameters,
116
                            ];
117
                            $mergedPropertyMiddleware = [
118
                                ...($routeAttribute->getMiddleware() ?? []),
119
                                ...array_column($routeMiddleware, 'name'),
120
                            ];
121
                            /** @var class-string<RoutingMessage>[] $mergedPropertyMessages */
122
                            $mergedPropertyMessages = [
123
                                ...($routeAttribute->getMessages() ?? []),
124
                                ...array_column($routeMessages, 'name'),
125
                            ];
126
127
                            if (! empty($routeSecure)) {
128
                                $routeAttribute->setSecure();
129
                            }
130
131
                            if (! empty($to = array_column($routeRedirect, 'to'))) {
132
                                $routeAttribute->setTo($to[0]);
133
                            }
134
135
                            $routeAttribute->setParameters($mergedPropertyParameters);
136
                            $routeAttribute->setMiddleware($mergedPropertyMiddleware);
137
                            $routeAttribute->setMessages($mergedPropertyMessages);
138
139
                            // And set a new route with the controller defined annotation additions
140
                            $finalAttributes[] = $this->getControllerBuiltRoute($classAttribute, $routeAttribute);
141
                        }
142
                    }
143
144
                    // Figure out if there should be an else that automatically sets routes from the class attributes
145
                }
146
            } else {
147
                $finalAttributes = $memberAttributes;
148
            }
149
150
            $routes = [
151
                ...$routes,
152
                ...$finalAttributes,
153
            ];
154
        }
155
156
        foreach ($routes as $route) {
157
            $this->setRouteProperties($route);
158
        }
159
160
        return $routes;
161
    }
162
163
    /**
164
     * Set the route properties from arguments.
165
     *
166
     * @param Route $route
167
     *
168
     * @throws ReflectionException
169
     *
170
     * @return void
171
     */
172
    protected function setRouteProperties(Route $route): void
173
    {
174
        if (($class = $route->getClass()) === null) {
175
            throw new InvalidArgumentException('Invalid class defined in route.');
176
        }
177
178
        if (($method = $route->getMethod()) !== null) {
179
            $methodReflection = $this->reflector->getMethodReflection($class, $method);
180
            // Set the dependencies
181
            $route->setDependencies($this->reflector->getDependencies($methodReflection));
182
        }
183
184
        // Avoid having large arrays in cached routes file
185
        $route->setMatches();
186
187
        $this->processor->route($route);
188
    }
189
190
    /**
191
     * Get a new attribute with controller attribute additions.
192
     *
193
     * @param Route $controllerAttribute The controller route attribute
194
     * @param Route $memberAttribute     The member route attribute
195
     *
196
     * @return Route
197
     */
198
    protected function getControllerBuiltRoute(Route $controllerAttribute, Route $memberAttribute): Route
199
    {
200
        $attribute = clone $memberAttribute;
201
202
        if (! $memberAttribute->getPath()) {
203
            throw new InvalidArgumentException('Invalid path defined in route.');
204
        }
205
206
        // Get the route's path
207
        $path           = $this->getFilteredPath($memberAttribute->getPath());
208
        $controllerPath = $this->getFilteredPath($controllerAttribute->getPath());
209
210
        // Set the path to the base path and route path
211
        $attribute->setPath($this->getFilteredPath($controllerPath . $path));
212
213
        // If there is a base name for this controller
214
        if (($controllerName = $controllerAttribute->getName()) !== null) {
215
            // Set the name to the base name and route name
216
            $attribute->setName($controllerName . (($name = $memberAttribute->getName()) ? '.' . $name : ''));
217
        }
218
219
        // If the base is dynamic
220
        if ($controllerAttribute->isDynamic()) {
221
            // Set the route to dynamic
222
            $attribute->setDynamic();
223
        }
224
225
        // If the base is secure
226
        if ($controllerAttribute->isSecure()) {
227
            // Set the route to secure
228
            $attribute->setSecure();
229
        }
230
231
        // If the base has a redirect
232
        if ($to = $controllerAttribute->getTo()) {
233
            // Set the route's redirect to path
234
            $attribute->setTo($to);
235
        }
236
237
        // If there is a base middleware collection for this controller
238
        if (($controllerMiddleware = $controllerAttribute->getMiddleware()) !== null) {
239
            // Merge the route's middleware and the controller's middleware
240
            // keeping the controller's middleware first
241
            $attribute->setMiddleware(
242
                [
243
                    ...$controllerMiddleware,
244
                    ...($memberAttribute->getMiddleware() ?? []),
245
                ]
246
            );
247
        }
248
249
        // If there is a base message collection for this controller
250
        if (($controllerMessages = $controllerAttribute->getMessages()) !== null) {
251
            // Merge the route's messages and the controller's messages
252
            // keeping the controller's messages first
253
            $attribute->setMessages(
254
                [
255
                    ...$controllerMessages,
256
                    ...($memberAttribute->getMessages() ?? []),
257
                ]
258
            );
259
        }
260
261
        // If there is a base parameters collection for this controller
262
        if (! empty($controllerParameters = $controllerAttribute->getParameters())) {
263
            // Merge the route's parameters and the controller's parameters
264
            // keeping the controller's parameters first
265
            $attribute->setParameters(
266
                [
267
                    ...$controllerParameters,
268
                    ...$memberAttribute->getParameters(),
269
                ]
270
            );
271
        }
272
273
        return $attribute;
274
    }
275
276
    /**
277
     * Validate a path.
278
     *
279
     * @param string $path The path
280
     *
281
     * @return string
282
     */
283
    protected function getFilteredPath(string $path): string
284
    {
285
        // Trim slashes from the beginning and end of the path
286
        if (! $path = trim($path, '/')) {
287
            // If the path only had a slash return as just slash
288
            return '/';
289
        }
290
291
        // If the route doesn't begin with an optional or required group
292
        if ($path[0] !== '[' && $path[0] !== '<') {
293
            // Append a slash
294
            return '/' . $path;
295
        }
296
297
        return $path;
298
    }
299
}
300