8000 [FrameworkBundle][Routing] added XML and YAML loaders to handle templ… · symfony/symfony@bd4af21 · GitHub
[go: up one dir, main page]

Skip to content

Commit bd4af21

Browse files
committed
[FrameworkBundle][Routing] added XML and YAML loaders to handle template and redirect controllers
1 parent 0bec08f commit bd4af21

File tree

12 files changed

+597
-8
lines changed

12 files changed

+597
-8
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ CHANGELOG
55
-----
66

77
* Added link to source on controller on `router:match`/`debug:router` (when `framework.ide` is configured)
8-
* Added `Routing\Loader` and `Routing\Loader\Configurator` namespaces to ease defining routes with default controllers
8+
* Added XML and YAML routing loaders to ease defining routes with redirect and template controllers
9+
* Added the `Routing\Loader\Configurator` namespace to ease defining routes with redirect and template controllers
910
* Added the `framework.router.context` configuration node to configure the `RequestContext`
1011
* Made `MicroKernelTrait::configureContainer()` compatible with `ContainerConfigurator`
1112
* Added a new `mailer.message_bus` option to configure or disable the message bus to use to send mails.

src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515

1616
<service id="routing.resolver" class="Symfony\Component\Config\Loader\LoaderResolver" />
1717

18-
<service id="routing.loader.xml" class="Symfony\Component\Routing\Loader\XmlFileLoader">
18+
<service id="routing.loader.xml" class="Symfony\Bundle\FrameworkBundle\Routing\Loader\XmlFileLoader">
1919
<tag name="routing.loader" />
2020
<argument type="service" id="file_locator" />
2121
</service>
2222

