ConventionalLoader::getDefaultCollectionRoutes()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 14
nc 1
nop 2
1
<?php
2
3
namespace Knp\RadBundle\Routing\Loader;
4
5
use Symfony\Component\Routing\RouteCollection;
6
use Symfony\Component\Routing\Route;
7
use Symfony\Component\Routing\Loader\YamlFileLoader;
8
use Symfony\Component\Config\Resource\FileResource;
9
use Symfony\Component\Config\FileLocatorInterface;
10
11
class ConventionalLoader extends YamlFileLoader
12
{
13
    private static $supportedControllerKeys = array(
14
        'prefix', 'defaults', 'requirements', 'options', 'collections', 'resources'
15
    );
16
    private static $supportedActionKeys = array(
17
        'pattern', 'defaults', 'requirements', 'options', 'methods'
18
    );
19
    private $yaml;
20
21
    public function __construct(FileLocatorInterface $locator, YamlParser $yaml = null)
22
    {
23
        parent::__construct($locator);
24
25
        $this->yaml = $yaml ?: new YamlParser;
26
    }
27
28
    public function supports($resource, $type = null)
29
    {
30
        return 'rad_convention' === $type;
31
    }
32
33
    public function load($file, $type = null)
34
    {
35
        $path   = $this->locator->locate($file);
36
        $config = $this->yaml->parse($path);
37
38
        $collection = new RouteCollection();
39
        $collection->addResource(new FileResource($file));
40
41
        if (null === $config) {
42
            return $collection;
43
        }
44
45
        if (!is_array($config)) {
46
            throw new \InvalidArgumentException(sprintf(
47
                'The file "%s" must contain a YAML array.', $path
48
            ));
49
        }
50
51
        foreach ($config as $shortname => $mapping) {
52
            $parts = explode(':', $shortname);
53
54
            if (3 == count($parts)) {
55
                list($bundle, $class, $action) = $parts;
56
57
                $routeName = $this->getRouteName($bundle, $class, $action);
58
                $route     = $this->getCustomCollectionRoute($bundle, $class, $action);
59
60
                $this->overrideRouteParams($shortname, $route, $mapping);
61
                $collection->add($routeName, $route);
62
63
                continue;
64
            }
65
66
            if (1 == count($parts)) {
67
                $this->parseClassical($collection, $shortname, $mapping, $path, $file);
68
69
                continue;
70
            }
71
72
            if (is_array($mapping)) {
73
                foreach ($mapping as $key => $val) {
74
                    if (in_array($key, self::$supportedControllerKeys)) {
75
                        continue;
76
                    }
77
78
                    if ('pattern' === $key) {
79
                        throw new \InvalidArgumentException(
80
                            'The `pattern` is only supported for actions, if you want to prefix '.
81
                            'all the routes of the controller, use `prefix` instead.'
82
                        );
83
                    }
84
85
                    throw new \InvalidArgumentException(sprintf(
86
                        '`%s` parameter is not supported by `%s` controller route. Use one of [%s].',
87
                        $key, $shortname, implode(', ', self::$supportedControllerKeys)
88
                    ));
89
                }
90
            }
91
92
            list($bundle, $class) = $parts;
93
94
            $prefix                 = $this->getPatternPrefix($class, $mapping);
95
            $collectionDefaults     = $this->getDefaultsFromMapping($mapping, 'collections');
96
            $collectionRequirements = $this->getRequirementsFromMapping($mapping, 'collections');
97
            $collectionOptions      = $this->getOptionsFromMapping($mapping, 'collections');
98
            $resourceDefaults       = $this->getDefaultsFromMapping($mapping, 'resources');
99
            $resourceRequirements   = $this->getRequirementsFromMapping($mapping, 'resources');
100
            $resourceOptions        = $this->getOptionsFromMapping($mapping, 'resources');
101
102
            $collectionRoutes = $this->getCollectionRoutesFromMapping($shortname, $mapping, $bundle, $class);
103
            $resourceRoutes   = $this->getResourceRoutesFromMapping($shortname, $mapping, $bundle, $class);
104
105
            $controllerCollection = new RouteCollection();
106
            foreach ($collectionRoutes as $name => $route) {
107
                $route->setDefaults(array_merge($collectionDefaults, $route->getDefaults()));
108
                $route->setRequirements(array_merge(
109
                    $collectionRequirements, $route->getRequirements()
110
                ));
111
                $route->setOptions(array_merge(
112
                    $collectionOptions, $route->getOptions()
113
                ));
114
                $controllerCollection->add($name, $route);
115
            }
116
            foreach ($resourceRoutes as $name => $route) {
117
                $route->setDefaults(array_merge($resourceDefaults, $route->getDefaults()));
118
                $route->setRequirements(array_merge(
119
                    $resourceRequirements, $route->getRequirements()
120
                ));
121
                $route->setOptions(array_merge(
122
                    $resourceOptions, $route->getOptions()
123
                ));
124
                $controllerCollection->add($name, $route);
125
            }
126
            $controllerCollection->addPrefix($prefix);
127
            $collection->addCollection($controllerCollection);
128
        }
129
130
        return $collection;
131
    }
132
133
    protected function parseClassical(RouteCollection $collection, $shortname,
0 ignored issues
show
Coding Style introduced by
The first parameter of a multi-line function declaration must be on the line after the opening bracket
Loading history...
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
134
                                      array $mapping, $path, $file)
0 ignored issues
show
Coding Style introduced by
Multi-line function declarations must define one parameter per line
Loading history...
Coding Style introduced by
The closing parenthesis of a multi-line function declaration must be on a new line
Loading history...
Coding Style introduced by
Multi-line function declaration not indented correctly; expected 8 spaces but found 38
Loading history...
135
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
136
        // Symfony 2.2+
137
        if (method_exists($this, 'validate')) {
138
            if (isset($mapping['pattern'])) {
139
                if (isset($mapping['path'])) {
140
                    throw new \InvalidArgumentException(sprintf(
141
                        'The file "%s" cannot define both a "path" and a "pattern" attribute. Use only "path".',
142
                        $path
143
                    ));
144
                }
145
146
                $mapping['path'] = $mapping['pattern'];
147
                unset($mapping['pattern']);
148
            }
149
150
            $this->validate($mapping, $shortname, $path);
151
        // Symfony 2.0, 2.1
152
        } else {
153
            foreach ($mapping as $key => $value) {
154
                if (!in_array($key, $expected = array(
155
                    'type', 'resource', 'prefix', 'pattern', 'options',
156
                    'defaults', 'requirements'
157
                ))) {
158
                    throw new \InvalidArgumentException(sprintf(
159
                        'Yaml routing loader does not support given key: "%s". Expected one of the (%s).',
160
                        $key, implode(', ', $expected)
161
                    ));
162
                }
163
            }
164
        }
165
166
        if (isset($mapping['resource'])) {
167
            // Symfony 2.2+
168
            if (method_exists($this, 'parseImport')) {
169
                $this->parseImport($collection, $mapping, $path, $file);
170
            // Symfony 2.1
171
            } else {
172
                $getOr = function ($key, $def) use ($mapping) {
173
                    return isset($mapping[$key]) ? $mapping[$key] : $def;
174
                };
175
176
                $type         = $getOr('type', null);
177
                $prefix       = $getOr('prefix', null);
178
                $defaults     = $getOr('defaults', array());
179
                $requirements = $getOr('requirements', array());
180
                $options      = $getOr('options', array());
181
182
                $this->setCurrentDir(dirname($path));
183
                $resourceCollection = $this->import($mapping['resource'], $type, false, $file);
184
                $resourceCollection->addPrefix($prefix, $defaults, $requirements);
185
                $resourceCollection->addOptions($options);
186
                $collection->addCollection($resourceCollection);
187
            }
188
        } else {
189
            $this->parseRoute($collection, $shortname, $mapping, $path);
190
        }
191
    }
