Completed
Pull Request — master (#11)
by Miles
05:41
created

RoutingServiceProvider::addActions()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 3 Features 2
Metric Value
c 9
b 3
f 2
dl 0
loc 29
rs 6.7272
cc 7
eloc 20
nc 9
nop 3
1
<?php
2
namespace MJanssen\Provider;
3
4
use InvalidArgumentException;
5
use Silex\Application;
6
use Silex\Controller;
7
use Silex\Route;
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');
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
                default:
211
                    $this->addAction($controller, $name, $value, $type);
212
                    break;
213
            }
214
        }
215
    }
216
217
    /**
218
     * @param Controller $controller
219
     * @param $name
220
     * @param $value
221
     * @param $type
222
     */
223
    protected function addAction(Controller $controller, $name, $value, $type)
224
    {
225
        call_user_func_array(array($controller, $type), array($name, $value));
226
    }
227
228
    protected function isClosure($param)
229
    {
230
        return is_object($param) && ($param instanceof \Closure);
231
    }
232
233
    /**
234
     * Adds a middleware (before/after)
235
     *
236
     * @param Controller $controller
237
     * @param string $type | 'before' or 'after'
238
     * @param $value
239
     */
240
    protected function addBeforeAfterMiddleware(Controller $controller, $type, $value)
241
    {
242
        $supportedMWTypes = ['before', 'after'];
243
244
        if (!in_array($type, $supportedMWTypes)) {
245
            throw new \UnexpectedValueException(
246
                sprintf(
247
                    'type %s not supported',
248
                    $type
249
                )
250
            );
251
        }
252
253
        if ($this->isClosure($value)) {
254
            //When a closure is provided, we will just load it as a middleware type
255
            $controller->$type($value);
256
        } else {
257
            //In this case a yaml/xml configuration was used
258
            $this->addMiddlewareFromConfig($controller, $type, $value);
259
        }
260
    }
261
262
    /**
263
     * Adds a before/after middleware by its configuration
264
     *
265
     * @param Controller $controller
266
     * @param $type
267
     * @param $value
268
     */
269
    protected function addMiddlewareFromConfig(Controller $controller, $type, $value)
270
    {
271
        if (!is_string($value) || strpos($value, '::') === FALSE) {
272
            throw new InvalidArgumentException(
273
                sprintf(
274
                    '%s is no valid Middleware callback. Please provide the following syntax: NamespaceName\SubNamespaceName\ClassName::methodName',
275
                    $value
276
                )
277
            );
278
        }
279
280
        list($class, $method) = explode('::', $value, 2);
281
282
        if ($class && $method) {
283
284
            if (!method_exists($class, $method)) {
285
                throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', $class, $method));
286
            }
287
288
            $controller->$type([new $class, $method]);
289
        }
290
    }
291
}
292