Passed
Branch dev (8b2306)
by Alex
02:48
created

ControllerCollectorTrait::getPathConstraint()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 2
crap 2
1
<?php
2
3
/**
4
 * Codeburner Framework.
5
 *
6
 * @author Alex Rohleder <[email protected]>
7
 * @copyright 2016 Alex Rohleder
8
 * @license http://opensource.org/licenses/MIT
9
 */
10
11
namespace Codeburner\Router\Collectors;
12
13
use Codeburner\Router\Collector;
14
use Codeburner\Router\Group;
15
use ReflectionClass;
16
use ReflectionMethod;
17
use ReflectionParameter;
18
use Reflector;
19
20
/**
21
 * Methods for enable the collector to make routes from a controller.
22
 *
23
 * @author Alex Rohleder <[email protected]>
24
 */
25
26
trait ControllerCollectorTrait
27
{
28
29
    abstract public function getWildcards();
30
    abstract public function set($method, $pattern, $action);
31
32
    /**
33
     * Define how controller actions names will be joined to form the route pattern.
34
     *
35
     * @var string
36
     */
37
38
    protected $controllerActionJoin = "/";
39
40
    /**
41
     * Maps all the controller methods that begins with a HTTP method, and maps the rest of
42
     * name as a path. The path will be the method name with slashes before every camelcased 
43
     * word and without the HTTP method prefix, and the controller name will be used to prefix
44
     * the route pattern. e.g. ArticlesController::getCreate will generate a route to: GET articles/create
45
     *
46
     * @param string $controller The controller name
47
     * @param string $prefix
48
     *
49
     * @throws \ReflectionException
50
     * @return Group
51
     */
52
53 5
    public function controller($controller, $prefix = null)
54
    {
55 5
        $controller = new ReflectionClass($controller);
56 5
        $prefix     = $prefix === null ? $this->getControllerPrefix($controller) : $prefix;
57 5
        $methods    = $controller->getMethods(ReflectionMethod::IS_PUBLIC);
58 5
        return $this->collectControllerRoutes($controller, $methods, "/$prefix/");
59
    }
60
61
    /**
62
     * Maps several controllers at same time.
63
     *
64
     * @param string[] $controllers Controllers name.
65
     * @throws \ReflectionException
66
     * @return Group
67
     */
68
69 1 View Code Duplication
    public function controllers(array $controllers)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
70
    {
71 1
        $group = [];
72 1
        foreach ($controllers as $controller)
73 1
            $group[] = $this->controller($controller);
74 1
        return new Group($group);
75
    }
76
77
    /**
78
     * Alias for Collector::controller but maps a controller without using the controller name as prefix.
79
     *
80
     * @param string $controller The controller name
81
     * @throws \ReflectionException
82
     * @return Group
83
     */
84
85 2
    public function controllerWithoutPrefix($controller)
86
    {
87 2
        $controller = new ReflectionClass($controller);
88 2
        $methods = $controller->getMethods(ReflectionMethod::IS_PUBLIC);
89 2
        return $this->collectControllerRoutes($controller, $methods, "/");
90
    }
91
92
    /**
93
     * Alias for Collector::controllers but maps a controller without using the controller name as prefix.
94
     *
95
     * @param string[] $controllers
96
     * @throws \ReflectionException
97
     * @return Group
98
     */
99
100 1 View Code Duplication
    public function controllersWithoutPrefix(array $controllers)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
    {
102 1
        $group = [];
103 1
        foreach ($controllers as $controller)
104 1
            $group[] = $this->controllerWithoutPrefix($controller);
105 1
        return new Group($group);
106
    }
107
108
    /**
109
     * @param ReflectionClass $controller
110
     * @param string[] $methods
111
     * @param string $prefix
112
     *
113
     * @return Group
114
     */
115
116 7
    protected function collectControllerRoutes(ReflectionClass $controller, array $methods, $prefix)
117
    {
118 7
        $group = [];
119 7
        $controllerDefaultStrategy = $this->getAnnotatedStrategy($controller);
120
121
        /** @var ReflectionMethod $method */
122 7
        foreach ($methods as $method) {
123 7
            $name = preg_split("~(?=[A-Z])~", $method->name);
124 7
            $http = $name[0];
125 7
            unset($name[0]);
126
 
127 7
            if (strpos(Collector::HTTP_METHODS, $http) !== false) {
128 7
                $action   = $prefix . strtolower(implode($this->controllerActionJoin, $name));
129 7
                $dynamic  = $this->getMethodConstraints($method);
0 ignored issues
show
Documentation introduced by
$method is of type string, but the function expects a object<ReflectionMethod>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
130 7
                $strategy = $this->getAnnotatedStrategy($method);
0 ignored issues
show
Documentation introduced by
$method is of type string, but the function expects a object<ReflectionClass>|object<ReflectionMethod>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
131
132
                /** @var \Codeburner\Router\Route $route */
133 7
                $route = $this->set($http, "$action$dynamic", [$controller->name, $method->name]);
134
135 7
                if ($strategy !== null) {
136
                       $route->setStrategy($strategy);
137 7
                } else $route->setStrategy($controllerDefaultStrategy);
138
139 7
                $group[] = $route;
140 7
            }
141 7
        }
142
143 7
        return new Group($group);
144
    }
145
146
    /**
147
     * @param ReflectionClass $controller
148
     *
149
     * @return string
150
     */
151
152 4
    protected function getControllerPrefix(ReflectionClass $controller)
153
    {
154 4
        preg_match("~\@prefix\s([a-zA-Z\\\_]+)~i", (string) $controller->getDocComment(), $prefix);
155 4
        return isset($prefix[1]) ? $prefix[1] : str_replace("controller", "", strtolower($controller->getShortName()));
156
    }
157
158
    /**
159
     * @param \ReflectionMethod
160
     * @return string
161
     */
162
163 7
    protected function getMethodConstraints(ReflectionMethod $method)
164
    {
165 7
        $beginPath = "";
166 7
        $endPath = "";
167
168 7
        if ($parameters = $method->getParameters()) {
169 7
            $types = $this->getParamsConstraint($method);
170
171 7
            foreach ($parameters as $parameter) {
172 7
                if ($parameter->isOptional()) {
173 6
                    $beginPath .= "[";
174 6
                    $endPath .= "]";
175 6
                }
176
177 7
                $beginPath .= $this->getPathConstraint($parameter, $types);
178 7
            }
179 7
        }
180
181 7
        return $beginPath . $endPath;
182
    }
183
184
    /**
185
     * @param ReflectionParameter $parameter
186
     * @param string[] $types
187
     * @return string
188
     */
189
190 7
    protected function getPathConstraint(ReflectionParameter $parameter, $types)
191
    {
192 7
        $name = $parameter->name;
193 7
        $path = "/{" . $name;
194 7
        return isset($types[$name]) ? "$path:{$types[$name]}}" : "$path}";
195
    }
196
197
    /**
198
     * @param ReflectionMethod $method
199
     * @return string[]
200
     */
201
202 7
    protected function getParamsConstraint(ReflectionMethod $method)
203
    {
204 7
        $params = [];
205 7
        preg_match_all("~\@param\s(" . implode("|", array_keys($this->getWildcards())) . "|\(.+\))\s\\$([a-zA-Z0-1_]+)~i",
206 7
            $method->getDocComment(), $types, PREG_SET_ORDER);
207
208 7
        foreach ((array) $types as $type) {
209
            // if a pattern is defined on Match take it otherwise take the param type by PHPDoc.
210 1
            $params[$type[2]] = isset($type[4]) ? $type[4] : $type[1];
211 7
        }
212
213 7
        return $params;
214
    }
215
216
    /**
217
     * @param ReflectionClass|ReflectionMethod $reflector
218
     * @return string|null
219
     */
220
221 7
    protected function getAnnotatedStrategy($reflector)
222
    {
223 7
        preg_match("~\@strategy\s([a-zA-Z\\\_]+)~i", (string) $reflector->getDocComment(), $strategy);
224 7
        return isset($strategy[1]) ? $strategy[1] : null;
225
    }
226
227
    /**
228
     * Define how controller actions names will be joined to form the route pattern.
229
     * Defaults to "/" so actions like "getMyAction" will be "/my/action". If changed to
230
     * "-" the new pattern will be "/my-action".
231
     *
232
     * @param string $join
233
     */
234
235 1
    public function setControllerActionJoin($join)
236
    {
237 1
        $this->controllerActionJoin = $join;
238 1
    }
239
240
    /**
241
     * @return string
242
     */
243
244
    public function getControllerActionJoin()
245
    {
246
        return $this->controllerActionJoin;
247
    }
248
249
}
250