From 4f0227c5b2610ce8450e0bcaeacb670eaabb78fe Mon Sep 17 00:00:00 2001 From: Simon Groenewolt Date: Sat, 19 Feb 2022 21:26:35 +0100 Subject: [PATCH 1/3] Move content-type and taxonomy routing config from Kernel + annotations to a RouteLoader --- config/routes.yaml | 8 + config/services.yaml | 3 + src/Configuration/Config.php | 2 +- .../Backend/ContentEditController.php | 15 +- src/Controller/Frontend/DetailController.php | 12 +- src/Controller/Frontend/ListingController.php | 12 +- .../Frontend/TaxonomyController.php | 14 +- src/Kernel.php | 44 +--- src/Routing/DynamicRouteLoader.php | 215 ++++++++++++++++++ 9 files changed, 237 insertions(+), 88 deletions(-) create mode 100644 src/Routing/DynamicRouteLoader.php diff --git a/config/routes.yaml b/config/routes.yaml index 920ff8ec7..743a0d478 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -16,6 +16,14 @@ frontend: prefix: / type: annotation +# +# Special loader for dynamic routes based on names of content types and taxonomies. +# Both for front-end and for the admin. +# +dynamic_routes: + resource: 'bolt.dynamic_route_loader::loadRoutes' + type: service + # ------------------------------------------------------------------------------ # Place your own routes here, that have a LOWER priority than the default routes. diff --git a/config/services.yaml b/config/services.yaml index 427490c27..1d7905dbd 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -170,3 +170,6 @@ services: decorates: 'api_platform.doctrine.orm.data_persister' Symfony\Component\DependencyInjection\ContainerInterface: '@service_container' + + bolt.dynamic_route_loader: + class: Bolt\Routing\DynamicRouteLoader diff --git a/src/Configuration/Config.php b/src/Configuration/Config.php index afd6033c1..83596602c 100644 --- a/src/Configuration/Config.php +++ b/src/Configuration/Config.php @@ -82,7 +82,7 @@ private function getConfig(): Collection $this->cache->delete(self::CACHE_KEY); [$data] = $this->getCache(); - // Clear the entire cache in order to re-generate %bolt.requirement.contenttypes% + // Clear the entire cache in order to re-generate routing table $this->clearCacheController->clearcache($this->kernel); } } diff --git a/src/Controller/Backend/ContentEditController.php b/src/Controller/Backend/ContentEditController.php index 4198d522a..0a7d65b79 100644 --- a/src/Controller/Backend/ContentEditController.php +++ b/src/Controller/Backend/ContentEditController.php @@ -24,6 +24,7 @@ use Bolt\Repository\MediaRepository; use Bolt\Repository\RelationRepository; use Bolt\Repository\TaxonomyRepository; +use Bolt\Routing\DynamicRouteLoader; use Bolt\Security\ContentVoter; use Bolt\Utils\TranslationsManager; use Bolt\Validator\ContentValidatorInterface; @@ -133,25 +134,15 @@ public function edit(Content $content): Response } /** - * @Route( - * "/edit/{_locale}/{contentTypeSlug}/{slugOrId}", - * name="bolt_edit_content_slug", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, - * methods={"GET"}) - * @Route( - * "/edit/{contentTypeSlug}/{slugOrId}", - * name="bolt_edit_content_slug", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, - * methods={"GET"}) + * @see DynamicRouteLoader for content type based routes to this method. + * * @Route( * "/edit/{slugOrId}", * name="bolt_edit_content_slug", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, * methods={"GET"}) * @Route( * "/edit/{_locale}/{slugOrId}", * name="bolt_edit_content_slug", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, * methods={"GET"}) */ public function editFromSlug(?string $contentTypeSlug = null, $slugOrId): Response diff --git a/src/Controller/Frontend/DetailController.php b/src/Controller/Frontend/DetailController.php index 2a1d8d3e9..3f72a4baf 100644 --- a/src/Controller/Frontend/DetailController.php +++ b/src/Controller/Frontend/DetailController.php @@ -7,6 +7,7 @@ use Bolt\Configuration\Content\ContentType; use Bolt\Controller\TwigAwareController; use Bolt\Repository\ContentRepository; +use Bolt\Routing\DynamicRouteLoader; use Bolt\Utils\ContentHelper; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -26,16 +27,7 @@ public function __construct(ContentRepository $contentRepository, ContentHelper } /** - * @Route( - * "/{contentTypeSlug}/{slugOrId}", - * name="record", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, - * methods={"GET|POST"}) - * @Route( - * "/{_locale}/{contentTypeSlug}/{slugOrId}", - * name="record_locale", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%", "_locale": "%app_locales%"}, - * methods={"GET|POST"}) + * @see DynamicRouteLoader for routes to this method. * * @param string|int $slugOrId */ diff --git a/src/Controller/Frontend/ListingController.php b/src/Controller/Frontend/ListingController.php index 07f07857d..fd195424e 100644 --- a/src/Controller/Frontend/ListingController.php +++ b/src/Controller/Frontend/ListingController.php @@ -9,6 +9,7 @@ use Bolt\Controller\TwigAwareController; use Bolt\Entity\Content; use Bolt\Repository\ContentRepository; +use Bolt\Routing\DynamicRouteLoader; use Bolt\Storage\Query; use Pagerfanta\Adapter\ArrayAdapter; use Pagerfanta\Pagerfanta; @@ -28,16 +29,7 @@ public function __construct(Query $query) } /** - * @Route( - * "/{contentTypeSlug}", - * name="listing", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, - * methods={"GET|POST"}) - * @Route( - * "/{_locale}/{contentTypeSlug}", - * name="listing_locale", - * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%", "_locale": "%app_locales%"}, - * methods={"GET|POST"}) + * @see DynamicRouteLoader for routes to this method. */ public function listing(ContentRepository $contentRepository, string $contentTypeSlug, ?string $_locale = null): Response { diff --git a/src/Controller/Frontend/TaxonomyController.php b/src/Controller/Frontend/TaxonomyController.php index c6d18edb5..69279fbc9 100644 --- a/src/Controller/Frontend/TaxonomyController.php +++ b/src/Controller/Frontend/TaxonomyController.php @@ -8,24 +8,14 @@ use Bolt\Controller\TwigAwareController; use Bolt\Entity\Content; use Bolt\Repository\ContentRepository; +use Bolt\Routing\DynamicRouteLoader; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class TaxonomyController extends TwigAwareController implements FrontendZoneInterface { /** - * @Route( - * "/{taxonomyslug}/{slug}", - * name="taxonomy", - * requirements={"taxonomyslug"="%bolt.requirement.taxonomies%"}, - * methods={"GET|POST"} - * ) - * @Route( - * "/{_locale}/{taxonomyslug}/{slug}", - * name="taxonomy_locale", - * requirements={"taxonomyslug"="%bolt.requirement.taxonomies%", "_locale": "%app_locales%"}, - * methods={"GET|POST"} - * ) + * @see DynamicRouteLoader for routes to this method. */ public function listing(ContentRepository $contentRepository, string $taxonomyslug, string $slug): Response { diff --git a/src/Kernel.php b/src/Kernel.php index 8defe5c86..277fee974 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -4,8 +4,6 @@ namespace Bolt; -use Bolt\Configuration\Parser\ContentTypesParser; -use Bolt\Configuration\Parser\TaxonomyParser; use Bolt\Extension\ExtensionCompilerPass; use Bolt\Extension\ExtensionInterface; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; @@ -17,7 +15,6 @@ use Symfony\Component\HttpKernel\Kernel as BaseKernel; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; use Symfony\Component\Yaml\Yaml; -use Tightenco\Collect\Support\Collection; class Kernel extends BaseKernel { @@ -74,8 +71,7 @@ protected function configureContainer(ContainerBuilder $container, LoaderInterfa $loader->load($confDir . '/{services}_' . $this->environment . self::CONFIG_EXTS, 'glob'); $this->setBoltParameters($container, $confDir); - $this->setContentTypeRequirements($container); - $this->setTaxonomyRequirements($container); + $container->setParameter('bolt.requirement.contenttypes', "Dummy value so Checks.php in bobdenotter/configuration-notices doesn't break"); } protected function configureRoutes(RoutingConfigurator $routes): void @@ -121,44 +117,6 @@ private function flattenKeys(array $array, string $prefix = ''): array return $result; } - /** - * Set the ContentType requirements that are used in Routing. - * Note: this functionality is partially duplicated in \Bolt\Configuration\Config. - * - * @throws \Exception - */ - private function setContentTypeRequirements(ContainerBuilder $container): void - { - /** @var string $defaultLocale */ - $defaultLocale = $container->getParameter('locale'); - $ContentTypesParser = new ContentTypesParser($this->getProjectDir(), new Collection(), $defaultLocale); - $contentTypes = $ContentTypesParser->parse(); - - $pluralslugs = $contentTypes->pluck('slug')->implode('|'); - $slugs = $contentTypes->pluck('slug')->concat($contentTypes->pluck('singular_slug'))->unique()->implode('|'); - - $container->setParameter('bolt.requirement.pluralcontenttypes', $pluralslugs); - $container->setParameter('bolt.requirement.contenttypes', $slugs); - } - - /** - * Set the Taxonomy requirements that are used in Routing. - * Note: this functionality is partially duplicated in \Bolt\Configuration\Config. - * - * @throws \Exception - */ - private function setTaxonomyRequirements(ContainerBuilder $container): void - { - $taxonomyParser = new TaxonomyParser($this->getProjectDir()); - $taxonomies = $taxonomyParser->parse(); - - $pluralslugs = $taxonomies->pluck('slug')->implode('|'); - $slugs = $taxonomies->pluck('slug')->concat($taxonomies->pluck('singular_slug'))->unique()->implode('|'); - - $container->setParameter('bolt.requirement.pluraltaxonomies', $pluralslugs); - $container->setParameter('bolt.requirement.taxonomies', $slugs); - } - /** * Return the public folder of this project. This implementation locates the public folder * for this project by checking for the following candidates in the project dir: 'public', diff --git a/src/Routing/DynamicRouteLoader.php b/src/Routing/DynamicRouteLoader.php new file mode 100644 index 000000000..0d2953c26 --- /dev/null +++ b/src/Routing/DynamicRouteLoader.php @@ -0,0 +1,215 @@ +config = $config; + $this->locales = $locales; + } + + public function loadRoutes(): RouteCollection + { + $routes = new RouteCollection(); + + $contentTypes = $this->config->get('contenttypes'); + $contentTypeSlugs = $contentTypes + ->pluck('slug') + ->concat($contentTypes->pluck('singular_slug')) + ->unique() + ->implode('|'); + + $taxonomies = $this->config->get('taxonomies'); + $taxonomySlugs = $taxonomies + ->pluck('slug') + ->concat($taxonomies->pluck('singular_slug')) + ->unique() + ->implode('|'); + + // ListingController::listing (front-end) + /* + * OLD method annotations, for reference, not in use. + * + * @Route( + * "/{contentTypeSlug}", + * name="listing", + * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, + * methods={"GET|POST"}) + * @Route( + * "/{_locale}/{contentTypeSlug}", + * name="listing_locale", + * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%", "_locale": "%app_locales%"}, + * methods={"GET|POST"}) + */ + $listingControllerDefaults = [ + '_controller' => ListingController::class . '::listing', + ]; + $routes->add('listing', + (new Route('/{contentTypeSlug}', + $listingControllerDefaults, + [ + 'contentTypeSlug' => $contentTypeSlugs + ]) + )->setMethods(['GET', 'POST']) + ); + + $routes->add('listing_locale', + (new Route( + '/{_locale}/{contentTypeSlug}/{slugOrId}', + $listingControllerDefaults, + [ + 'contentTypeSlug' => $contentTypeSlugs, + '_locale' => $this->locales, + ]) + )->setMethods(['GET', 'POST']) + ); + + // DetailController::record() (front-end) + /* + * OLD method annotations, for reference, not in use. + * + * @Route( + * "/{contentTypeSlug}/{slugOrId}", + * name="record", + * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, + * methods={"GET|POST"}) + * @Route( + * "/{_locale}/{contentTypeSlug}/{slugOrId}", + * name="record_locale", + * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%", "_locale": "%app_locales%"}, + * methods={"GET|POST"}) + */ + $datailControllerDefaults = [ + '_controller' => DetailController::class . '::record', + ]; + $routes->add('record', + (new Route( + '/{contentTypeSlug}/{slugOrId}', + $datailControllerDefaults, + [ + 'contentTypeSlug' => $contentTypeSlugs + ]) + )->setMethods(['GET', 'POST']) + ); + $routes->add( + 'record_locale', + (new Route( + '/{_locale}/{contentTypeSlug}/{slugOrId}', + $datailControllerDefaults, + [ + 'contentTypeSlug' => $contentTypeSlugs, + '_locale' => $this->locales, + ]) + )->setMethods(['GET', 'POST']) + ); + + // TaxonomyController::listing() (front-end) + /* + * OLD method annotations, for reference, not in use. + * + * @Route( + * "/{taxonomyslug}/{slug}", + * name="taxonomy", + * requirements={"taxonomyslug"="%bolt.requirement.taxonomies%"}, + * methods={"GET|POST"} + * ) + * @Route( + * "/{_locale}/{taxonomyslug}/{slug}", + * name="taxonomy_locale", + * requirements={"taxonomyslug"="%bolt.requirement.taxonomies%", "_locale": "%app_locales%"}, + * methods={"GET|POST"} + * ) + */ + $taxonomyControllerDefaults = [ + '_controller' => TaxonomyController::class . '::listing', + ]; + $routes->add('taxonomy', + (new Route( + '/{taxonomyslug}/{slug}', + $taxonomyControllerDefaults, + [ + 'taxonomyslug' => $taxonomySlugs + ]) + )->setMethods(['GET', 'POST']) + ); + $routes->add( + 'taxonomy_locale', + (new Route( + '/{_locale}/{taxonomyslug}/{slug}', + $taxonomyControllerDefaults, + [ + 'taxonomyslug' => $taxonomySlugs, + '_locale' => $this->locales, + ]) + )->setMethods(['GET', 'POST']) + ); + + // routes for ContentEditController::editFromSlug + /* + * OLD method annotations, for reference, not in use. + * + * @Route( + * "/edit/{_locale}/{contentTypeSlug}/{slugOrId}", + * name="bolt_edit_content_slug", + * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, + * methods={"GET"}) + * @Route( + * "/edit/{contentTypeSlug}/{slugOrId}", + * name="bolt_edit_content_slug", + * requirements={"contentTypeSlug"="%bolt.requirement.contenttypes%"}, + * methods={"GET"}) + */ + $contentEditControllerDefaults = [ + '_controller' => ContentEditController::class . '::editFromSlug', + ]; + $routes->add('taxonomy', + (new Route( + '/{taxonomyslug}/{slug}', + $contentEditControllerDefaults, + [ + ['contentTypeSlug' => $contentTypeSlugs] + ]) + )->setMethods(['GET', 'POST']) + ); + $routes->add( + 'taxonomy_locale', + (new Route( + '/{_locale}/{taxonomyslug}/{slug}', + $contentEditControllerDefaults, + [ + 'contentTypeSlug' => $contentTypeSlugs, + '_locale' => '.*', + ]) + )->setMethods(['GET', 'POST']) + ); + + return $routes; + } +} From 4f2cdb10dabedacd5b9d8f689f60d78af11f4fae Mon Sep 17 00:00:00 2001 From: Simon Groenewolt Date: Sat, 19 Feb 2022 21:48:50 +0100 Subject: [PATCH 2/3] remove useless doc --- src/Routing/DynamicRouteLoader.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Routing/DynamicRouteLoader.php b/src/Routing/DynamicRouteLoader.php index 0d2953c26..07c487e5f 100644 --- a/src/Routing/DynamicRouteLoader.php +++ b/src/Routing/DynamicRouteLoader.php @@ -26,7 +26,6 @@ class DynamicRouteLoader implements RouteLoaderInterface private $locales; /** - * @param Config $config * @param string $locales '|' separated string of locales, see services.yaml */ public function __construct(Config $config, string $locales) From 37bfc2e2e7cd2f897493b9bb9b097bd014c28920 Mon Sep 17 00:00:00 2001 From: Simon Groenewolt Date: Sun, 20 Feb 2022 12:28:04 +0100 Subject: [PATCH 3/3] fix error in route definition (accidental extra []) --- src/Routing/DynamicRouteLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Routing/DynamicRouteLoader.php b/src/Routing/DynamicRouteLoader.php index 07c487e5f..88979b194 100644 --- a/src/Routing/DynamicRouteLoader.php +++ b/src/Routing/DynamicRouteLoader.php @@ -193,7 +193,7 @@ public function loadRoutes(): RouteCollection '/{taxonomyslug}/{slug}', $contentEditControllerDefaults, [ - ['contentTypeSlug' => $contentTypeSlugs] + 'contentTypeSlug' => $contentTypeSlugs ]) )->setMethods(['GET', 'POST']) );