PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Routing/Loader/ConventionalLoader.php

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