Completed
Pull Request — master (#13)
by Jesus
06:16
created

RoutingServiceProvider::addRoutes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 1 Features 3
Metric Value
c 6
b 1
f 3
dl 0
loc 11
rs 9.4285
cc 3
eloc 5
nc 3
nop 2
1
<?php
2
namespace MJanssen\Provider;
3
4
use Silex\Route;
5
use Silex\Controller;
6
use Silex\Application;
7
use InvalidArgumentException;
8
use Silex\ServiceProviderInterface;
9
10
/**
11
 * Class RoutingServiceProvider
12
 * @package MJanssen\Provider
13
 */
14
class RoutingServiceProvider implements ServiceProviderInterface
15
{
16
    /**
17
     * @var
18
     */
19
    protected $appRoutingKey;
20
21
    /**
22
     * @param string $appRoutingKey
23
     */
24
    public function __construct($appRoutingKey = 'config.routes')
25
    {
26
        $this->appRoutingKey = $appRoutingKey;
27
    }
28
29
    /**
30
     * @param Application $app
31
     * @throws \InvalidArgumentException
32
     */
33
    public function register(Application $app)
34
    {
35
        if (isset($app[$this->appRoutingKey])) {
36
            if (is_array($app[$this->appRoutingKey])) {
37
                $this->addRoutes($app, $app[$this->appRoutingKey]);
38
            } else {
39
                throw new InvalidArgumentException('config.routes must be of type Array');
40
            }
41
        }
42
    }
43
44
    /**
45
     * @param Application $app
46
     * @codeCoverageIgnore
47
     */
48
    public function boot(Application $app)
49
    {
50
    }
51
52
    /**
53
     * Adds all routes
54
     *
55
     * @param Application $app
56
     * @param $routes
57
     */
58
    public function addRoutes(Application $app, $routes)
59
    {
60
        foreach ($routes as $name => $route) {
61
62
            if (is_numeric($name)) {
63
                $name = '';
64
            }
65
66
            $this->addRoute($app, $route, $name);
67
        }
68
    }
69
70
    /**
71
     * Adds a route, a given route-name (for named routes) and all of its methods
72
     *
73
     * @param Application $app
74
     * @param array $route
75
     * @throws InvalidArgumentException
76
     */
77
    public function addRoute(Application $app, array $route, $name = '')
78
    {
79
        if (isset($route['method']) && is_string($route['method'])) {
80
            $route['method'] = array($route['method']);
81
        }
82
83
        $this->validateRoute($route);
84
85
        if (array_key_exists('name', $route)) {
86
            $name = $route['name'];
87
        }
88
89
        $controller = $app->match(
90
            $route['pattern'],
91
            $route['controller'])
92
            ->bind(
93
                $this->sanitizeRouteName($name)
94
            )->method(
95
                join('|', array_map('strtoupper', $route['method']))
96
            );
97
98
        $supportedProperties = array('value', 'assert', 'convert', 'before', 'after', 'secure');
99
        foreach ($supportedProperties AS $property) {
100
            if (isset($route[$property])) {
101
                $this->addActions($controller, $route[$property], $property);
102
            }
103
        }
104
105
        if (isset($route['scheme'])) {
106
            if ('https' === $route['scheme']) {
107
                $controller->requireHttps();
108
            }
109
        }
110
    }
111
112
    /**
113
     * Validates the given methods. Only get, put, post, delete, options, head
114
     * are allowed
115
     *
116
     * @param array $methods
117
     */
118
    protected function validateMethods(Array $methods)
119
    {
120
        $availableMethods = array('get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'purge', 'options', 'trace', 'connect');
121
        foreach (array_map('strtolower', $methods) as $method) {
122
            if (!in_array($method, $availableMethods)) {
123
                throw new InvalidArgumentException('Method "' . $method . '" is not valid, only the following methods are allowed: ' . join(', ', $availableMethods));
124
            }
125
        }
126
    }
127
128
    /**
129
     * Validates the given $route Array
130
     *
131
     * @param $route
132
     * @throws \InvalidArgumentException
133
     */
134
    protected function validateRoute($route)
135
    {
136
        if (!isset($route['pattern']) || !isset($route['method']) || !isset($route['controller'])) {
137
            throw new InvalidArgumentException('Required parameter (pattern/method/controller) is not set.');
138
        }
139
140
        $arrayParameters = array('method', 'assert', 'value');
141
142
        foreach ($arrayParameters as $parameter) {
143
            if (isset($route[$parameter]) && !is_array($route[$parameter])) {
144
                throw new InvalidArgumentException(sprintf(
145
                    '%s is not of type Array (%s)',
146
                    $parameter, gettype($route[$parameter])
147
                ));
148
            }
149
        }
150
151
        $this->validateMethods($route['method']);
152
    }
153
154
155
    /**
156
     * Sanitizes the routeName for named route:
157
     *
158
     * - replaces '/', ':', '|', '-' with '_'
159
     * - removes special characters
160
     *
161
     *  Algorithm copied from \Silex\Controller->generateRouteName
162
     *  see: https://github.com/silexphp/Silex/blob/1.2/src/Silex/Controller.php
163
     *
164
     * @param string $routeName
165
     * @return string
166
     */
167
    protected function sanitizeRouteName($routeName)
168
    {
169
        if (empty($routeName)) {
170
            //If no routeName is specified,
171
            //we set an empty route name to force the default route name e.g. "GET_myRouteName"
172
            return '';
173
        }
174
175
        $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName);
176
        $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
177
178
        return $routeName;
179
    }
180
181
    /**
182
     * @param Controller $controller
183
     * @param $actions
184
     * @param $type
185
     * @throws \InvalidArgumentException
186
     */
187
    protected function addActions(Controller $controller, $actions, $type)
188
    {
189
        if (!is_array($actions)){
190
            if ($type === 'before' || $type === 'after') {
191
                $actions = array($actions);
192
            } else {
193
                throw new InvalidArgumentException(
194
                    sprintf(
195
                        'Action %s is not of type Array (%s)',
196
                        $type, gettype($actions)
197
                    )
198
                );
199
            }
200
        }
201
202
        foreach ($actions as $name => $value) {
203
            switch ($type) {
204
                case 'after':
205
                    $this->addBeforeAfterMiddleware($controller, $type, $value);
206
                    break;
207
                case 'before':
208
                    $this->addBeforeAfterMiddleware($controller, $type, $value);
209
                    break;
210
                case 'secure':
211
                    $this->addSecure($controller, $type, $actions);
212
                    break;
213
                default:
214
                    $this->addAction($controller, $name, $value, $type);
215
                    break;
216
            }
217
        }
218
    }
219
220
    /**
221
     * @param Controller $controller
222
     * @param $name
223
     * @param $value
224
     * @param $type
225
     */
226
    protected function addAction(Controller $controller, $name, $value, $type)
227
    {
228
        call_user_func_array(array($controller, $type), array($name, $value));
229
    }
230
231
    /**
232
     * @param Controller $controller
233
     * @param $type
234
     * @param array $values
235
     */
236
    protected function addSecure(Controller $controller, $type, Array $values)
237
    {
238
        call_user_func_array(array($controller, $type), $values);
239
    }
240
241
    protected function isClosure($param)
242
    {
243
        return is_object($param) && ($param instanceof \Closure);
244
    }
245
246
    /**
247
     * Adds a middleware (before/after)
248
     *
249
     * @param Controller $controller
250
     * @param string $type | 'before' or 'after'
251
     * @param $value
252
     */
253
    protected function addBeforeAfterMiddleware(Controller $controller, $type, $value)
254
    {
255
        $supportedMWTypes = ['before', 'after'];
256
257
        if (!in_array($type, $supportedMWTypes)) {
258
            throw new \UnexpectedValueException(
259
                sprintf(
260
                    'type %s not supported',
261
                    $type
262
                )
263
            );
264
        }
265
266
        if ($this->isClosure($value)) {
267
            //When a closure is provided, we will just load it as a middleware type
268
            $controller->$type($value);
269
        } else {
270
            //In this case a yaml/xml configuration was used
271
            $this->addMiddlewareFromConfig($controller, $type, $value);
272
        }
273
    }
274
275
    /**
276
     * Adds a before/after middleware by its configuration
277
     *
278
     * @param Controller $controller
279
     * @param $type
280
     * @param $value
281
     */
282
    protected function addMiddlewareFromConfig(Controller $controller, $type, $value)
283
    {
284
        if (!is_string($value) || strpos($value, '::') === FALSE) {
285
            throw new InvalidArgumentException(
286
                sprintf(
287
                    '%s is no valid Middleware callback. Please provide the following syntax: NamespaceName\SubNamespaceName\ClassName::methodName',
288
                    $value
289
                )
290
            );
291
        }
292
293
        list($class, $method) = explode('::', $value, 2);
294
295
        if ($class && $method) {
296
297
            if (!method_exists($class, $method)) {
298
                throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', $class, $method));
299
            }
300
301
            $controller->$type([new $class, $method]);
302
        }
303
    }
304
}
305