├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── composer.json
├── phpunit.xml.dist
├── readme.md
├── src
├── DependencyInjection
│ ├── CompilerPass
│ │ └── ReplaceRouterCompilerPass.php
│ ├── Configuration.php
│ └── I18nRoutingExtension.php
├── I18nRoutingBundle.php
├── Resources
│ └── config
│ │ ├── annotations.php
│ │ └── services.php
└── Routing
│ ├── Annotation
│ └── I18nRoute.php
│ ├── I18nRouter.php
│ └── Loader
│ ├── AnnotatedI18nRouteLoader.php
│ ├── MissingRouteLocale.php
│ ├── MissingRoutePath.php
│ └── YamlFileLoader.php
└── tests
├── AnnotatedI18nRouteLoaderTest.php
├── AnnotationFixtures
├── AbstractClassController.php
├── ActionPathController.php
├── DefaultValueController.php
├── InvokableController.php
├── InvokableLocalizedController.php
├── LocalizedActionPathController.php
├── LocalizedMethodActionControllers.php
├── LocalizedPrefixLocalizedActionController.php
├── LocalizedPrefixMissingLocaleActionController.php
├── LocalizedPrefixMissingRouteLocaleActionController.php
├── LocalizedPrefixWithRouteWithoutLocale.php
├── MethodActionControllers.php
├── MissingRouteNameController.php
├── NothingButNameController.php
├── PrefixedActionLocalizedRouteController.php
├── PrefixedActionPathController.php
└── SymfonyRouteWithPrefixController.php
├── FileLocatorStub.php
├── I18nRouteTest.php
├── I18nRoutingBundleTest.php
├── I18nRoutingExtensionTest.php
├── I18nRoutingRouterTest.php
├── RouterStub.php
├── YamlFileLoaderTest.php
└── fixtures
├── empty.yml
├── imported-with-locale.yml
├── importer-with-all-options.yml
├── importer-with-controller-default.yml
├── importer-with-locale.yml
├── importing-localized-route.yml
├── importing-not-localized-with-localized-prefix.yml
├── invalid.yml
├── localized-route.yml
├── missing-locale-in-importer.yml
├── not-an-array.yml
├── not-localized.yml
├── route-has-too-many-properties.yml
├── route-is-not-an-array.yml
├── route-with-2-controllers.yml
├── route-with-resource.yml
├── route-with-type.yml
└── route-without-path-or-locales.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | coverage/
3 | composer.lock
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | paths: [src/*]
3 | excluded_paths: [tests/*, coverage/*]
4 | checks:
5 | php:
6 | code_rating: true
7 | remove_extra_empty_lines: true
8 | remove_php_closing_tag: true
9 | remove_trailing_whitespace: true
10 | fix_use_statements:
11 | remove_unused: true
12 | preserve_multiple: false
13 | preserve_blanklines: true
14 | order_alphabetically: true
15 | fix_php_opening_tag: true
16 | fix_linefeed: true
17 | fix_line_ending: true
18 | fix_identation_4spaces: true
19 | fix_doc_comments: true
20 | tools:
21 | external_code_coverage:
22 | timeout: 1200
23 | runs: 2
24 | php_code_coverage: false
25 | php_code_sniffer:
26 | config:
27 | standard: PSR2
28 | filter:
29 | paths: ['src']
30 | php_loc:
31 | enabled: true
32 | excluded_dirs: [vendor, tests]
33 | php_sim: false
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.1
5 | - 7.2
6 |
7 | install:
8 | - travis_retry composer install
9 |
10 | script:
11 | - vendor/bin/phpunit --coverage-text --coverage-clover coverage.xml
12 |
13 | after_script:
14 | - wget https://scrutinizer-ci.com/ocular.phar
15 | - php ocular.phar code-coverage:upload --format=php-clover coverage.xml
16 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frankdejonge/symfony-i18n-routing",
3 | "description": "Internationalised routing for Symfony 4",
4 | "type": "library",
5 | "license": "MIT",
6 | "autoload": {
7 | "psr-4": {
8 | "FrankDeJonge\\SymfonyI18nRouting\\": "src"
9 | }
10 | },
11 | "autoload-dev": {
12 | "psr-4": {
13 | "FrankDeJonge\\SymfonyI18nRouting\\AnnotationFixtures\\": "tests/AnnotationFixtures"
14 | }
15 | },
16 | "require": {
17 | "php": "^7.1.3",
18 | "symfony/routing": "^4.0",
19 | "symfony/config": "^4.0"
20 | },
21 | "require-dev": {
22 | "symfony/dependency-injection": "^4.0",
23 | "phpunit/phpunit": "^6.0",
24 | "symfony/http-kernel": "^4.0",
25 | "symfony/yaml": "^4.0",
26 | "matthiasnoback/symfony-dependency-injection-test": "^2.3",
27 | "doctrine/annotations": "^1.6",
28 | "nyholm/symfony-bundle-test": "^1.3"
29 | },
30 | "authors": [
31 | {
32 | "name": "Frank de Jonge",
33 | "email": "info@frenky.net"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests/
6 | src/
7 |
8 |
9 |
10 |
11 |
12 | ./src/
13 |
14 | src/*
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Internationalized routing for Symfony 4
2 |
3 | > This bundle provides i18n routing for Symfony 4.
4 |
5 | [](https://twitter.com/frankdejonge)
6 | [](https://travis-ci.org/frankdejonge/symfony-i18n-routing)
7 | [](https://scrutinizer-ci.com/g/frankdejonge/symfony-i18n-routing/code-structure)
8 | [](LICENSE)
9 | [](https://packagist.org/packages/frankdejonge/symfony-i18n-routing)
10 | [](https://packagist.org/packages/frankdejonge/symfony-i18n-routing)
11 |
12 | ## Purpose
13 |
14 | This bundle provides a method of internationalization of route definitions. This means
15 | you can define a path per locale and still have them route to the same controller action.
16 |
17 | ## Installation
18 |
19 | ```bash
20 | composer req frankdejonge/symfony-i18n-routing
21 | ```
22 |
23 | Register the bundle in `bundles.php`
24 |
25 | ```php
26 | ['all' => true],
30 | // ...
31 | ];
32 | ```
33 |
34 | Note that if you want to use the annotations you'll need to ensure this bundle is loaded BEFORE the FrameworkBundle.
35 |
36 | ## Configuration
37 |
38 | ```yaml
39 | frankdejonge_i18n_routing:
40 | default_locale: en
41 | use_annotations: false # set to true to enable annotation loading
42 | ```
43 |
44 | ## Yaml usage
45 |
46 | From your main `config/routes.yml` import your localized routes:
47 |
48 | ```yaml
49 | i18n_routes:
50 | resource: ./i18n_routes/routes.yml
51 | type: i18n_routes
52 | ```
53 |
54 | Now you can define i18n routes in `config/i18n_routes/routes.yml`:
55 |
56 | ```yaml
57 | contact:
58 | controller: ContactController::formAction
59 | locales:
60 | en: /send-us-an-email
61 | nl: /stuur-ons-een-email
62 | ```
63 |
64 | This is effectively the same as defining:
65 |
66 | ```yaml
67 | contact.en:
68 | controller: ContactController::formAction
69 | path: /send-us-an-email
70 | defaults:
71 | _locale: en
72 |
73 | contact.nl:
74 | controller: ContactController::formAction
75 | path: /stuur-ons-een-email
76 | defaults:
77 | _locale: nl
78 | ```
79 |
80 | As you can see this saves you a bit of typing and prevents you from
81 | having to keep 2 definitions in sync (less error prone).
82 |
83 | ## Annotation usage
84 |
85 | The annotation loader supports both normal route annotations and
86 | localized ones. The `@I18nROute` and `@Route` annotations can be
87 | be mixed at will.
88 |
89 | ```php
90 | generate('contact');
128 | $urlWithSpecifiedLocale = $urlGenerator->generate('contact', ['_locale' => 'nl']);
129 | ```
130 |
--------------------------------------------------------------------------------
/src/DependencyInjection/CompilerPass/ReplaceRouterCompilerPass.php:
--------------------------------------------------------------------------------
1 | setAlias('router', 'frankdejonge_i18n_routing.router')
17 | ->setPublic('true');
18 | }
19 | }
--------------------------------------------------------------------------------
/src/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | root('frankdejonge_i18n_routing');
14 | $root->children()
15 | ->scalarNode('default_locale')
16 | ->defaultValue('en')
17 | ->end()
18 | ->booleanNode('use_annotations')
19 | ->defaultFalse()
20 | ->end()
21 | ->end();
22 |
23 | return $treeBuilder;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/DependencyInjection/I18nRoutingExtension.php:
--------------------------------------------------------------------------------
1 | load('services.php');
32 | $config = $this->processConfiguration(new Configuration(), $configs);
33 | $container->setParameter('frankdejonge_i18n_routing.default_locale', $config['default_locale']);
34 | $this->configureAnnotationLoader($config, $loader);
35 | }
36 |
37 | private function configureAnnotationLoader(array $config, LoaderInterface $loader)
38 | {
39 | if ($config['use_annotations'] ?? false) {
40 | $loader->load('annotations.php');
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/I18nRoutingBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new ReplaceRouterCompilerPass());
16 | }
17 |
18 |
19 | public function getContainerExtension()
20 | {
21 | return new I18nRoutingExtension();
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Resources/config/annotations.php:
--------------------------------------------------------------------------------
1 | register('frankdejonge_i18n_routing.annotation.directory_loader', AnnotationDirectoryLoader::class)
9 | ->addTag('routing.loader')
10 | ->setArguments([
11 | new Reference('file_locator'),
12 | new Reference('frankdejonge_i18n_routing.annotation.class_loader')
13 | ]);
14 |
15 | $container->register('frankdejonge_i18n_routing.annotation.file_loader', AnnotationFileLoader::class)
16 | ->addTag('routing.loader')
17 | ->setArguments([
18 | new Reference('file_locator'),
19 | new Reference('frankdejonge_i18n_routing.annotation.class_loader')
20 | ]);
21 |
22 | $container->register('frankdejonge_i18n_routing.annotation.class_loader', AnnotatedI18nRouteLoader::class)
23 | ->addTag('routing.loader')
24 | ->setArguments([
25 | new Reference('annotation_reader')
26 | ]);
--------------------------------------------------------------------------------
/src/Resources/config/services.php:
--------------------------------------------------------------------------------
1 | register('frankdejonge_i18n_routing.yaml_loader', YamlFileLoader::class)
8 | ->addTag('routing.loader')
9 | ->addArgument(new Reference('file_locator'));
10 |
11 | $container->register('frankdejonge_i18n_routing.router', I18nRouter::class)
12 | ->addArgument(new Reference('router.default'))
13 | ->addArgument('%frankdejonge_i18n_routing.default_locale%');
--------------------------------------------------------------------------------
/src/Routing/Annotation/I18nRoute.php:
--------------------------------------------------------------------------------
1 | locales;
31 | }
32 |
33 | public function setLocales(array $locales)
34 | {
35 | $this->locales = $locales;
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Routing/I18nRouter.php:
--------------------------------------------------------------------------------
1 | router = $router;
30 | $this->defaultLocale = $defaultLocale;
31 | }
32 |
33 | public function setContext(RequestContext $context)
34 | {
35 | $this->router->setContext($context);
36 | }
37 |
38 | public function getContext()
39 | {
40 | return $this->router->getContext();
41 | }
42 |
43 | public function getRouteCollection()
44 | {
45 | return $this->router->getRouteCollection();
46 | }
47 |
48 | /**
49 | * Generates a URL or path for a specific route based on the given parameters.
50 | *
51 | * Parameters that reference placeholders in the route pattern will substitute them in the
52 | * path or host. Extra params are added as query string to the URL.
53 | *
54 | * When the passed reference type cannot be generated for the route because it requires a different
55 | * host or scheme than the current one, the method will return a more comprehensive reference
56 | * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH
57 | * but the route requires the https scheme whereas the current scheme is http, it will instead return an
58 | * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches
59 | * the route in any case.
60 | *
61 | * If there is no route with the given name, the generator must throw the RouteNotFoundException.
62 | *
63 | * The special parameter _fragment will be used as the document fragment suffixed to the final URL.
64 | *
65 | * @param string $name The name of the route
66 | * @param mixed $parameters An array of parameters
67 | * @param int $referenceType The type of reference to be generated (one of the constants)
68 | *
69 | * @return string The generated URL
70 | *
71 | * @throws RouteNotFoundException If the named route doesn't exist
72 | * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route
73 | * @throws InvalidParameterException When a parameter value for a placeholder is not correct because
74 | * it does not match the requirement
75 | */
76 | public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
77 | {
78 | $locale = $parameters['_locale']
79 | ?? $this->router->getContext()->getParameter('_locale')
80 | ?: $this->defaultLocale;
81 | $i18nParameters = $parameters;
82 | unset($i18nParameters['_locale']);
83 | $i18nRouteName = "{$name}.{$locale}";
84 |
85 | try {
86 | return $this->router->generate($i18nRouteName, $i18nParameters, $referenceType);
87 | } catch (RouteNotFoundException $exception) {
88 | return $this->router->generate($name, $parameters, $referenceType);
89 | }
90 | }
91 |
92 | /**
93 | * Tries to match a URL path with a set of routes.
94 | *
95 | * If the matcher can not find information, it must throw one of the exceptions documented
96 | * below.
97 | *
98 | * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded)
99 | *
100 | * @return array An array of parameters
101 | *
102 | * @throws NoConfigurationException If no routing configuration could be found
103 | * @throws ResourceNotFoundException If the resource could not be found
104 | * @throws MethodNotAllowedException If the resource was found but the request method is not allowed
105 | */
106 | public function match($pathinfo)
107 | {
108 | return $this->router->match($pathinfo);
109 | }
110 |
111 | /**
112 | * Warms up the cache.
113 | *
114 | * @param string $cacheDir The cache directory
115 | */
116 | public function warmUp($cacheDir)
117 | {
118 | if ($this->router instanceof WarmableInterface) {
119 | $this->router->warmUp($cacheDir);
120 | }
121 | }
122 | }
--------------------------------------------------------------------------------
/src/Routing/Loader/AnnotatedI18nRouteLoader.php:
--------------------------------------------------------------------------------
1 | isAbstract()) {
37 | throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName()));
38 | }
39 |
40 | $globals = $this->getGlobals($class);
41 |
42 | $collection = new RouteCollection();
43 | $collection->addResource(new FileResource($class->getFileName()));
44 |
45 | foreach ($class->getMethods() as $method) {
46 | $this->defaultRouteIndex = 0;
47 | foreach ($this->reader->getMethodAnnotations($method) as $annotation) {
48 | if ($annotation instanceof SymfonyRoute) {
49 | $this->addRoute($collection, $annotation, $globals, $class, $method);
50 | }
51 | }
52 | }
53 |
54 | if (0 === $collection->count() && $class->hasMethod('__invoke') && $annotation = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) {
55 | $globals['path'] = '';
56 | $globals['name'] = '';
57 | $globals['locales'] = [];
58 | $this->addRoute($collection, $annotation, $globals, $class, $class->getMethod('__invoke'));
59 | }
60 |
61 | return $collection;
62 | }
63 |
64 | /**
65 | * @param RouteCollection $collection
66 | * @param SymfonyRoute $annotation
67 | * @param array $globals
68 | * @param ReflectionClass $class
69 | * @param ReflectionMethod $method
70 | */
71 | protected function addRoute(RouteCollection $collection, $annotation, $globals, ReflectionClass $class, ReflectionMethod $method)
72 | {
73 | $name = $annotation->getName();
74 |
75 | if (null === $name) {
76 | $name = $this->getDefaultRouteName($class, $method);
77 | }
78 |
79 | $name = $globals['name_prefix'].$name;
80 | $defaults = array_replace($globals['defaults'], $annotation->getDefaults());
81 |
82 | foreach ($method->getParameters() as $param) {
83 | if ( ! isset($defaults[$param->name]) && $param->isDefaultValueAvailable()) {
84 | $defaults[$param->name] = $param->getDefaultValue();
85 | }
86 | }
87 |
88 | $requirements = array_replace($globals['requirements'], $annotation->getRequirements());
89 | $options = array_replace($globals['options'], $annotation->getOptions());
90 | $schemes = array_merge($globals['schemes'], $annotation->getSchemes());
91 | $methods = array_merge($globals['methods'], $annotation->getMethods());
92 | $host = $annotation->getHost() ?: $globals['host'];
93 | $condition = $annotation->getCondition() ?: $globals['condition'];
94 | $path = $annotation->getPath();
95 | $locales = $annotation instanceof I18nRoute ? $annotation->getLocales() : [];
96 |
97 | $hasLocalizedPrefix = empty($globals['locales']) === false;
98 | $hasPrefix = $hasLocalizedPrefix || empty($globals['path']) === false;
99 | $isLocalized = ! empty($locales);
100 | $hasPathOrLocales = empty($path) === false || $isLocalized;
101 |
102 | if ($hasPrefix === false && $hasPathOrLocales === false) {
103 | throw MissingRoutePath::forAnnotation("{$class->name}::{$method->name}");
104 | }
105 |
106 | if ( ! $hasPathOrLocales) {
107 | if ($hasLocalizedPrefix) {
108 | foreach ($globals['locales'] as $locale => $localePath) {
109 | $routeName = "{$name}.{$locale}";
110 | $route = $this->createRoute($localePath, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
111 | $this->configureRoute($route, $class, $method, $annotation);
112 | $route->setDefault('_locale', $locale);
113 | $collection->add($routeName, $route);
114 | }
115 | } else {
116 | $route = $this->createRoute($globals['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
117 | $this->configureRoute($route, $class, $method, $annotation);
118 | $collection->add($name, $route);
119 | }
120 | } elseif ( ! $hasPrefix) {
121 | if ($isLocalized) {
122 | foreach ($locales as $locale => $localePath) {
123 | $routeName = "{$name}.{$locale}";
124 | $route = $this->createRoute($localePath, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
125 | $this->configureRoute($route, $class, $method, $annotation);
126 | $route->setDefault('_locale', $locale);
127 | $collection->add($routeName, $route);
128 | }
129 | } else {
130 | $route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
131 | $this->configureRoute($route, $class, $method, $annotation);
132 | $collection->add($name, $route);
133 | }
134 | } else {
135 | if ($hasLocalizedPrefix) {
136 | if ($isLocalized) {
137 | $missing = array_diff(array_keys($globals['locales']), array_keys($locales));
138 |
139 | if ( ! empty($missing)) {
140 | throw MissingRouteLocale::forClass($class, $method, join(' and ', $missing));
141 | }
142 |
143 | foreach ($locales as $locale => $localePath) {
144 | if ( ! isset($globals['locales'][$locale])) {
145 | throw MissingRouteLocale::forClass($class, $method, $locale);
146 | }
147 |
148 | $routePath = $globals['locales'][$locale] . $localePath;
149 | $routeName = "{$name}.{$locale}";
150 | $route = $this->createRoute($routePath, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
151 | $this->configureRoute($route, $class, $method, $annotation);
152 | $route->setDefault('_locale', $locale);
153 | $collection->add($routeName, $route);
154 | }
155 | } else {
156 | foreach ($globals['locales'] as $locale => $localePrefix) {
157 | $routeName = "{$name}.{$locale}";
158 | $routePath = $localePrefix . $path;
159 | $route = $this->createRoute($routePath, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
160 | $this->configureRoute($route, $class, $method, $annotation);
161 | $route->setDefault('_locale', $locale);
162 | $collection->add($routeName, $route);
163 | }
164 | }
165 | } else {
166 | if ($isLocalized) {
167 | foreach ($locales as $locale => $localePath) {
168 | $routePath = $globals['path'] . $localePath;
169 | $routeName = "{$name}.{$locale}";
170 | $route = $this->createRoute($routePath, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
171 | $this->configureRoute($route, $class, $method, $annotation);
172 | $route->setDefault('_locale', $locale);
173 | $collection->add($routeName, $route);
174 | }
175 | } else {
176 | $routePath = $globals['path'] . $path;
177 | $route = $this->createRoute($routePath, $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
178 | $this->configureRoute($route, $class, $method, $annotation);
179 | $collection->add($name, $route);
180 | }
181 | }
182 | }
183 | }
184 |
185 | /**
186 | * @inheritdoc
187 | */
188 | protected function getGlobals(ReflectionClass $class)
189 | {
190 | $globals = [
191 | 'path' => '',
192 | 'locales' => [],
193 | 'requirements' => [],
194 | 'options' => [],
195 | 'defaults' => [],
196 | 'schemes' => [],
197 | 'methods' => [],
198 | 'host' => '',
199 | 'condition' => '',
200 | 'name_prefix' => '',
201 | ];
202 |
203 | $annotation = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass);
204 |
205 | if ($annotation instanceof SymfonyRoute === false) {
206 | return $globals;
207 | }
208 | if ($annotation instanceof I18nRoute) {
209 | $globals['locales'] = $annotation->getLocales();
210 | }
211 | if (null !== $annotation->getPath()) {
212 | $globals['path'] = $annotation->getPath();
213 | }
214 | if (null !== $annotation->getRequirements()) {
215 | $globals['requirements'] = $annotation->getRequirements();
216 | }
217 | if (null !== $annotation->getOptions()) {
218 | $globals['options'] = $annotation->getOptions();
219 | }
220 | if (null !== $annotation->getDefaults()) {
221 | $globals['defaults'] = $annotation->getDefaults();
222 | }
223 | if (null !== $annotation->getSchemes()) {
224 | $globals['schemes'] = $annotation->getSchemes();
225 | }
226 | if (null !== $annotation->getMethods()) {
227 | $globals['methods'] = $annotation->getMethods();
228 | }
229 | if (null !== $annotation->getHost()) {
230 | $globals['host'] = $annotation->getHost();
231 | }
232 | if (null !== $annotation->getCondition()) {
233 | $globals['condition'] = $annotation->getCondition();
234 | }
235 |
236 | return $globals;
237 | }
238 |
239 | protected function configureRoute(Route $route, ReflectionClass $class, ReflectionMethod $method, $annot)
240 | {
241 | $route->setDefault('_controller', $class->name . '::' . $method->getName());
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/Routing/Loader/MissingRouteLocale.php:
--------------------------------------------------------------------------------
1 | name} while it is/are defined on its action {$method->name}.");
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Routing/Loader/MissingRoutePath.php:
--------------------------------------------------------------------------------
1 |
19 | * @author Tobias Schultze
20 | */
21 | class YamlFileLoader extends FileLoader
22 | {
23 | private $yamlParser;
24 |
25 | private static $availableKeys = [
26 | 'locales', 'resource', 'type', 'prefix', 'path',
27 | 'host', 'schemes', 'methods', 'defaults', 'requirements',
28 | 'options', 'condition', 'controller',
29 | ];
30 |
31 | /**
32 | * Loads a Yaml file.
33 | *
34 | * @param string $file A Yaml file path
35 | * @param string|null $type The resource type
36 | *
37 | * @return RouteCollection A RouteCollection instance
38 | *
39 | * @throws InvalidArgumentException When a route can't be parsed because YAML is invalid
40 | */
41 | public function load($file, $type = null)
42 | {
43 | $path = $this->locator->locate($file, null, true);
44 |
45 | if ( ! stream_is_local($path)) {
46 | throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $path));
47 | }
48 |
49 | if ( ! file_exists($path)) {
50 | throw new InvalidArgumentException(sprintf('File "%s" not found.', $path));
51 | }
52 |
53 | if (null === $this->yamlParser) {
54 | $this->yamlParser = new YamlParser();
55 | }
56 |
57 | try {
58 | $parsedConfig = $this->yamlParser->parseFile($path);
59 | } catch (ParseException $e) {
60 | throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
61 | }
62 |
63 | $collection = new RouteCollection();
64 | $collection->addResource(new FileResource($path));
65 |
66 | // empty file
67 | if (null === $parsedConfig) {
68 | return $collection;
69 | }
70 |
71 | // not an array
72 | if ( ! is_array($parsedConfig)) {
73 | throw new InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path));
74 | }
75 |
76 | foreach ($parsedConfig as $name => $config) {
77 | $this->validate($config, $name, $path);
78 |
79 | if (isset($config['resource'])) {
80 | $this->parseImport($collection, $config, $path, $file);
81 | } else {
82 | $this->parseRoute($collection, $name, $config, $path);
83 | }
84 | }
85 |
86 | return $collection;
87 | }
88 |
89 | public function supports($resource, $type = null)
90 | {
91 | return $type === 'i18n_routes'
92 | && is_string($resource)
93 | && in_array(pathinfo($resource, PATHINFO_EXTENSION), ['yml', 'yaml'], true);
94 | }
95 |
96 | /**
97 | * Parses a route and adds it to the RouteCollection.
98 | *
99 | * @param RouteCollection $collection A RouteCollection instance
100 | * @param string $name Route name
101 | * @param array $config Route definition
102 | * @param string $path Full path of the YAML file being processed
103 | */
104 | protected function parseRoute(RouteCollection $collection, $name, array $config, $path)
105 | {
106 | $defaults = isset($config['defaults']) ? $config['defaults'] : [];
107 | $requirements = isset($config['requirements']) ? $config['requirements'] : [];
108 | $options = isset($config['options']) ? $config['options'] : [];
109 | $host = isset($config['host']) ? $config['host'] : '';
110 | $schemes = isset($config['schemes']) ? $config['schemes'] : [];
111 | $methods = isset($config['methods']) ? $config['methods'] : [];
112 | $condition = isset($config['condition']) ? $config['condition'] : null;
113 |
114 | if (isset($config['controller'])) {
115 | $defaults['_controller'] = $config['controller'];
116 | }
117 |
118 | if (isset($config['locales'])) {
119 | $routes = $this->generateLocaleRoutes(
120 | $name,
121 | $config['locales'],
122 | new Route('', $defaults, $requirements, $options, $host, $schemes, $methods, $condition)
123 | );
124 | foreach ($routes as $routeName => $route) {
125 | $collection->add($routeName, $route);
126 | }
127 | } else {
128 | $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition);
129 | $collection->add($name, $route);
130 | }
131 | }
132 |
133 | protected function parseImport(RouteCollection $collection, array $config, $path, $file)
134 | {
135 | $type = isset($config['type']) ? $config['type'] : 'i18n_routes';
136 | $prefix = isset($config['prefix']) ? $config['prefix'] : '';
137 | $defaults = isset($config['defaults']) ? $config['defaults'] : [];
138 | $requirements = isset($config['requirements']) ? $config['requirements'] : [];
139 | $options = isset($config['options']) ? $config['options'] : [];
140 | $host = isset($config['host']) ? $config['host'] : null;
141 | $condition = isset($config['condition']) ? $config['condition'] : null;
142 | $schemes = isset($config['schemes']) ? $config['schemes'] : null;
143 | $methods = isset($config['methods']) ? $config['methods'] : null;
144 |
145 | if (isset($config['controller'])) {
146 | $defaults['_controller'] = $config['controller'];
147 | }
148 |
149 | $this->setCurrentDir(dirname($path));
150 |
151 | /* @var $subCollection RouteCollection */
152 | $subCollection = $this->import($config['resource'], $type, false, $file);
153 | $prefixIsLocalized = is_array($prefix);
154 |
155 |
156 | foreach ($subCollection->all() as $routeName => $route) {
157 | $routeLocale = $route->getDefault('_locale');
158 | if ($prefixIsLocalized && null === $routeLocale) {
159 | throw new InvalidArgumentException("Route {$routeName} doesn't have a locale.");
160 | }
161 | if ($prefixIsLocalized && false === isset($prefix[$routeLocale])) {
162 | throw new InvalidArgumentException("Route {$routeName} with locale {$routeLocale} does not have a prefix defined in {$file}");
163 | }
164 |
165 | $localePrefix = is_array($prefix) ? $prefix[$routeLocale] : $prefix;
166 | $route->setPath($localePrefix . $route->getPath());
167 | }
168 |
169 | if (null !== $host) {
170 | $subCollection->setHost($host);
171 | }
172 | if (null !== $condition) {
173 | $subCollection->setCondition($condition);
174 | }
175 | if (null !== $schemes) {
176 | $subCollection->setSchemes($schemes);
177 | }
178 | if (null !== $methods) {
179 | $subCollection->setMethods($methods);
180 | }
181 | $subCollection->addDefaults($defaults);
182 | $subCollection->addRequirements($requirements);
183 | $subCollection->addOptions($options);
184 |
185 | $collection->addCollection($subCollection);
186 | }
187 |
188 | /**
189 | * Validates the route configuration.
190 | *
191 | * @param array $config A resource config
192 | * @param string $name The config key
193 | * @param string $path The loaded file path
194 | *
195 | * @throws InvalidArgumentException If one of the provided config keys is not supported,
196 | * something is missing or the combination is nonsense
197 | */
198 | protected function validate($config, $name, $path)
199 | {
200 | if ( ! is_array($config)) {
201 | throw new InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path));
202 | }
203 | if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) {
204 | throw new InvalidArgumentException(sprintf(
205 | 'The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".',
206 | $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys)
207 | ));
208 | }
209 | if (isset($config['resource']) && (isset($config['path']) || isset($config['locales']))) {
210 | throw new InvalidArgumentException(sprintf(
211 | 'The routing file "%s" must not specify both the "resource" key and the "path" or "locales" key for "%s". Choose between an import and a route definition.',
212 | $path, $name
213 | ));
214 | }
215 | if ( ! isset($config['resource']) && isset($config['type'])) {
216 | throw new InvalidArgumentException(sprintf(
217 | 'The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.',
218 | $name, $path
219 | ));
220 | }
221 | if ( ! isset($config['resource']) && ! isset($config['path']) && ! isset($config['locales'])) {
222 | throw new InvalidArgumentException(sprintf(
223 | 'You must define a "path" or "locales" for the route "%s" in file "%s".',
224 | $name, $path
225 | ));
226 | }
227 | if (isset($config['controller']) && isset($config['defaults']['_controller'])) {
228 | throw new InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name));
229 | }
230 | }
231 |
232 | private function generateLocaleRoutes(string $name, array $locales, Route $route): Generator
233 | {
234 | foreach ($locales as $locale => $path) {
235 | $localeRoute = clone $route;
236 | $localeRoute->setPath($path);
237 | $localeRoute->setDefault('_locale', $locale);
238 | yield "{$name}.{$locale}" => $localeRoute;
239 | }
240 | }
241 | }
--------------------------------------------------------------------------------
/tests/AnnotatedI18nRouteLoaderTest.php:
--------------------------------------------------------------------------------
1 | loader = new AnnotatedI18nRouteLoader(new AnnotationReader());
40 | AnnotationRegistry::registerLoader('class_exists');
41 | }
42 |
43 | /**
44 | * @test
45 | */
46 | public function simple_path_routes()
47 | {
48 | $routes = $this->loader->load(ActionPathController::class);
49 | $this->assertCount(1, $routes);
50 | $this->assertEquals('/path', $routes->get('action')->getPath());
51 | }
52 |
53 | /**
54 | * @test
55 | */
56 | public function invokable_controller_loading()
57 | {
58 | $routes = $this->loader->load(InvokableController::class);
59 | $this->assertCount(1, $routes);
60 | $this->assertEquals('/here', $routes->get('lol')->getPath());
61 | }
62 |
63 | /**
64 | * @test
65 | */
66 | public function invokable_localized_controller_loading()
67 | {
68 | $routes = $this->loader->load(InvokableLocalizedController::class);
69 | $this->assertCount(2, $routes);
70 | $this->assertEquals('/here', $routes->get('action.en')->getPath());
71 | $this->assertEquals('/hier', $routes->get('action.nl')->getPath());
72 | }
73 |
74 | /**
75 | * @test
76 | */
77 | public function localized_path_routes()
78 | {
79 | $routes = $this->loader->load(LocalizedActionPathController::class);
80 | $this->assertCount(2, $routes);
81 | $this->assertEquals('/path', $routes->get('action.en')->getPath());
82 | $this->assertEquals('/pad', $routes->get('action.nl')->getPath());
83 | }
84 |
85 | /**
86 | * @test
87 | */
88 | public function default_values_for_methods()
89 | {
90 | $routes = $this->loader->load(DefaultValueController::class);
91 | $this->assertCount(1, $routes);
92 | $this->assertEquals('/path', $routes->get('action')->getPath());
93 | $this->assertEquals('value', $routes->get('action')->getDefault('default'));
94 | }
95 |
96 | /**
97 | * @test
98 | */
99 | public function method_action_controllers()
100 | {
101 | $routes = $this->loader->load(MethodActionControllers::class);
102 | $this->assertCount(2, $routes);
103 | $this->assertEquals('/the/path', $routes->get('put')->getPath());
104 | $this->assertEquals('/the/path', $routes->get('post')->getPath());
105 | }
106 |
107 | /**
108 | * @test
109 | */
110 | public function localized_method_action_controllers()
111 | {
112 | $routes = $this->loader->load(LocalizedMethodActionControllers::class);
113 | $this->assertCount(4, $routes);
114 | $this->assertEquals('/the/path', $routes->get('put.en')->getPath());
115 | $this->assertEquals('/the/path', $routes->get('post.en')->getPath());
116 | }
117 |
118 | /**
119 | * @test
120 | */
121 | public function route_with_path_with_prefix()
122 | {
123 | $routes = $this->loader->load(PrefixedActionPathController::class);
124 | $this->assertCount(1, $routes);
125 | $route = $routes->get('action');
126 | $this->assertEquals('/prefix/path', $route->getPath());
127 | $this->assertEquals('lol=fun', $route->getCondition());
128 | $this->assertEquals('frankdejonge.nl', $route->getHost());
129 | }
130 |
131 | /**
132 | * @test
133 | */
134 | public function localized_route_with_path_with_prefix()
135 | {
136 | $routes = $this->loader->load(PrefixedActionLocalizedRouteController::class);
137 | $this->assertCount(2, $routes);
138 | $this->assertEquals('/prefix/path', $routes->get('action.en')->getPath());
139 | $this->assertEquals('/prefix/pad', $routes->get('action.nl')->getPath());
140 | }
141 |
142 | /**
143 | * @test
144 | */
145 | public function localized_prefix_localized_route()
146 | {
147 | $routes = $this->loader->load(LocalizedPrefixLocalizedActionController::class);
148 | $this->assertCount(2, $routes);
149 | $this->assertEquals('/nl/actie', $routes->get('action.nl')->getPath());
150 | $this->assertEquals('/en/action', $routes->get('action.en')->getPath());
151 | }
152 |
153 | /**
154 | * @test
155 | */
156 | public function missing_a_prefix_locale()
157 | {
158 | $this->expectException(MissingRouteLocale::class);
159 | $this->loader->load(LocalizedPrefixMissingLocaleActionController::class);
160 | }
161 |
162 | /**
163 | * @test
164 | */
165 | public function missing_a_route_locale()
166 | {
167 | $this->expectException(MissingRouteLocale::class);
168 | $this->loader->load(LocalizedPrefixMissingRouteLocaleActionController::class);
169 | }
170 |
171 | /**
172 | * @test
173 | */
174 | public function missing_a_route_name()
175 | {
176 | $routes = $this->loader->load(MissingRouteNameController::class)->all();
177 | $this->assertCount(1, $routes);
178 | $this->assertEquals('/path', reset($routes)->getPath());
179 | }
180 |
181 | /**
182 | * @test
183 | */
184 | public function nothing_but_a_name()
185 | {
186 | $this->expectException(MissingRoutePath::class);
187 | $this->loader->load(NothingButNameController::class);
188 | }
189 |
190 | /**
191 | * @test
192 | */
193 | public function non_existing_class_loading()
194 | {
195 | $this->expectException(LogicException::class);
196 | $this->loader->load('ClassThatDoesNotExist');
197 | }
198 |
199 | /**
200 | * @test
201 | */
202 | public function loading_an_abstract_class()
203 | {
204 | $this->expectException(LogicException::class);
205 | $this->loader->load(AbstractClassController::class);
206 | }
207 |
208 | /**
209 | * @test
210 | */
211 | public function localized_prefix_without_route_locale()
212 | {
213 | $routes = $this->loader->load(LocalizedPrefixWithRouteWithoutLocale::class);
214 | $this->assertCount(2, $routes);
215 | $this->assertEquals('/en/{param}', $routes->get('action.en')->getPath());
216 | $this->assertEquals('/nl/{param}', $routes->get('action.nl')->getPath());
217 | }
218 |
219 | /**
220 | * @test
221 | */
222 | public function loading_route_with_prefix()
223 | {
224 | $routes = $this->loader->load(SymfonyRouteWithPrefixController::class);
225 | $this->assertCount(1, $routes);
226 | $this->assertEquals('/prefix/path', $routes->get('action')->getPath());
227 | }
228 | }
--------------------------------------------------------------------------------
/tests/AnnotationFixtures/AbstractClassController.php:
--------------------------------------------------------------------------------
1 | expectException(BadMethodCallException::class);
14 | new I18nRoute(['invalid' => 'property']);
15 | }
16 |
17 | /**
18 | * @test
19 | * @dataProvider validPropertiesProvider
20 | */
21 | public function passing_valid_properties($property, $value)
22 | {
23 | $route = new I18nRoute([$property => $value]);
24 | $this->assertEquals($value, $route->{"get" . ucfirst($property)}());
25 | }
26 |
27 | public function validPropertiesProvider()
28 | {
29 | return [
30 | ['path', 'value'],
31 | ['locales', ['nl', 'es']],
32 | ['name', 'value'],
33 | ['requirements', ['segment' => '.*']],
34 | ['options', ['option' => true]],
35 | ['defaults', ['default' => 'value']],
36 | ['host', 'localhost'],
37 | ['methods', ['POST', 'PUT']],
38 | ['schemes', ['ftp', 'https']],
39 | ['condition', 'something=valid'],
40 | ];
41 | }
42 |
43 | /**
44 | * @test
45 | * @dataProvider validValues
46 | */
47 | public function passing_valid_values($getter, $value)
48 | {
49 | $route = new I18nRoute(['value' => $value]);
50 | $this->assertEquals($value, $route->{$getter}());
51 | }
52 |
53 | public function validValues()
54 | {
55 | return [
56 | ['getPath', '/nl'],
57 | ['getLocales', ['nl' => '/nl']],
58 | ];
59 | }
60 | }
--------------------------------------------------------------------------------
/tests/I18nRoutingBundleTest.php:
--------------------------------------------------------------------------------
1 | bootKernel();
23 | $container = $this->getContainer();
24 | $router = $container->get('router');
25 | $this->assertInstanceOf(I18nRouter::class, $router);
26 | }
27 | }
--------------------------------------------------------------------------------
/tests/I18nRoutingExtensionTest.php:
--------------------------------------------------------------------------------
1 | 'en'];
17 | }
18 |
19 | /**
20 | * @test
21 | */
22 | public function it_registers_a_router()
23 | {
24 | $this->load();
25 | $this->assertContainerBuilderHasService('frankdejonge_i18n_routing.router');
26 | }
27 |
28 | /**
29 | * @test
30 | */
31 | public function loading_annotation_services()
32 | {
33 | $this->load(['use_annotations' => true]);
34 | $this->assertContainerBuilderHasService('frankdejonge_i18n_routing.annotation.class_loader', AnnotatedI18nRouteLoader::class);
35 | $this->assertContainerBuilderHasService('frankdejonge_i18n_routing.annotation.file_loader', AnnotationFileLoader::class);
36 | $this->assertContainerBuilderHasService('frankdejonge_i18n_routing.annotation.directory_loader', AnnotationDirectoryLoader::class);
37 | }
38 |
39 | /**
40 | * Return an array of container extensions you need to be registered for each test (usually just the container
41 | * extension you are testing.
42 | *
43 | * @return ExtensionInterface[]
44 | */
45 | protected function getContainerExtensions()
46 | {
47 | return [(new I18nRoutingBundle())->getContainerExtension()];
48 | }
49 | }
--------------------------------------------------------------------------------
/tests/I18nRoutingRouterTest.php:
--------------------------------------------------------------------------------
1 | add('home', new Route('/'));
20 | $internalRouter = new RouterStub($routes);
21 | $this->assertFalse($internalRouter->warmed);
22 | $router = new I18nRouter($internalRouter, 'en');
23 |
24 | $this->assertEquals($routes, $router->getRouteCollection());
25 |
26 | $router->warmUp('string');
27 | $this->assertTrue($internalRouter->warmed);
28 |
29 | $newContext = new RequestContext('/new-context/');
30 | $this->assertNull($router->getContext());
31 | $router->setContext($newContext);
32 | $this->assertEquals($newContext, $router->getContext());
33 |
34 | $match = $router->match('/');
35 | $this->assertEquals(['_route' => 'home'], $match);
36 |
37 | $this->assertEquals('http://not_i18n/', $router->generate('not_i18n'));
38 | $this->assertEquals('http://is_i18n.en/', $router->generate('is_i18n'));
39 | }
40 | }
--------------------------------------------------------------------------------
/tests/RouterStub.php:
--------------------------------------------------------------------------------
1 | routes = $routes ?: new RouteCollection();
35 | }
36 |
37 | public function setContext(RequestContext $context)
38 | {
39 | $this->context = $context;
40 | }
41 |
42 | public function getContext()
43 | {
44 | return $this->context;
45 | }
46 |
47 | public function getRouteCollection()
48 | {
49 | return $this->routes;
50 | }
51 |
52 | /**
53 | * Generates a URL or path for a specific route based on the given parameters.
54 | *
55 | * Parameters that reference placeholders in the route pattern will substitute them in the
56 | * path or host. Extra params are added as query string to the URL.
57 | *
58 | * When the passed reference type cannot be generated for the route because it requires a different
59 | * host or scheme than the current one, the method will return a more comprehensive reference
60 | * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH
61 | * but the route requires the https scheme whereas the current scheme is http, it will instead return an
62 | * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches
63 | * the route in any case.
64 | *
65 | * If there is no route with the given name, the generator must throw the RouteNotFoundException.
66 | *
67 | * The special parameter _fragment will be used as the document fragment suffixed to the final URL.
68 | *
69 | * @param string $name The name of the route
70 | * @param mixed $parameters An array of parameters
71 | * @param int $referenceType The type of reference to be generated (one of the constants)
72 | *
73 | * @return string The generated URL
74 | *
75 | * @throws RouteNotFoundException If the named route doesn't exist
76 | * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route
77 | * @throws InvalidParameterException When a parameter value for a placeholder is not correct because
78 | * it does not match the requirement
79 | */
80 | public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
81 | {
82 | if ($name === 'not_i18n.en') {
83 | throw new RouteNotFoundException();
84 | }
85 |
86 | return 'http://'. $name . '/';
87 | }
88 |
89 | /**
90 | * Tries to match a URL path with a set of routes.
91 | *
92 | * If the matcher can not find information, it must throw one of the exceptions documented
93 | * below.
94 | *
95 | * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded)
96 | *
97 | * @return array An array of parameters
98 | *
99 | * @throws NoConfigurationException If no routing configuration could be found
100 | * @throws ResourceNotFoundException If the resource could not be found
101 | * @throws MethodNotAllowedException If the resource was found but the request method is not allowed
102 | */
103 | public function match($pathinfo)
104 | {
105 | return (new UrlMatcher($this->routes, new RequestContext('/')))->match($pathinfo);
106 | }
107 |
108 | /**
109 | * Warms up the cache.
110 | *
111 | * @param string $cacheDir The cache directory
112 | */
113 | public function warmUp($cacheDir)
114 | {
115 | $this->warmed = true;
116 | }
117 | }
--------------------------------------------------------------------------------
/tests/YamlFileLoaderTest.php:
--------------------------------------------------------------------------------
1 | locator = new FileLocator(__DIR__.'/fixtures/');
28 | $this->loader = new YamlFileLoader($this->locator);
29 | }
30 |
31 | public function setupStubbedLoader()
32 | {
33 | $this->locator = new FileLocatorStub();
34 | $this->loader = new YamlFileLoader($this->locator);
35 | }
36 |
37 | /**
38 | * @test
39 | */
40 | public function test_it_accepts_yaml_files()
41 | {
42 | $this->assertTrue($this->loader->supports('something.yaml', 'i18n_routes'));
43 | $this->assertTrue($this->loader->supports('something.yml', 'i18n_routes'));
44 | $this->assertFalse($this->loader->supports('something.yaml', 'routes'));
45 | $this->assertFalse($this->loader->supports('something.xml'));
46 | }
47 |
48 | /**
49 | * @test
50 | */
51 | public function loading_an_empty_file()
52 | {
53 | $routes = $this->loader->load('empty.yml');
54 | $this->assertEmpty($routes->all());
55 | }
56 |
57 | /**
58 | * @test
59 | */
60 | public function remote_sources_are_not_accepted()
61 | {
62 | $this->setupStubbedLoader();
63 | $this->expectException(InvalidArgumentException::class);
64 | $this->loader->load('http://remote.com/here.yml');
65 | }
66 |
67 | /**
68 | * @test
69 | */
70 | public function loading_non_existing_files()
71 | {
72 | $this->setupStubbedLoader();
73 | $this->expectException(InvalidArgumentException::class);
74 | $this->loader->load('non-existing.yml');
75 | }
76 |
77 | /**
78 | * @test
79 | */
80 | public function loading_invalid_yaml()
81 | {
82 | $this->expectException(InvalidArgumentException::class);
83 | $this->loader->load('invalid.yml');
84 | }
85 |
86 | /**
87 | * @test
88 | */
89 | public function loading_not_an_array()
90 | {
91 | $this->expectException(InvalidArgumentException::class);
92 | $this->loader->load('not-an-array.yml');
93 | }
94 |
95 | /**
96 | * @test
97 | */
98 | public function loading_a_localized_route()
99 | {
100 | $routes = $this->loader->load('localized-route.yml');
101 |
102 | $this->assertCount(3, $routes);
103 | }
104 |
105 | /**
106 | * @test
107 | */
108 | public function importing_routes_from_a_definition()
109 | {
110 | $routes = $this->loader->load('importing-localized-route.yml');
111 |
112 | $this->assertCount(3, $routes);
113 | $this->assertEquals('/nl', $routes->get('home.nl')->getPath());
114 | $this->assertEquals('/en', $routes->get('home.en')->getPath());
115 | $this->assertEquals('/here', $routes->get('not_localized')->getPath());
116 | }
117 |
118 | /**
119 | * @test
120 | */
121 | public function importing_routes_with_locales()
122 | {
123 | $routes = $this->loader->load('importer-with-locale.yml');
124 |
125 | $this->assertCount(2, $routes);
126 | $this->assertEquals('/nl/voorbeeld', $routes->get('imported.nl')->getPath());
127 | $this->assertEquals('/en/example', $routes->get('imported.en')->getPath());
128 | }
129 |
130 | /**
131 | * @test
132 | */
133 | public function importing_routes_from_a_definition_missing_a_locale_prefix()
134 | {
135 | $this->expectException(InvalidArgumentException::class);
136 | $this->loader->load('missing-locale-in-importer.yml');
137 | }
138 |
139 | /**
140 | * @test
141 | */
142 | public function importing_not_localized_routes_from_a_localized_import()
143 | {
144 | $this->expectException(InvalidArgumentException::class);
145 | $this->loader->load('importing-not-localized-with-localized-prefix.yml');
146 | }
147 |
148 | /**
149 | * @test
150 | */
151 | public function importing_a_route_that_is_not_an_array()
152 | {
153 | $this->expectException(InvalidArgumentException::class);
154 | $this->loader->load('route-is-not-an-array.yml');
155 | }
156 |
157 | /**
158 | * @test
159 | */
160 | public function importing_a_route_with_too_many_properties()
161 | {
162 | $this->expectException(InvalidArgumentException::class);
163 | $this->loader->load('route-has-too-many-properties.yml');
164 | }
165 |
166 | /**
167 | * @test
168 | */
169 | public function importing_a_route_without_a_path_or_locales()
170 | {
171 | $this->expectException(InvalidArgumentException::class);
172 | $this->loader->load('route-without-path-or-locales.yml');
173 | }
174 |
175 | /**
176 | * @test
177 | */
178 | public function importing_a_route_with_a_resource()
179 | {
180 | $this->expectException(InvalidArgumentException::class);
181 | $this->loader->load('route-with-resource.yml');
182 | }
183 |
184 | /**
185 | * @test
186 | */
187 | public function importing_a_route_with_a_type()
188 | {
189 | $this->expectException(InvalidArgumentException::class);
190 | $this->loader->load('route-with-type.yml');
191 | }
192 |
193 | /**
194 | * @test
195 | */
196 | public function importing_a_route_without_a_controller()
197 | {
198 | $this->expectException(InvalidArgumentException::class);
199 | $this->loader->load('route-with-2-controllers.yml');
200 | }
201 |
202 | /**
203 | * @test
204 | */
205 | public function importing_with_a_controller_default()
206 | {
207 | $routes = $this->loader->load('importer-with-controller-default.yml');
208 | $this->assertCount(3, $routes);
209 | $controller = $routes->get('home.en')->getDefault('_controller');
210 | $this->assertEquals('DefaultController::defaultAction', $controller);
211 | }
212 |
213 | /**
214 | * @test
215 | */
216 | public function importing_with_a_full_definition()
217 | {
218 | $routes = $this->loader->load('importer-with-all-options.yml');
219 | $this->assertCount(3, $routes);
220 | $route = $routes->get('home.en');
221 |
222 | $this->assertEquals(['POST', 'GET'], $route->getMethods());
223 | $this->assertEquals(['https', 'http'], $route->getSchemes());
224 | $this->assertEquals("context.getMethod() in ['GET', 'HEAD']", $route->getCondition());
225 | }
226 | }
--------------------------------------------------------------------------------
/tests/fixtures/empty.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
--------------------------------------------------------------------------------
/tests/fixtures/imported-with-locale.yml:
--------------------------------------------------------------------------------
1 | ---
2 | imported:
3 | controller: ImportedController::someAction
4 | locales:
5 | nl: /voorbeeld
6 | en: /example
--------------------------------------------------------------------------------
/tests/fixtures/importer-with-all-options.yml:
--------------------------------------------------------------------------------
1 | ---
2 | i_need:
3 | resource: ./localized-route.yml
4 | type: i18n_routes
5 | host: localhost
6 | methods: [POST, GET]
7 | schemes: [https,http]
8 | condition: "context.getMethod() in ['GET', 'HEAD']"
--------------------------------------------------------------------------------
/tests/fixtures/importer-with-controller-default.yml:
--------------------------------------------------------------------------------
1 | ---
2 | i_need:
3 | controller: DefaultController::defaultAction
4 | resource: ./localized-route.yml
5 | type: i18n_routes
--------------------------------------------------------------------------------
/tests/fixtures/importer-with-locale.yml:
--------------------------------------------------------------------------------
1 | ---
2 | i_need:
3 | resource: ./imported-with-locale.yml
4 | prefix:
5 | nl: /nl
6 | en: /en
--------------------------------------------------------------------------------
/tests/fixtures/importing-localized-route.yml:
--------------------------------------------------------------------------------
1 | ---
2 | i_need:
3 | resource: ./localized-route.yml
4 | type: i18n_routes
--------------------------------------------------------------------------------
/tests/fixtures/importing-not-localized-with-localized-prefix.yml:
--------------------------------------------------------------------------------
1 | ---
2 | import_these:
3 | resource: ./not-localized.yml
4 | type: i18n_routes
5 | prefix:
6 | nl: /nl
7 | en: /en
--------------------------------------------------------------------------------
/tests/fixtures/invalid.yml:
--------------------------------------------------------------------------------
1 | opwjef: [
--------------------------------------------------------------------------------
/tests/fixtures/localized-route.yml:
--------------------------------------------------------------------------------
1 | ---
2 | home:
3 | locales:
4 | nl: /nl
5 | en: /en
6 |
7 | not_localized:
8 | controller: HomeController::otherAction
9 | path: /here
--------------------------------------------------------------------------------
/tests/fixtures/missing-locale-in-importer.yml:
--------------------------------------------------------------------------------
1 | ---
2 | importing_with_missing_prefix:
3 | resource: ./localized-route.yml
4 | type: i18n_routes
5 | prefix:
6 | nl: /prefix
--------------------------------------------------------------------------------
/tests/fixtures/not-an-array.yml:
--------------------------------------------------------------------------------
1 | ---
2 | "something"
--------------------------------------------------------------------------------
/tests/fixtures/not-localized.yml:
--------------------------------------------------------------------------------
1 | ---
2 | not_localized:
3 | controller: string
4 | path: /here
--------------------------------------------------------------------------------
/tests/fixtures/route-has-too-many-properties.yml:
--------------------------------------------------------------------------------
1 | ---
2 | routename:
3 | path: /haha
4 | unknown: option
--------------------------------------------------------------------------------
/tests/fixtures/route-is-not-an-array.yml:
--------------------------------------------------------------------------------
1 | ---
2 | routename: 1234
--------------------------------------------------------------------------------
/tests/fixtures/route-with-2-controllers.yml:
--------------------------------------------------------------------------------
1 | ---
2 | home:
3 | controller: haha::haha
4 | defaults:
5 | _controller: lol::lol
6 | locales:
7 | nl: /nl
8 | en: /en
--------------------------------------------------------------------------------
/tests/fixtures/route-with-resource.yml:
--------------------------------------------------------------------------------
1 | ---
2 | routename:
3 | controller: HomeController::indexAction
4 | locales:
5 | nl: /nl
6 | en: /en
7 | resource: ./localized-route.yml
--------------------------------------------------------------------------------
/tests/fixtures/route-with-type.yml:
--------------------------------------------------------------------------------
1 | ---
2 | routename:
3 | controller: HomeController::indexAction
4 | locales:
5 | nl: /nl
6 | en: /en
7 | type: i18n_routes
--------------------------------------------------------------------------------
/tests/fixtures/route-without-path-or-locales.yml:
--------------------------------------------------------------------------------
1 | ---
2 | routename:
3 | controller: Here::here
--------------------------------------------------------------------------------