23-
<service id="routing.loader.yml" class="Symfony\Component\Routing\Loader\YamlFileLoader">
23+
<service id="routing.loader.yml" class="Symfony\Bundle\FrameworkBundle\Routing\Loader\YamlFileLoader">
2424
<tag name="routing.loader" />
2525
<argument type="service" id="file_locator" />
2626
</service>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<xsd:schema xmlns="http://symfony.com/schema/routing"
4+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5+
targetNamespace="http://symfony.com/schema/routing"
6+
elementFormDefault="qualified">
7+
8+
<xsd:redefine schemaLocation="https://symfony.com/schema/routing/routing-1.0.xsd">
9+
<xsd:complexType name="routes">
10+
<xsd:complexContent>
11+
<xsd:extension base="routes">
12+
<xsd:choice minOccurs="0" maxOccurs="unbounded">
13+
<xsd:element name="template-route" type="template-route" />
14+
<xsd:element name="redirect-route" type="redirect-route" />
15+
<xsd:element name="url-redirect-route" type="url-redirect-route" />
16+
<xsd:element name="gone-route" type="gone-route" />
17+
</xsd:choice>
18+
</xsd:extension>
19+
</xsd:complexContent>
20+
</xsd:complexType>
21+
</xsd:redefine>
22+
23+
<xsd:group name="base-configs">
24+
<xsd:choice>
25+
<xsd:element name="path" type="localized-path" />
26+
<xsd:element name="requirement" type="element" />
27+
<xsd:element name="option" type="element" />
28+
<xsd:element name="condition" type="xsd:string" />
29+
</xsd:choice>
30+
</xsd:group>
31+
32+
<xsd:complexType name="base-route" abstract="true">
33+
<xsd:attribute name="id" type="xsd:string" use="required" />
34+
<xsd:attribute name="path" type="xsd:string" />
35+
<xsd:attribute name="host" type="xsd:string" />
36+
<xsd:attribute name="methods" type="xsd:string" />
37+
<xsd:attribute name="locale" type="xsd:string" />
38+
<xsd:attribute name="format" type="xsd:string" />
39+
<xsd:attribute name="utf8" type="xsd:boolean" />
40+
</xsd:complexType>
41+
42+
<xsd:complexType name="template-route">
43+
<xsd:complexContent>
44+
<xsd:extension base="base-route">
45+
<xsd:sequence>
46+
<xsd:element name="context" type="map" minOccurs="0" maxOccurs="1" />
47+
<xsd:group ref="base-configs" minOccurs="0" maxOccurs="unbounded" />
48+
</xsd:sequence>
49+
<xsd:attribute name="template" type="xsd:string" use="required" />
50+
<xsd:attribute name="max-age" type="xsd:int" />
51+
<xsd:attribute name="shared-max-age" type="xsd:int" />
52+
<xsd:attribute name="private" type="xsd:boolean" />
53+
<xsd:attribute name="schemes" type="xsd:string" />
54+
</xsd:extension>
55+
</xsd:complexContent>
56+
</xsd:complexType>
57+
58+
<xsd:complexType name="redirect-route">
59+
<xsd:complexContent>
60+
<xsd:extension base="base-route">
61+
<xsd:sequence>
62+
<xsd:group ref="base-configs" minOccurs="0" maxOccurs="unbounded" />
63+
</xsd:sequence>
64+
<xsd:attribute name="redirect-to-route" type="xsd:string" use="required"/>
65+
<xsd:attribute name="permanent" type="xsd:boolean" />
66+
<xsd:attribute name="ignore-attributes" type="xsd:string" />
67+
<xsd:attribute name="keep-request-method" type="xsd:boolean" />
68+
<xsd:attribute name="keep-query-params" type="xsd:boolean" />
69+
<xsd:attribute name="schemes" type="xsd:string" />
70+
</xsd:extension>
71+
</xsd:complexContent>
72+
</xsd:complexType>
73+
74+
<xsd:complexType name="url-redirect-route">
75+
<xsd:complexContent>
76+
<xsd:extension base="base-route">
77+
<xsd:sequence>
78+
<xsd:group ref="base-configs" minOccurs="0" maxOccurs="unbounded" />
79+
</xsd:sequence>
80+
<xsd:attribute name="redirect-to-url" type="xsd:string" use="required" />
81+
<xsd:attribute name="permanent" type="xsd:boolean" />
82+
<xsd:attribute name="scheme" type="xsd:string" />
83+
<xsd:attribute name="http-port" type="xsd:int" />
84+
<xsd:attribute name="https-port" type="xsd:int" />
85+
<xsd:attribute name="keep-request-method" type="xsd:boolean" />
86+
</xsd:extension>
87+
</xsd:complexContent>
88+
</xsd:complexType>
89+
90+
<xsd:complexType name="gone-route">
91+
<xsd:complexContent>
92+
<xsd:extension base="base-route">
93+
<xsd:sequence>
94+
<xsd:group ref="base-configs" minOccurs="0" maxOccurs="unbounded" />
95+
</xsd:sequence>
96+
<xsd:attribute name="permanent" type="xsd:boolean" />
97+
</xsd:extension>
98+
</xsd:complexContent>
99+
</xsd:complexType>
100+
101+
</xsd:schema>
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Routing\Loader;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;
15+
use Symfony\Bundle\FrameworkBundle\Controller\TemplateController;
16+
use Symfony\Component\Config\Util\Exception\InvalidXmlException;
17+
use Symfony\Component\Config\Util\Exception\XmlParsingException;
18+
use Symfony\Component\Config\Util\XmlUtils;
19+
use Symfony\Component\Routing\Loader\XmlFileLoader as BaseXmlFileLoader;
20+
use Symfony\Component\Routing\Route;
21+
use Symfony\Component\Routing\RouteCollection;
22+
23+
/**
24+
* @author Jules Pietri <jules@heahprod.com>
25+
*/
26+
class XmlFileLoader extends BaseXmlFileLoader
27+
{
28+
public const SCHEME_PATH = __DIR__.'/../../Resources/config/schema/framework-routing-1.0.xsd';
29+
30+
private const REDEFINED_SCHEME_URI = 'https://symfony.com/schema/routing/routing-1.0.xsd';
31+
private const SCHEME_URI = 'https://symfony.com/schema/routing/framework-routing-1.0.xsd';
32+
private const SCHEMA_LOCATIONS = [
33+
self::REDEFINED_SCHEME_URI => parent::SCHEME_PATH,
34+
self::SCHEME_URI => self::SCHEME_PATH,
35+
];
36+
37+
/** @var \DOMDocument */
38+
private $document;
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
protected function loadFile(string $file)
44+
{
45+
if ('' === trim($content = @file_get_contents($file))) {
46+
throw new \InvalidArgumentException(sprintf('File "%s" does not contain valid XML, it is empty.', $file));
47+
}
48+
49+
foreach (self::SCHEMA_LOCATIONS as $uri => $path) {
50+
if (false !== strpos($content, $uri)) {
51+
$content = str_replace($uri, self::getRealSchemePath($path), $content);
52+
}
53+
}
54+
55+
try {
56+
return $this->document = XmlUtils::parse($content, function (\DOMDocument $document) {
57+
return @$document->schemaValidateSource(str_replace(
58+
self::REDEFINED_SCHEME_URI,
59+
self::getRealSchemePath(parent::SCHEME_PATH),
60+
file_get_contents(self::SCHEME_PATH)
61+
));
62+
});
63+
} catch (InvalidXmlException $e) {
64+
throw new XmlParsingException(sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious());
65+
}
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file)
72+
{
73+
switch ($node->localName) {
74+
case 'template-route':
75+
case 'redirect-route':
76+
case 'url-redirect-route':
77+
case 'gone-route':
78+
if (self::NAMESPACE_URI !== $node->namespaceURI) {
79+
return;
80+
}
81+
82+
$this->parseRoute($collection, $node, $path);
83+
84+
return;
85+
}
86+
87+
parent::parseNode($collection, $node, $path, $file);
88+
}
89+
90+
/**
91+
* {@inheritdoc}
92+
*/
93+
protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path)
94+
{
95+
$templateContext = [];
96+
97+
if ('template-route' === $node->localName) {
98+
/** @var \DOMElement $context */
99+
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, 'context') as $context) {
100+
$node->removeChild($context);
101+
$map = $this->document->createElementNS(self::NAMESPACE_URI, 'map');
102+
103+
// extract context vars into a map
104+
foreach ($context->childNodes as $n) {
105+
if (!$n instanceof \DOMElement) {
106+
continue;
107+
}
108+
109+
$map->appendChild($n);
110+
}
111+
112+
$default = $this->document->createElementNS(self::NAMESPACE_URI, 'default');
113+
$default->setAttribute('key', 'context');
114+
$default->appendChild($map);
115+
116+
$templateContext = $this->parseDefaultsConfig($default, $path);
117+
}
118+
}
119+
120+
parent::parseRoute($collection, $node, $path);
121+
122+
if ($route = $collection->get($id = $node->getAttribute(('id')))) {
123+
$this->parseConfig($node, $route, $templateContext);
124+
125+
return;
126+
}
127+
128+
foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, 'path') as $n) {
129+
$route = $collection->get($id.'.'.$n->getAttribute('locale'));
130+
131+
$this->parseConfig($node, $route, $templateContext);
132+
}
133+
}
134+
135+
private function parseConfig(\DOMElement $node, Route $route, array $templateContext): void
136+
{
137+
switch ($node->localName) {
138+
case 'template-route':
139+
$route
140+
->setDefault('_controller', TemplateController::class)
141+
->setDefault('template', $node->getAttribute('template'))
142+
->setDefault('context', $templateContext)
143+
->setDefault('maxAge', (int) $node->getAttribute('max-age') ?: null)
144+
->setDefault('sharedAge', (int) $node->getAttribute('shared-max-age') ?: null)
145+
->setDefault('private', $node->hasAttribute('private') ? XmlUtils::phpize($node->getAttribute('private')) : null)
146+
;
147+
break;
148+
case 'redirect-route':
149+
$route
150+
->setDefault('_controller', RedirectController::class.'::redirectAction')
151+
->setDefault('route', $node->getAttribute('redirect-to-route'))
152+
->setDefault('permanent', self::getBooleanAttribute($node, 'permanent'))
153+
->setDefault('keepRequestMethod', self::getBooleanAttribute($node, 'keep-request-method'))
154+
->setDefault('keepQueryParams', self::getBooleanAttribute($node, 'keep-query-params'))
155+
;
156+
157+
if (\is_string($ignoreAttributes = XmlUtils::phpize($node->getAttribute('ignore-attributes')))) {
158+
$ignoreAttributes = array_map('trim', explode(',', $ignoreAttributes));
159+
}
160+
161+
$route->setDefault('ignoreAttributes', $ignoreAttributes);
162+
break;
163+
case 'url-redirect-route':
164+
$route
165+
->setDefault('_controller', RedirectController::class.'::urlRedirectAction')
166+
->setDefault('path', $node->getAttribute('redirect-to-url'))
167+
->setDefault('permanent', self::getBooleanAttribute($node, 'permanent'))
168+
->setDefault('scheme', $node->getAttribute('scheme'))
169+
->setDefault('keepRequestMethod', self::getBooleanAttribute($node, 'keep-request-method'))
170+
;
171+
if ($node->hasAttribute('http-port')) {
172+
$route->setDefault('httpPort', (int) $node->getAttribute('http-port') ?: null);
173+
} elseif ($node->hasAttribute('https-port')) {
174+
$route->setDefault('httpsPort', (int) $node->getAttribute('https-port') ?: null);
175+
}
176+
break;
177+
case 'gone-route':
178+
$route
179+
->setDefault('_controller', RedirectController::class.'::redirectAction')
180+
->setDefault('route', '')
181+
;
182+
if ($node->hasAttribute('permanent')) {
183+
$route->setDefault('permanent', self::getBooleanAttribute($node, 'permanent'));
184+
}
185+
break;
186+
}
187+
}
188+
189+
private static function getRealSchemePath(string $schemePath): string
190+
{
191+
return 'file:///'.str_replace('\\', '/', realpath($schemePath));
192+
}
193+
194+
private static function getBooleanAttribute(\DOMElement $node, string $attribute): bool
195+
{
196+
return $node->hasAttribute($attribute) ? XmlUtils::phpize($node->getAttribute($attribute)) : false;
197+
}
198+
}

0 commit comments

Comments
 (0)
0