Passed
Branch dev (140aec)
by Alex
02:37
created

ControllerCollectorTrait::controllers()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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