192
193
    private function getDefaultsFromMapping($mapping, $routeType = 'collections')
194
    {
195
        $defaults = array();
196
197
        if (!is_array($mapping)) {
198
            return $defaults;
199
        }
200
201
        if (isset($mapping['defaults'])) {
202
            $defaults = $mapping['defaults'];
203
        }
204
205
        if (isset($mapping[$routeType]) && is_array($mapping[$routeType])) {
206
            if (isset($mapping[$routeType]['defaults'])) {
207
                $defaults = array_merge($defaults, $mapping[$routeType]['defaults']);
208
            }
209
        }
210
211
        return $defaults;
212
    }
213
214
    private function getRequirementsFromMapping($mapping, $routeType = 'collections')
215
    {
216
        $requirements = array();
217
218
        if (!is_array($mapping)) {
219
            return $requirements;
220
        }
221
222
        if (isset($mapping['requirements'])) {
223
            $requirements = $mapping['requirements'];
224
        }
225
226
        if (isset($mapping[$routeType]) && is_array($mapping[$routeType])) {
227
            if (isset($mapping[$routeType]['requirements'])) {
228
                $requirements = array_merge($requirements, $mapping[$routeType]['requirements']);
229
            }
230
        }
231
232
        return $requirements;
233
    }
234
235
    private function getOptionsFromMapping($mapping, $routeType = 'collections')
