Passed
Pull Request — master (#13)
by Divine Niiquaye
02:26
created

Listener::getDefaultRouteName()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 8
nc 4
nop 1
dl 0
loc 15
ccs 9
cts 9
cp 1
crap 5
rs 9.6111
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.1 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Flight\Routing\Annotation;
19
20
use Biurad\Annotations\InvalidAnnotationException;
21
use Biurad\Annotations\ListenerInterface;
22
use Flight\Routing\Route as BaseRoute;
23
use Flight\Routing\RouteCollection;
24
use Flight\Routing\Router;
25
26
class Listener implements ListenerInterface
27
{
28
    /** @var RouteCollection */
29
    private $collector;
30
31
    /** @var int */
32
    private $defaultRouteIndex = 0;
33
34
    /**
35
     * @param null|RouteCollection $collector
36
     */
37 15
    public function __construct(?RouteCollection $collector = null)
38
    {
39 15
        $this->collector = $collector ?? new RouteCollection();
40 15
    }
41
42
    /**
43
     * {@inheritdoc}
44
     *
45
     * @param array<string,array<string,mixed>> $annotations
46
     */
47 5
    public function onAnnotation(array $annotations): RouteCollection
48
    {
49
        /** @var class-string $class */
50 5
        foreach ($annotations as $class => $collection) {
51 5
            if (isset($collection['method'])) {
52 4
                $this->addRouteGroup($collection['class'] ?? [], $collection['method']);
53
54 4
                continue;
55
            }
56
57 4
            $this->defaultRouteIndex = 0;
58
59
            /** @var Route $annotation */
60 4
            foreach ($collection['class'] ?? [] as $annotation) {
61 4
                $this->collector->add($this->addRoute($annotation, $class));
62
            }
63
        }
64
65 4
        return $this->collector;
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 3
    public function getArguments(): array
72
    {
73 3
        return [];
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79 5
    public function getAnnotation(): string
80
    {
81 5
        return 'Flight\Routing\Annotation\Route';
82
    }
83
84
    /**
85
     * Add a route from annotation
86
     *
87
     * @param Route                 $annotation
88
     * @param class-string|string[] $handler
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|string[] at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|string[].
Loading history...
89
     * @param null|Route            $group
90
     *
91
     * @return BaseRoute
92
     */
93 5
    protected function addRoute(Route $annotation, $handler, ?Route $group = null): BaseRoute
94
    {
95 5
        if (null === $annotation->getPath()) {
96 1
            throw new InvalidAnnotationException('@Route.path must not be left empty.');
97
        }
98
99 4
        $name = $annotation->getName() ?? $this->getDefaultRouteName($handler);
100
101 4
        $methods = str_ends_with($name, '__restful') ? Router::HTTP_METHODS_STANDARD : $annotation->getMethods();
102 4
        $route   = new BaseRoute($annotation->getPath(), \join('|', $methods), $handler);
103
104 4
        $route->scheme(...$annotation->getSchemes())->middleware(...$annotation->getMiddlewares())->bind($name);
105
106 4
        if (null !== $annotation->getDomain()) {
107 3
            $route->domain($annotation->getDomain());
108
        }
109
110 4
        foreach ($annotation->getDefaults() as $variable => $default) {
111 3
            $route->default($variable, $default);
112
        }
113
114 4
        foreach ($annotation->getPatterns() as $variable => $regexp) {
115 3
            $route->assert($variable, $regexp);
116
        }
117
118 4
        if (null !== $group) {
119 4
            $route = $this->mergeGroup($group, $route);
120
        }
121
122 4
        return $route;
123
    }
124
125
    /**
126
     * Add a routes from annotation into group
127
     *
128
     * @param Route[] $grouping
129
     * @param mixed[] $methods
130
     */
131 4
    protected function addRouteGroup(array $grouping, array $methods): void
132
    {
133 4
        if (!empty($grouping)) {
134 4
            foreach ($grouping as $group) {
135 4
                $this->mergeAnnotations($methods, $group);
136
            }
137
138 4
            return;
139
        }
140
141 3
        $this->mergeAnnotations($methods);
142 3
    }
143
144
    /**
145
     * @param mixed[]    $methods
146
     * @param null|Route $group
147
     */
148 4
    protected function mergeAnnotations(array $methods, ?Route $group = null): void
149
    {
150 4
        $this->defaultRouteIndex = 0;
151
152 4
        $routes = [];
153
154 4
        foreach ($methods as [$method, $annotation]) {
155 4
            $routes[] = $this->addRoute($annotation, [$method->class, $method->getName()], $group);
156
        }
157
158 4
        $this->collector->add(...$routes);
159 4
    }
160
161
    /**
162
     * @param Route     $group
163
     * @param BaseRoute $route
164
     *
165
     * @return BaseRoute
166
     */
167 4
    protected function mergeGroup(Route $group, BaseRoute $route): BaseRoute
168
    {
169 4
        $route = $route->bind($group->getName() . $route->getName())
170 4
            ->scheme(...$group->getSchemes())
171 4
            ->prefix($group->getPath() ?? '')
172 4
            ->method(...$group->getMethods())
173 4
        ->middleware(...$group->getMiddlewares());
174
175 4
        if (null !== $group->getDomain()) {
176 3
            $route->domain($group->getDomain());
177
        }
178
179 4
        foreach ($group->getDefaults() as $variable => $default) {
180 2
            $route->default($variable, $default);
181
        }
182
183 4
        foreach ($group->getPatterns() as $variable => $regexp) {
184 2
            $route->assert($variable, $regexp);
185
        }
186
187 4
        return $route;
188
    }
189
190
    /**
191
     * Gets the default route name for a class method.
192
     *
193
     * @param class-string|mixed[] $handler
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|mixed[] at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|mixed[].
Loading history...
194
     *
195
     * @return string
196
     */
197 4
    private function getDefaultRouteName($handler): string
198
    {
199 4
        $classReflection = new \ReflectionClass(\is_array($handler) ? $handler[0] : $handler);
200 4
        $name            = \str_replace('\\', '_', $classReflection->name);
201
202 4
        if (\is_array($handler) || $classReflection->hasMethod('__invoke')) {
203 4
            $name .= '_' . $handler[1] ?? '__invoke';
204
        }
205
206 4
        if ($this->defaultRouteIndex > 0) {
207 3
            $name .= '_' . $this->defaultRouteIndex;
208
        }
209 4
        ++$this->defaultRouteIndex;
210
211 4
        return \strtolower($name);
212
    }
213
}
214