* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\MainBundle\CRUD\Routing; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\HttpFoundation\Request; use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\CRUDController; /** * Class CRUDRoutesLoader * Load the route for CRUD * * @package Chill\MainBundle\CRUD\Routing */ class CRUDRoutesLoader extends Loader { protected array $crudConfig = []; protected array $apiCrudConfig = []; /** * @var bool */ private $isLoaded = false; private const ALL_SINGLE_METHODS = [ Request::METHOD_GET, Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_DELETE ]; private const ALL_INDEX_METHODS = [ Request::METHOD_GET, Request::METHOD_HEAD ]; /** * CRUDRoutesLoader constructor. * * @param $crudConfig the config from cruds * @param $apicrudConfig the config from api_crud */ public function __construct(array $crudConfig, array $apiConfig) { $this->crudConfig = $crudConfig; $this->apiConfig = $apiConfig; } /** * @param mixed $resource * @param null $type * @return bool */ public function supports($resource, $type = null) { return 'CRUD' === $type; } /** * Load routes for CRUD and CRUD Api */ public function load($resource, $type = null): RouteCollection { if (true === $this->isLoaded) { throw new \RuntimeException('Do not add the "CRUD" loader twice'); } $collection = new RouteCollection(); foreach ($this->crudConfig as $crudConfig) { $collection->addCollection($this->loadCrudConfig($crudConfig)); } foreach ($this->apiConfig as $crudConfig) { $collection->addCollection($this->loadApi($crudConfig)); } return $collection; } /** * Load routes for CRUD (without api) * * @param $crudConfig * @return RouteCollection */ protected function loadCrudConfig($crudConfig): RouteCollection { $collection = new RouteCollection(); $controller ='cscrud_'.$crudConfig['name'].'_controller'; foreach ($crudConfig['actions'] as $name => $action) { // defaults (controller name) $defaults = [ '_controller' => $controller.':'.($action['controller_action'] ?? $name) ]; if ($name === 'index') { $path = "{_locale}".$crudConfig['base_path']; $route = new Route($path, $defaults); } elseif ($name === 'new') { $path = "{_locale}".$crudConfig['base_path'].'/'.$name; $route = new Route($path, $defaults); } else { $path = "{_locale}".$crudConfig['base_path'].($action['path'] ?? '/{id}/'.$name); $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; $route = new Route($path, $defaults, $requirements); } $collection->add('chill_crud_'.$crudConfig['name'].'_'.$name, $route); } return $collection; } /** * Load routes for api single * * @param $crudConfig * @return RouteCollection */ protected function loadApi(array $crudConfig): RouteCollection { $collection = new RouteCollection(); $controller = 'csapi_'.$crudConfig['name'].'_controller'; foreach ($crudConfig['actions'] as $name => $action) { // filter only on single actions $singleCollection = $action['single_collection'] ?? $name === '_entity' ? 'single' : NULL; if ('collection' === $singleCollection) { // continue; } // compute default action switch ($name) { case '_entity': $controllerAction = 'entityApi'; break; case '_index': $controllerAction = 'indexApi'; break; default: $controllerAction = $name.'Api'; break; } $defaults = [ '_controller' => $controller.':'.($action['controller_action'] ?? $controllerAction) ]; // path are rewritten // if name === 'default', we rewrite it to nothing :-) $localName = \in_array($name, [ '_entity', '_index' ]) ? '' : '/'.$name; if ('collection' === $action['single_collection'] || '_index' === $name) { $localPath = $action['path'] ?? $localName.'.{_format}'; } else { $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}'; } $path = $crudConfig['base_path'].$localPath; $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; $methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; }, ARRAY_FILTER_USE_BOTH)); if (count($methods) === 0) { throw new \RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", ". "does not have any allowed methods. You should remove this action from the config ". "or allow, at least, one method"); } if ('_entity' === $name && \in_array(Request::METHOD_POST, $methods)) { unset($methods[\array_search(Request::METHOD_POST, $methods)]); $entityPostRoute = $this->createEntityPostRoute($name, $crudConfig, $action, $controller); $collection->add("chill_api_single_{$crudConfig['name']}_{$name}_create", $entityPostRoute); } if (count($methods) === 0) { // the only method was POST, // continue to next continue; } $route = new Route($path, $defaults, $requirements); $route->setMethods($methods); $collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route); } return $collection; } private function createEntityPostRoute(string $name, $crudConfig, array $action, $controller): Route { $localPath = $action['path'].'.{_format}'; $defaults = [ '_controller' => $controller.':'.($action['controller_action'] ?? 'entityPost') ]; $path = $crudConfig['base_path'].$localPath; $requirements = $action['requirements'] ?? []; $route = new Route($path, $defaults, $requirements); $route->setMethods([ Request::METHOD_POST ]); return $route; } }