From de74794acfceb1d793d0a7686caaed6d3f5ae9a5 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Thu, 21 Mar 2019 18:57:02 +0100 Subject: [PATCH] [FrameworkBundle][Routing] added Configurators to handle template and redirect controllers --- .../Bundle/FrameworkBundle/CHANGELOG.md | 7 +- .../Kernel/MicroKernelTrait.php | 4 +- .../Resources/config/routing.xml | 2 +- .../Configurator/GoneRouteConfigurator.php | 33 +++++ .../RedirectRouteConfigurator.php | 63 ++++++++++ .../Loader/Configurator/RouteConfigurator.php | 73 +++++++++++ .../Configurator/RoutingConfigurator.php | 20 +++ .../TemplateRouteConfigurator.php | 53 ++++++++ .../Loader/Configurator/Traits/AddTrait.php | 46 +++++++ .../UrlRedirectRouteConfigurator.php | 62 ++++++++++ .../Routing/Loader/PhpFileLoader.php | 31 +++++ .../Resources/config/routing/routes.php | 45 +++++++ .../Routing/Loader/AbstractLoaderTest.php | 116 ++++++++++++++++++ .../Routing/Loader/PhpFileLoaderTest.php | 28 +++++ .../Bundle/FrameworkBundle/composer.json | 1 + src/Symfony/Component/Routing/CHANGELOG.md | 1 + .../Configurator/ImportConfigurator.php | 35 +----- .../Loader/Configurator/RouteConfigurator.php | 2 +- .../Loader/Configurator/Traits/AddTrait.php | 60 ++------- .../Traits/LocalizedRouteTrait.php | 74 +++++++++++ .../Configurator/Traits/PrefixTrait.php | 59 +++++++++ .../Loader/Configurator/Traits/RouteTrait.php | 2 +- .../Routing/Loader/PhpFileLoader.php | 14 ++- .../Routing/Loader/XmlFileLoader.php | 87 +++++-------- .../Routing/Loader/YamlFileLoader.php | 75 +++-------- 25 files changed, 788 insertions(+), 205 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/GoneRouteConfigurator.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RedirectRouteConfigurator.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RouteConfigurator.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RoutingConfigurator.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/TemplateRouteConfigurator.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/Traits/AddTrait.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/UrlRedirectRouteConfigurator.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Loader/PhpFileLoader.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/config/routing/routes.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/AbstractLoaderTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/PhpFileLoaderTest.php create mode 100644 src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php create mode 100644 src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 2da2363dab20..a3478c0b4fd4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.1.0 ----- + * Added `Routing\Loader` and `Routing\Loader\Configurator` namespaces to ease defining routes with default controllers * Added the `framework.router.context` configuration node to configure the `RequestContext` * Made `MicroKernelTrait::configureContainer()` compatible with `ContainerConfigurator` * Added a new `mailer.message_bus` option to configure or disable the message bus to use to send mails. @@ -29,7 +30,7 @@ CHANGELOG * Removed the `translator.selector` and `session.save_listener` services * Removed `SecurityUserValueResolver`, use `UserValueResolver` instead * Removed `routing.loader.service`. - * Service route loaders must be tagged with `routing.route_loader`. + * Service route loaders must be tagged with `routing.route_loader`. * Added `slugger` service and `SluggerInterface` alias * Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services. * Removed the `router.cache_class_prefix` parameter. @@ -81,8 +82,8 @@ CHANGELOG options if you're using Symfony's serializer. * [BC Break] Removed the `framework.messenger.routing.send_and_handle` configuration. Instead of setting it to true, configure a `SyncTransport` and route messages to it. - * Added information about deprecated aliases in `debug:autowiring` - * Added php ini session options `sid_length` and `sid_bits_per_character` + * Added information about deprecated aliases in `debug:autowiring` + * Added php ini session options `sid_length` and `sid_bits_per_character` to the `session` section of the configuration * Added support for Translator paths, Twig paths in translation commands. * Added support for PHP files with translations in translation commands. diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index d1becd6d643c..bd95b4db46cf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -11,14 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Kernel; +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\RoutingConfigurator; +use Symfony\Bundle\FrameworkBundle\Routing\Loader\PhpFileLoader as RoutingPhpFileLoader; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\AbstractConfigurator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader as ContainerPhpFileLoader; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; -use Symfony\Component\Routing\Loader\PhpFileLoader as RoutingPhpFileLoader; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollectionBuilder; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index bf739e71ab46..3482321a48ff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -25,7 +25,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/GoneRouteConfigurator.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/GoneRouteConfigurator.php new file mode 100644 index 000000000000..d008b7559d1e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/GoneRouteConfigurator.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\Traits\AddTrait; +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; + +/** + * @author Nicolas Grekas + */ +class GoneRouteConfigurator extends RouteConfigurator +{ + use AddTrait; + + /** + * @param bool $permanent Whether the redirection is permanent + * + * @return $this + */ + final public function permanent(bool $permanent = true) + { + return $this->defaults(['permanent' => $permanent]); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RedirectRouteConfigurator.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RedirectRouteConfigurator.php new file mode 100644 index 000000000000..80c28bc5246b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RedirectRouteConfigurator.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\Traits\AddTrait; +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; + +/** + * @author Jules Pietri + */ +class RedirectRouteConfigurator extends RouteConfigurator +{ + use AddTrait; + + /** + * @param bool $permanent Whether the redirection is permanent + * + * @return $this + */ + final public function permanent(bool $permanent = true) + { + return $this->defaults(['permanent' => $permanent]); + } + + /** + * @param bool|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore + * + * @return $this + */ + final public function ignoreAttributes($ignoreAttributes = true) + { + return $this->defaults(['ignoreAttributes' => $ignoreAttributes]); + } + + /** + * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method + * + * @return $this + */ + final public function keepRequestMethod(bool $keepRequestMethod = true) + { + return $this->defaults(['keepRequestMethod' => $keepRequestMethod]); + } + + /** + * @param bool $keepQueryParams Whether redirect action should keep query parameters + * + * @return $this + */ + final public function keepQueryParams(bool $keepQueryParams = true) + { + return $this->defaults(['keepQueryParams' => $keepQueryParams]); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RouteConfigurator.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RouteConfigurator.php new file mode 100644 index 000000000000..5932d987479b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RouteConfigurator.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator as BaseRouteConfigurator; + +/** + * @author Jules Pietri + */ +class RouteConfigurator extends BaseRouteConfigurator +{ + /** + * @param string $template The template name + * @param array $context The template variables + */ + final public function template(string $template, array $context = []): TemplateRouteConfigurator + { + return (new TemplateRouteConfigurator($this->collection, $this->route, $this->name, $this->parentConfigurator, $this->prefixes)) + ->defaults([ + '_controller' => TemplateController::class, + 'template' => $template, + 'context' => $context, + ]) + ; + } + + /** + * @param string $route The route name to redirect to + */ + final public function redirectToRoute(string $route): RedirectRouteConfigurator + { + return (new RedirectRouteConfigurator($this->collection, $this->route, $this->name, $this->parentConfigurator, $this->prefixes)) + ->defaults([ + '_controller' => RedirectController::class.'::redirectAction', + 'route' => $route, + ]) + ; + } + + /** + * @param string $url The relative path or URL to redirect to + */ + final public function redirectToUrl(string $url): UrlRedirectRouteConfigurator + { + return (new UrlRedirectRouteConfigurator($this->collection, $this->route, $this->name, $this->parentConfigurator, $this->prefixes)) + ->defaults([ + '_controller' => RedirectController::class.'::urlRedirectAction', + 'path' => $url, + ]) + ; + } + + final public function gone(): GoneRouteConfigurator + { + return (new GoneRouteConfigurator($this->collection, $this->route, $this->name, $this->parentConfigurator, $this->prefixes)) + ->defaults([ + '_controller' => RedirectController::class.'::redirectAction', + 'route' => '', + ]) + ; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RoutingConfigurator.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RoutingConfigurator.php new file mode 100644 index 000000000000..a429e9e75a83 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/RoutingConfigurator.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\Traits\AddTrait; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator as BaseRoutingConfigurator; + +class RoutingConfigurator extends BaseRoutingConfigurator +{ + use AddTrait; +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/TemplateRouteConfigurator.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/TemplateRouteConfigurator.php new file mode 100644 index 000000000000..ea53a22e2395 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/TemplateRouteConfigurator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\Traits\AddTrait; +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; + +/** + * @author Jules Pietri + */ +class TemplateRouteConfigurator extends RouteConfigurator +{ + use AddTrait; + + /** + * @param int|null $maxAge Max age for client caching + * + * @return $this + */ + final public function maxAge(?int $maxAge) + { + return $this->defaults(['maxAge' => $maxAge]); + } + + /** + * @param int|null $sharedMaxAge Max age for shared (proxy) caching + * + * @return $this + */ + final public function sharedMaxAge(?int $sharedMaxAge) + { + return $this->defaults(['sharedAge' => $sharedMaxAge]); + } + + /** + * @param bool|null $private Whether or not caching should apply for client caches only + * + * @return $this + */ + final public function private(?bool $private = true) + { + return $this->defaults(['private' => $private]); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/Traits/AddTrait.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/Traits/AddTrait.php new file mode 100644 index 000000000000..6647cb4a2754 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/Traits/AddTrait.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\Traits; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\RouteConfigurator; +use Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator; +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator as BaseRouteConfigurator; + +trait AddTrait +{ + /** + * Adds a route. + * + * @param string|array $path the path, or the localized paths of the route + * + * @return RouteConfigurator + */ + public function add(string $name, $path): BaseRouteConfigurator + { + $parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null); + $route = $this->createLocalizedRoute($this->collection, $name, $path, $this->name, $this->prefixes); + + return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes); + } + + /** + * Adds a route. + * + * @param string|array $path the path, or the localized paths of the route + * + * @return RouteConfigurator + */ + final public function __invoke(string $name, $path): BaseRouteConfigurator + { + return $this->add($name, $path); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/UrlRedirectRouteConfigurator.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/UrlRedirectRouteConfigurator.php new file mode 100644 index 000000000000..4061d5aa0fad --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/Configurator/UrlRedirectRouteConfigurator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\Traits\AddTrait; +use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; + +/** + * @author Jules Pietri + */ +class UrlRedirectRouteConfigurator extends RouteConfigurator +{ + use AddTrait; + + /** + * @param bool $permanent Whether the redirection is permanent + * + * @return $this + */ + final public function permanent(bool $permanent = true) + { + return $this->defaults(['permanent' => $permanent]); + } + + /** + * @param string|null $scheme The URL scheme (null to keep the current one) + * @param int|null $port The HTTP or HTTPS port (null to keep the current one for the same scheme or the default configured port) + * + * @return $this + */ + final public function scheme(?string $scheme, int $port = null) + { + $this->defaults(['scheme' => $scheme]); + + if ('http' === $scheme) { + $this->defaults(['httpPort' => $port]); + } elseif ('https' === $scheme) { + $this->defaults(['httpsPort' => $port]); + } + + return $this; + } + + /** + * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method + * + * @return $this + */ + final public function keepRequestMethod(bool $keepRequestMethod = true) + { + return $this->defaults(['keepRequestMethod' => $keepRequestMethod]); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/PhpFileLoader.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/PhpFileLoader.php new file mode 100644 index 000000000000..0265612b88d7 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Loader/PhpFileLoader.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing\Loader; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\Configurator\RoutingConfigurator; +use Symfony\Component\Routing\Loader\PhpFileLoader as BasePhpFileLoader; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Jules Pietri + */ +class PhpFileLoader extends BasePhpFileLoader +{ + protected function callConfigurator(callable $result, string $path, string $file): RouteCollection + { + $collection = new RouteCollection(); + + $result(new RoutingConfigurator($collection, $this, $path, $file)); + + return $collection; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/config/routing/routes.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/config/routing/routes.php new file mode 100644 index 000000000000..eaa8affaaba1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/config/routing/routes.php @@ -0,0 +1,45 @@ +add('classic_route', '/classic'); + + $routes->add('template_route', '/static') + ->template('static.html.twig', ['foo' => 'bar']) + ->maxAge(300) + ->sharedMaxAge(100) + ->private() + ->methods(['GET']) + ->utf8() + ->condition('abc') + ; + $routes->add('redirect_route', '/redirect') + ->redirectToRoute('target_route') + ->permanent() + ->ignoreAttributes(['attr', 'ibutes']) + ->keepRequestMethod() + ->keepQueryParams() + ->schemes(['http']) + ->host('legacy') + ->utf8() + ; + $routes->add('url_redirect_route', '/redirect-url') + ->redirectToUrl('/url-target') + ->permanent() + ->scheme('http', 1) + ->keepRequestMethod() + ->host('legacy') + ->utf8() + ; + $routes->add('not_a_route', '/not-a-path') + ->gone() + ->host('legacy') + ->utf8() + ; + $routes->add('gone_route', '/gone-path') + ->gone() + ->permanent() + ->utf8() + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/AbstractLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/AbstractLoaderTest.php new file mode 100644 index 000000000000..12a832a37cdb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/AbstractLoaderTest.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Routing\Loader; + +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +abstract class AbstractLoaderTest extends TestCase +{ + /** @var LoaderInterface */ + protected $loader; + + abstract protected function getLoader(): LoaderInterface; + + abstract protected function getType(): string; + + protected function setUp(): void + { + $this->loader = $this->getLoader(); + } + + protected function tearDown(): void + { + $this->loader = null; + } + + public function getLocator(): FileLocatorInterface + { + return new FileLocator([__DIR__.'/../../Fixtures/Resources/config/routing']); + } + + public function testRoutesAreLoaded() + { + $routeCollection = $this->loader->load('routes.'.$this->getType()); + + $expectedCollection = new RouteCollection(); + + $expectedCollection->add('classic_route', (new Route('/classic'))); + + $expectedCollection->add('template_route', (new Route('/static')) + ->setDefaults([ + '_controller' => TemplateController::class, + 'context' => ['foo' => 'bar'], + 'template' => 'static.html.twig', + 'maxAge' => 300, + 'sharedAge' => 100, + 'private' => true, + ]) + ->setMethods(['GET']) + ->setOptions(['utf8' => true]) + ->setCondition('abc') + ); + $expectedCollection->add('redirect_route', (new Route('/redirect')) + ->setDefaults([ + '_controller' => RedirectController::class.'::redirectAction', + 'route' => 'target_route', + 'permanent' => true, + 'ignoreAttributes' => ['attr', 'ibutes'], + 'keepRequestMethod' => true, + 'keepQueryParams' => true, + ]) + ->setSchemes(['http']) + ->setHost('legacy') + ->setOptions(['utf8' => true]) + ); + $expectedCollection->add('url_redirect_route', (new Route('/redirect-url')) + ->setDefaults([ + '_controller' => RedirectController::class.'::urlRedirectAction', + 'path' => '/url-target', + 'permanent' => true, + 'scheme' => 'http', + 'httpPort' => 1, + 'keepRequestMethod' => true, + ]) + ->setHost('legacy') + ->setOptions(['utf8' => true]) + ); + $expectedCollection->add('not_a_route', (new Route('/not-a-path')) + ->setDefaults([ + '_controller' => RedirectController::class.'::redirectAction', + 'route' => '', + ]) + ->setHost('legacy') + ->setOptions(['utf8' => true]) + ); + $expectedCollection->add('gone_route', (new Route('/gone-path')) + ->setDefaults([ + '_controller' => RedirectController::class.'::redirectAction', + 'route' => '', + 'permanent' => true, + ]) + ->setOptions(['utf8' => true]) + ); + $expectedCollection->addResource(new FileResource(realpath( + __DIR__.'/../../Fixtures/Resources/config/routing/routes.'.$this->getType() + ))); + + $this->assertEquals($expectedCollection, $routeCollection); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/PhpFileLoaderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/PhpFileLoaderTest.php new file mode 100644 index 000000000000..196233b5d11b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/Loader/PhpFileLoaderTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Routing\Loader; + +use Symfony\Bundle\FrameworkBundle\Routing\Loader\PhpFileLoader; +use Symfony\Component\Config\Loader\LoaderInterface; + +class PhpFileLoaderTest extends AbstractLoaderTest +{ + protected function getLoader(): LoaderInterface + { + return new PhpFileLoader($this->getLocator()); + } + + protected function getType(): string + { + return 'php'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 896a9c09ee47..eddb25a3727b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -80,6 +80,7 @@ "symfony/messenger": "<4.4", "symfony/mime": "<4.4", "symfony/property-info": "<4.4", + "symfony/routing": "<5.1", "symfony/serializer": "<4.4", "symfony/stopwatch": "<4.4", "symfony/translation": "<5.0", diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index 11c285c60d35..4c04edcb2f65 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.1.0 ----- + * added the protected method `PhpFileLoader::callConfigurator()` as extension point to ease custom routing configuration * deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`. * added "priority" option to annotated routes * added argument `$priority` to `RouteCollection::add()` diff --git a/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php index f11b7957525b..37996536ed87 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Routing\Loader\Configurator; -use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; /** @@ -19,6 +18,7 @@ */ class ImportConfigurator { + use Traits\PrefixTrait; use Traits\RouteTrait; private $parent; @@ -43,38 +43,7 @@ public function __destruct() */ final public function prefix($prefix, bool $trailingSlashOnRoot = true): self { - if (!\is_array($prefix)) { - $this->route->addPrefix($prefix); - if (!$trailingSlashOnRoot) { - $rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath(); - foreach ($this->route->all() as $route) { - if ($route->getPath() === $rootPath) { - $route->setPath(rtrim($rootPath, '/')); - } - } - } - } else { - foreach ($prefix as $locale => $localePrefix) { - $prefix[$locale] = trim(trim($localePrefix), '/'); - } - foreach ($this->route->all() as $name => $route) { - if (null === $locale = $route->getDefault('_locale')) { - $this->route->remove($name); - foreach ($prefix as $locale => $localePrefix) { - $localizedRoute = clone $route; - $localizedRoute->setDefault('_locale', $locale); - $localizedRoute->setDefault('_canonical_route', $name); - $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $this->route->add($name.'.'.$locale, $localizedRoute); - } - } elseif (!isset($prefix[$locale])) { - throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); - } else { - $route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $this->route->add($name, $route); - } - } - } + $this->addPrefix($this->route, $prefix, $trailingSlashOnRoot); return $this; } diff --git a/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php index e700f8de7c13..d617403a51ce 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/RouteConfigurator.php @@ -21,7 +21,7 @@ class RouteConfigurator use Traits\AddTrait; use Traits\RouteTrait; - private $parentConfigurator; + protected $parentConfigurator; public function __construct(RouteCollection $collection, $route, string $name = '', CollectionConfigurator $parentConfigurator = null, array $prefixes = null) { diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php index 085fde4bc9f4..001e1a414398 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/AddTrait.php @@ -13,64 +13,33 @@ use Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator; use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; -use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +/** + * @author Nicolas Grekas + */ trait AddTrait { + use LocalizedRouteTrait; + /** * @var RouteCollection */ - private $collection; - - private $name = ''; - - private $prefixes; + protected $collection; + protected $name = ''; + protected $prefixes; /** * Adds a route. * * @param string|array $path the path, or the localized paths of the route */ - final public function add(string $name, $path): RouteConfigurator + public function add(string $name, $path): RouteConfigurator { - $paths = []; $parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null); + $route = $this->createLocalizedRoute($this->collection, $name, $path, $this->name, $this->prefixes); - if (\is_array($path)) { - if (null === $this->prefixes) { - $paths = $path; - } elseif ($missing = array_diff_key($this->prefixes, $path)) { - throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing)))); - } else { - foreach ($path as $locale => $localePath) { - if (!isset($this->prefixes[$locale])) { - throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); - } - - $paths[$locale] = $this->prefixes[$locale].$localePath; - } - } - } elseif (null !== $this->prefixes) { - foreach ($this->prefixes as $locale => $prefix) { - $paths[$locale] = $prefix.$path; - } - } else { - $this->collection->add($this->name.$name, $route = $this->createRoute($path)); - - return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes); - } - - $routes = new RouteCollection(); - - foreach ($paths as $locale => $path) { - $routes->add($name.'.'.$locale, $route = $this->createRoute($path)); - $this->collection->add($this->name.$name.'.'.$locale, $route); - $route->setDefault('_locale', $locale); - $route->setDefault('_canonical_route', $this->name.$name); - } - - return new RouteConfigurator($this->collection, $routes, $this->name, $parentConfigurator, $this->prefixes); + return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes); } /** @@ -78,13 +47,8 @@ final public function add(string $name, $path): RouteConfigurator * * @param string|array $path the path, or the localized paths of the route */ - final public function __invoke(string $name, $path): RouteConfigurator + public function __invoke(string $name, $path): RouteConfigurator { return $this->add($name, $path); } - - private function createRoute(string $path): Route - { - return new Route($path); - } } diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php new file mode 100644 index 000000000000..35ddbf2a9956 --- /dev/null +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/LocalizedRouteTrait.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + * @author Jules Pietri + */ +trait LocalizedRouteTrait +{ + /** + * Creates one or many routes. + * + * @param string|array $path the path, or the localized paths of the route + * + * @return Route|RouteCollection + */ + final protected function createLocalizedRoute(RouteCollection $collection, string $name, $path, string $namePrefix = '', array $prefixes = null) + { + $paths = []; + + if (\is_array($path)) { + if (null === $prefixes) { + $paths = $path; + } elseif ($missing = array_diff_key($prefixes, $path)) { + throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing)))); + } else { + foreach ($path as $locale => $localePath) { + if (!isset($prefixes[$locale])) { + throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); + } + + $paths[$locale] = $prefixes[$locale].$localePath; + } + } + } elseif (null !== $prefixes) { + foreach ($prefixes as $locale => $prefix) { + $paths[$locale] = $prefix.$path; + } + } else { + $collection->add($namePrefix.$name, $route = $this->createRoute($path)); + + return $route; + } + + $routes = new RouteCollection(); + + foreach ($paths as $locale => $path) { + $routes->add($name.'.'.$locale, $route = $this->createRoute($path)); + $collection->add($namePrefix.$name.'.'.$locale, $route); + $route->setDefault('_locale', $locale); + $route->setDefault('_canonical_route', $namePrefix.$name); + } + + return $routes; + } + + private function createRoute(string $path): Route + { + return new Route($path); + } +} diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php new file mode 100644 index 000000000000..eb329d69b33a --- /dev/null +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/PrefixTrait.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @author Nicolas Grekas + */ +trait PrefixTrait +{ + final protected function addPrefix(RouteCollection $routes, $prefix, bool $trailingSlashOnRoot) + { + if (\is_array($prefix)) { + foreach ($prefix as $locale => $localePrefix) { + $prefix[$locale] = trim(trim($localePrefix), '/'); + } + foreach ($routes->all() as $name => $route) { + if (null === $locale = $route->getDefault('_locale')) { + $routes->remove($name); + foreach ($prefix as $locale => $localePrefix) { + $localizedRoute = clone $route; + $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setDefault('_canonical_route', $name); + $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); + $routes->add($name.'.'.$locale, $localizedRoute); + } + } elseif (!isset($prefix[$locale])) { + throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); + } else { + $route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); + $routes->add($name, $route); + } + } + + return; + } + + $routes->addPrefix($prefix); + if (!$trailingSlashOnRoot) { + $rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath(); + foreach ($routes->all() as $route) { + if ($route->getPath() === $rootPath) { + $route->setPath(rtrim($rootPath, '/')); + } + } + } + } +} diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php index 04009cd16d3a..d9e8e70250f1 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php @@ -19,7 +19,7 @@ trait RouteTrait /** * @var RouteCollection|Route */ - private $route; + protected $route; /** * Adds defaults. diff --git a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php index 31fe88ddd8af..04d9df61956c 100644 --- a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php @@ -22,6 +22,8 @@ * The file must return a RouteCollection instance. * * @author Fabien Potencier + * @author Nicolas grekas + * @author Jules Pietri */ class PhpFileLoader extends FileLoader { @@ -47,8 +49,7 @@ public function load($file, string $type = null) $result = $load($path); if (\is_object($result) && \is_callable($result)) { - $collection = new RouteCollection(); - $result(new RoutingConfigurator($collection, $this, $path, $file)); + $collection = $this->callConfigurator($result, $path, $file); } else { $collection = $result; } @@ -65,6 +66,15 @@ public function supports($resource, string $type = null) { return \is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type); } + + protected function callConfigurator(callable $result, string $path, string $file): RouteCollection + { + $collection = new RouteCollection(); + + $result(new RoutingConfigurator($collection, $this, $path, $file)); + + return $collection; + } } /** diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 9d46cfd438e6..01163fe773dd 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -14,7 +14,8 @@ use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Util\XmlUtils; -use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; use Symfony\Component\Routing\RouteCollection; /** @@ -25,6 +26,9 @@ */ class XmlFileLoader extends FileLoader { + use LocalizedRouteTrait; + use PrefixTrait; + const NAMESPACE_URI = 'http://symfony.com/schema/routing'; const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; @@ -98,41 +102,40 @@ public function supports($resource, string $type = null) /** * Parses a route and adds it to the RouteCollection. * - * @param \DOMElement $node Element to parse that represents a Route - * @param string $path Full path of the XML file being processed + * @param \DOMElement $node Element to parse that represents a Route + * @param string $filepath Full path of the XML file being processed * * @throws \InvalidArgumentException When the XML is invalid */ - protected function parseRoute(RouteCollection $collection, \DOMElement $node, string $path) + protected function parseRoute(RouteCollection $collection, \DOMElement $node, string $filepath) { if ('' === $id = $node->getAttribute('id')) { - throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" attribute.', $path)); + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" attribute.', $filepath)); } $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY); $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY); - list($defaults, $requirements, $options, $condition, $paths) = $this->parseConfigs($node, $path); + list($defaults, $requirements, $options, $condition, $paths) = $this->parseConfigs($node, $filepath); - if (!$paths && '' === $node->getAttribute('path')) { - throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "path" attribute or child nodes.', $path)); - } + $path = $node->getAttribute('path'); - if ($paths && '' !== $node->getAttribute('path')) { - throw new \InvalidArgumentException(sprintf('The element in file "%s" must not have both a "path" attribute and child nodes.', $path)); + if (!$paths && '' === $path) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "path" attribute or child nodes.', $filepath)); } - if (!$paths) { - $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); - $collection->add($id, $route); - } else { - foreach ($paths as $locale => $p) { - $defaults['_locale'] = $locale; - $defaults['_canonical_route'] = $id; - $route = new Route($p, $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); - $collection->add($id.'.'.$locale, $route); - } + if ($paths && '' !== $path) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must not have both a "path" attribute and child nodes.', $filepath)); } + + $route = $this->createLocalizedRoute($collection, $id, $paths ?: $path); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + $route->addOptions($options); + $route->setHost($node->getAttribute('host')); + $route->setSchemes($schemes); + $route->setMethods($methods); + $route->setCondition($condition); } /** @@ -156,6 +159,7 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, s $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null; $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null; $trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : true; + $namePrefix = $node->getAttribute('name-prefix') ?: null; list($defaults, $requirements, $options, $condition, /* $paths */, $prefixes) = $this->parseConfigs($node, $path); @@ -187,39 +191,7 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, s } foreach ($imported as $subCollection) { - /* @var $subCollection RouteCollection */ - if ('' !== $prefix || !$prefixes) { - $subCollection->addPrefix($prefix); - if (!$trailingSlashOnRoot) { - $rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath(); - foreach ($subCollection->all() as $route) { - if ($route->getPath() === $rootPath) { - $route->setPath(rtrim($rootPath, '/')); - } - } - } - } else { - foreach ($prefixes as $locale => $localePrefix) { - $prefixes[$locale] = trim(trim($localePrefix), '/'); - } - foreach ($subCollection->all() as $name => $route) { - if (null === $locale = $route->getDefault('_locale')) { - $subCollection->remove($name); - foreach ($prefixes as $locale => $localePrefix) { - $localizedRoute = clone $route; - $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $localizedRoute->setDefault('_locale', $locale); - $localizedRoute->setDefault('_canonical_route', $name); - $subCollection->add($name.'.'.$locale, $localizedRoute); - } - } elseif (!isset($prefixes[$locale])) { - throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $path)); - } else { - $route->setPath($prefixes[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $subCollection->add($name, $route); - } - } - } + $this->addPrefix($subCollection, $prefixes ?: $prefix, $trailingSlashOnRoot); if (null !== $host) { $subCollection->setHost($host); @@ -233,14 +205,13 @@ protected function parseImport(RouteCollection $collection, \DOMElement $node, s if (null !== $methods) { $subCollection->setMethods($methods); } + if (null !== $namePrefix) { + $subCollection->addNamePrefix($namePrefix); + } $subCollection->addDefaults($defaults); $subCollection->addRequirements($requirements); $subCollection->addOptions($options); - if ($namePrefix = $node->getAttribute('name-prefix')) { - $subCollection->addNamePrefix($namePrefix); - } - $collection->addCollection($subCollection); } } diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index 3b47b20f4a4a..6960d28e4218 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -13,7 +13,8 @@ use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser as YamlParser; @@ -27,6 +28,9 @@ */ class YamlFileLoader extends FileLoader { + use LocalizedRouteTrait; + use PrefixTrait; + private static $availableKeys = [ 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', ]; @@ -110,10 +114,6 @@ protected function parseRoute(RouteCollection $collection, string $name, array $ $defaults = isset($config['defaults']) ? $config['defaults'] : []; $requirements = isset($config['requirements']) ? $config['requirements'] : []; $options = isset($config['options']) ? $config['options'] : []; - $host = isset($config['host']) ? $config['host'] : ''; - $schemes = isset($config['schemes']) ? $config['schemes'] : []; - $methods = isset($config['methods']) ? $config['methods'] : []; - $condition = isset($config['condition']) ? $config['condition'] : null; foreach ($requirements as $placeholder => $requirement) { if (\is_int($placeholder)) { @@ -134,20 +134,14 @@ protected function parseRoute(RouteCollection $collection, string $name, array $ $options['utf8'] = $config['utf8']; } - if (\is_array($config['path'])) { - $route = new Route('', $defaults, $requirements, $options, $host, $schemes, $methods, $condition); - - foreach ($config['path'] as $locale => $path) { - $localizedRoute = clone $route; - $localizedRoute->setDefault('_locale', $locale); - $localizedRoute->setDefault('_canonical_route', $name); - $localizedRoute->setPath($path); - $collection->add($name.'.'.$locale, $localizedRoute); - } - } else { - $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); - $collection->add($name, $route); - } + $route = $this->createLocalizedRoute($collection, $name, $config['path']); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + $route->addOptions($options); + $route->setHost($config['host'] ?? ''); + $route->setSchemes($config['schemes'] ?? []); + $route->setMethods($config['methods'] ?? []); + $route->setCondition($config['condition'] ?? null); } /** @@ -169,6 +163,7 @@ protected function parseImport(RouteCollection $collection, array $config, strin $schemes = isset($config['schemes']) ? $config['schemes'] : null; $methods = isset($config['methods']) ? $config['methods'] : null; $trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true; + $namePrefix = $config['name_prefix'] ?? ''; $exclude = $config['exclude'] ?? null; if (isset($config['controller'])) { @@ -186,6 +181,7 @@ protected function parseImport(RouteCollection $collection, array $config, strin $this->setCurrentDir(\dirname($path)); + /** @var RouteCollection[] $imported */ $imported = $this->import($config['resource'], $type, false, $file, $exclude) ?: []; if (!\is_array($imported)) { @@ -193,39 +189,7 @@ protected function parseImport(RouteCollection $collection, array $config, strin } foreach ($imported as $subCollection) { - /* @var $subCollection RouteCollection */ - if (!\is_array($prefix)) { - $subCollection->addPrefix($prefix); - if (!$trailingSlashOnRoot) { - $rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath(); - foreach ($subCollection->all() as $route) { - if ($route->getPath() === $rootPath) { - $route->setPath(rtrim($rootPath, '/')); - } - } - } - } else { - foreach ($prefix as $locale => $localePrefix) { - $prefix[$locale] = trim(trim($localePrefix), '/'); - } - foreach ($subCollection->all() as $name => $route) { - if (null === $locale = $route->getDefault('_locale')) { - $subCollection->remove($name); - foreach ($prefix as $locale => $localePrefix) { - $localizedRoute = clone $route; - $localizedRoute->setDefault('_locale', $locale); - $localizedRoute->setDefault('_canonical_route', $name); - $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $subCollection->add($name.'.'.$locale, $localizedRoute); - } - } elseif (!isset($prefix[$locale])) { - throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix when imported in "%s".', $name, $locale, $file)); - } else { - $route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); - $subCollection->add($name, $route); - } - } - } + $this->addPrefix($subCollection, $prefix, $trailingSlashOnRoot); if (null !== $host) { $subCollection->setHost($host); @@ -239,14 +203,13 @@ protected function parseImport(RouteCollection $collection, array $config, strin if (null !== $methods) { $subCollection->setMethods($methods); } + if (null !== $namePrefix) { + $subCollection->addNamePrefix($namePrefix); + } $subCollection->addDefaults($defaults); $subCollection->addRequirements($requirements); $subCollection->addOptions($options); - if (isset($config['name_prefix'])) { - $subCollection->addNamePrefix($config['name_prefix']); - } - $collection->addCollection($subCollection); } }