236
    {
237
        $options = array();
238
239
        if (!is_array($mapping)) {
240
            return $options;
241
        }
242
243
        if (isset($mapping['options'])) {
244
            $options = $mapping['options'];
245
        }
246
247
        if (isset($mapping[$routeType]) && is_array($mapping[$routeType])) {
248
            if (isset($mapping[$routeType]['options'])) {
249
                $options = array_merge($options, $mapping[$routeType]['options']);
250
            }
251
        }
252
253
        return $options;
254
    }
255
256
    private function getCollectionRoutesFromMapping($shortname, $mapping, $bundle, $class)
257
    {
258
        $defaults = $this->getDefaultCollectionRoutes($bundle, $class);
259
        if (!is_array($mapping) || !isset($mapping['collections'])) {
260
            return $defaults;
261
        }
262
263
        $collections = $mapping['collections'];
264
        unset($collections['defaults']);
265
        unset($collections['requirements']);
266
        unset($collections['options']);
267
268
        if (0 == count($collections)) {
269
            return $defaults;
270
        }
271
272
        if (false === $collections) {
273
            return array();
274
        }
275
276
        $routes = array();
277
        foreach ($collections as $action => $params) {
278
            if (is_integer($action)) {
279
                $action = $params;
280
                $params = null;
281
            }
282
283
            $routeName = $this->getRouteName($bundle, $class, $action);
284
            if (isset($defaults[$routeName])) {
285
                $route = $defaults[$routeName];
286
            } else {
287
                $route = $this->getCustomCollectionRoute($bundle, $class, $action);
288
            }
289
290
            $this->overrideRouteParams($shortname, $route, $params);
291
292
            $routes[$routeName] = $route;
293
        }
294
295
        return $routes;
296
    }
297
298
    private function getResourceRoutesFromMapping($shortname, $mapping, $bundle, $class)
299
    {
300
        $defaults = $this->getDefaultResourceRoutes($bundle, $class);
301
        if (!is_array($mapping) || !isset($mapping['resources'])) {
302
            return $defaults;
303
        }
304
305
        $resources = $mapping['resources'];
306
        unset($resources['defaults']);
307
        unset($resources['requirements']);
308
        unset($resources['options']);
309
310
        if (0 == count($resources)) {
311
            return $defaults;
312
        }
313
314
        if (false === $resources) {
315
            return array();
316
        }
317
318
        $routes = array();
319
        foreach ($resources as $action => $params) {
320
            if (is_integer($action)) {
321
                $action = $params;
322
                $params = null;
323
            }
324
325
            $routeName = $this->getRouteName($bundle, $class, $action);
326
            if (isset($defaults[$routeName])) {
327
                $route = $defaults[$routeName];
328
            } else {
329
                $route = $this->getCustomResourceRoute($bundle, $class, $action);
330
            }
331
332
            $this->overrideRouteParams($shortname, $route, $params);
333
334
            $routes[$routeName] = $route;
335
        }
336
337
        return $routes;
338
    }
339
340
    private function overrideRouteParams($shortname, Route $route, $params)
