Passed
Branch dev (473855)
by Alex
02:25
created

controllerWithoutPrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

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 1
eloc 4
nc 1
nop 1
crap 1
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
    public function controllers(array $controllers)
70
    {
71 1
        $group = new Group;
72 1
        foreach ($controllers as $controller)
73 1
            $group->set($this->controller($controller));
74 1
        return $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
    public function controllersWithoutPrefix(array $controllers)
101
    {
102 1
        $group = new Group;
103 1
        foreach ($controllers as $controller)
104 1
            $group->set($this->controllerWithoutPrefix($controller));
105 1
        return $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 = new 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->set($route);
140 7
            }
141 7
        }
142
143 7
        return $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