341
    {
342
        if (is_array($params)) {
343
            foreach ($params as $key => $val) {
344
                if (in_array($key, self::$supportedActionKeys)) {
345
                    continue;
346
                }
347
348
                throw new \InvalidArgumentException(sprintf(
349
                    '`%s` parameter is not supported by `%s` action route. Use one of [%s].',
350
                    $key, $shortname, implode(', ', self::$supportedActionKeys)
351
                ));
352
            }
353
        }
354
355
        if (is_string($params)) {
356
            $route->setPattern($params);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Routing\Route::setPattern() has been deprecated with message: since version 2.2, to be removed in 3.0. Use setPath instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
357
        }
358
        if (is_array($params)) {
359
            if (isset($params['pattern'])) {
360
                $route->setPattern($params['pattern']);
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Component\Routing\Route::setPattern() has been deprecated with message: since version 2.2, to be removed in 3.0. Use setPath instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
361
            }
362
            if (isset($params['defaults'])) {
363
                $route->setDefaults(array_merge(
364
                    $route->getDefaults(), $params['defaults']
365
                ));
366
            }
367
            if (isset($params['requirements'])) {
368
                $route->setRequirements($params['requirements']);
369
            }
370
            if (isset($params['options'])) {
371
                $route->setOptions($params['options']);
372
            }
373
            if (isset($params['methods'])) {
374
                $route->setMethods($params['methods']);
375
            }
376
        }
377
    }
378
379
    private function getDefaultCollectionRoutes($bundle, $class)
380
    {
381
        return array(
382
            $this->getRouteName($bundle, $class, 'index') => new Route(
383
                '/',
384
                array('_controller' => sprintf('%s:%s:index', $bundle, $class)),
385
                array('_method' => 'GET')
386
            ),
387
            $this->getRouteName($bundle, $class, 'new') => new Route(
388
                '/new',
389
                array('_controller' => sprintf('%s:%s:new', $bundle, $class)),
390
                array('_method' => 'GET')
391
            ),
392
            $this->getRouteName($bundle, $class, 'create') => new Route(
393
                '/',
394
                array('_controller' => sprintf('%s:%s:new', $bundle, $class)),
395
                array('_method' => 'POST')
396
            ),
397
        );
398
    }
399
400
    private function getCustomCollectionRoute($bundle, $class, $action)
401
    {
402
        return new Route(
403
            '/'.$action,
404
            array('_controller' => sprintf('%s:%s:%s', $bundle, $class, $action)),
405
            array('_method' => 'GET')
406
        );
407
    }
408
409
    private function getDefaultResourceRoutes($bundle, $class)
410
    {
411
        return array(
412
            $this->getRouteName($bundle, $class, 'show') => new Route(
413
                '/{id}',
414
                array('_controller' => sprintf('%s:%s:show', $bundle, $class)),
415
                array('_method' => 'GET')
416
            ),
417
            $this->getRouteName($bundle, $class, 'edit') => new Route(
418
                '/{id}/edit',
419
                array('_controller' => sprintf('%s:%s:edit', $bundle, $class)),
420
                array('_method' => 'GET')
421
            ),
422
            $this->getRouteName($bundle, $class, 'update') => new Route(
423
                '/{id}',
424
                array('_controller' => sprintf('%s:%s:edit', $bundle, $class)),
425
                array('_method' => 'PUT')
426
            ),
427
            $this->getRouteName($bundle, $class, 'delete') => new Route(
428
                '/{id}',
429
                array('_controller' => sprintf('%s:%s:delete', $bundle, $class)),
430
                array('_method' => 'DELETE')
431
            ),
432
        );
433
    }
434
435
    private function getCustomResourceRoute($bundle, $class, $action)
436
    {
437
        return new Route(
438
            '/{id}/'.$action,
439
            array('_controller' => sprintf('%s:%s:%s', $bundle, $class, $action)),
440
            array('_method' => 'PUT')
441
        );
442
    }
443
444
    private function getPatternPrefix($class, $mapping)
445
    {
446
        if (is_string($mapping)) {
447
            return $mapping;
448
        } elseif (is_array($mapping) && isset($mapping['prefix'])) {
449
            return $mapping['prefix'];
450
        }
451
452
        return '/'.strtolower(str_replace('\\', '/', $class));
453
    }
454
455
    private function getRouteName($bundle, $class, $action)
456
    {
457
        $group = implode('_', array_map('lcfirst', explode('\\', $class)));
458
459
        return sprintf('%s_%s_%s', lcfirst($bundle), $group, lcfirst($action));
460
    }
461
}
462