]>
Commit | Line | Data |
---|---|---|
4f5b44bd NL |
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\Component\Routing\Loader; | |
13 | ||
14 | use Symfony\Component\Routing\RouteCollection; | |
15 | use Symfony\Component\Routing\Route; | |
16 | use Symfony\Component\Config\Resource\FileResource; | |
17 | use Symfony\Component\Config\Loader\FileLoader; | |
18 | use Symfony\Component\Config\Util\XmlUtils; | |
19 | ||
20 | /** | |
21 | * XmlFileLoader loads XML routing files. | |
22 | * | |
23 | * @author Fabien Potencier <fabien@symfony.com> | |
24 | * @author Tobias Schultze <http://tobion.de> | |
25 | * | |
26 | * @api | |
27 | */ | |
28 | class XmlFileLoader extends FileLoader | |
29 | { | |
30 | const NAMESPACE_URI = 'http://symfony.com/schema/routing'; | |
31 | const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; | |
32 | ||
33 | /** | |
34 | * Loads an XML file. | |
35 | * | |
36 | * @param string $file An XML file path | |
37 | * @param string|null $type The resource type | |
38 | * | |
39 | * @return RouteCollection A RouteCollection instance | |
40 | * | |
41 | * @throws \InvalidArgumentException When the file cannot be loaded or when the XML cannot be | |
42 | * parsed because it does not validate against the scheme. | |
43 | * | |
44 | * @api | |
45 | */ | |
46 | public function load($file, $type = null) | |
47 | { | |
48 | $path = $this->locator->locate($file); | |
49 | ||
50 | $xml = $this->loadFile($path); | |
51 | ||
52 | $collection = new RouteCollection(); | |
53 | $collection->addResource(new FileResource($path)); | |
54 | ||
55 | // process routes and imports | |
56 | foreach ($xml->documentElement->childNodes as $node) { | |
57 | if (!$node instanceof \DOMElement) { | |
58 | continue; | |
59 | } | |
60 | ||
61 | $this->parseNode($collection, $node, $path, $file); | |
62 | } | |
63 | ||
64 | return $collection; | |
65 | } | |
66 | ||
67 | /** | |
68 | * Parses a node from a loaded XML file. | |
69 | * | |
70 | * @param RouteCollection $collection Collection to associate with the node | |
71 | * @param \DOMElement $node Element to parse | |
72 | * @param string $path Full path of the XML file being processed | |
73 | * @param string $file Loaded file name | |
74 | * | |
75 | * @throws \InvalidArgumentException When the XML is invalid | |
76 | */ | |
77 | protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) | |
78 | { | |
79 | if (self::NAMESPACE_URI !== $node->namespaceURI) { | |
80 | return; | |
81 | } | |
82 | ||
83 | switch ($node->localName) { | |
84 | case 'route': | |
85 | $this->parseRoute($collection, $node, $path); | |
86 | break; | |
87 | case 'import': | |
88 | $this->parseImport($collection, $node, $path, $file); | |
89 | break; | |
90 | default: | |
91 | throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); | |
92 | } | |
93 | } | |
94 | ||
95 | /** | |
96 | * {@inheritdoc} | |
97 | * | |
98 | * @api | |
99 | */ | |
100 | public function supports($resource, $type = null) | |
101 | { | |
102 | return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type); | |
103 | } | |
104 | ||
105 | /** | |
106 | * Parses a route and adds it to the RouteCollection. | |
107 | * | |
108 | * @param RouteCollection $collection RouteCollection instance | |
109 | * @param \DOMElement $node Element to parse that represents a Route | |
110 | * @param string $path Full path of the XML file being processed | |
111 | * | |
112 | * @throws \InvalidArgumentException When the XML is invalid | |
113 | */ | |
114 | protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path) | |
115 | { | |
116 | if ('' === ($id = $node->getAttribute('id')) || (!$node->hasAttribute('pattern') && !$node->hasAttribute('path'))) { | |
117 | throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" must have an "id" and a "path" attribute.', $path)); | |
118 | } | |
119 | ||
120 | if ($node->hasAttribute('pattern')) { | |
121 | if ($node->hasAttribute('path')) { | |
122 | throw new \InvalidArgumentException(sprintf('The <route> element in file "%s" cannot define both a "path" and a "pattern" attribute. Use only "path".', $path)); | |
123 | } | |
124 | ||
125 | $node->setAttribute('path', $node->getAttribute('pattern')); | |
126 | $node->removeAttribute('pattern'); | |
127 | } | |
128 | ||
129 | $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY); | |
130 | $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY); | |
131 | ||
132 | list($defaults, $requirements, $options) = $this->parseConfigs($node, $path); | |
133 | ||
134 | $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods); | |
135 | $collection->add($id, $route); | |
136 | } | |
137 | ||
138 | /** | |
139 | * Parses an import and adds the routes in the resource to the RouteCollection. | |
140 | * | |
141 | * @param RouteCollection $collection RouteCollection instance | |
142 | * @param \DOMElement $node Element to parse that represents a Route | |
143 | * @param string $path Full path of the XML file being processed | |
144 | * @param string $file Loaded file name | |
145 | * | |
146 | * @throws \InvalidArgumentException When the XML is invalid | |
147 | */ | |
148 | protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file) | |
149 | { | |
150 | if ('' === $resource = $node->getAttribute('resource')) { | |
151 | throw new \InvalidArgumentException(sprintf('The <import> element in file "%s" must have a "resource" attribute.', $path)); | |
152 | } | |
153 | ||
154 | $type = $node->getAttribute('type'); | |
155 | $prefix = $node->getAttribute('prefix'); | |
156 | $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null; | |
157 | $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null; | |
158 | $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null; | |
159 | ||
160 | list($defaults, $requirements, $options) = $this->parseConfigs($node, $path); | |
161 | ||
162 | $this->setCurrentDir(dirname($path)); | |
163 | ||
164 | $subCollection = $this->import($resource, ('' !== $type ? $type : null), false, $file); | |
165 | /* @var $subCollection RouteCollection */ | |
166 | $subCollection->addPrefix($prefix); | |
167 | if (null !== $host) { | |
168 | $subCollection->setHost($host); | |
169 | } | |
170 | if (null !== $schemes) { | |
171 | $subCollection->setSchemes($schemes); | |
172 | } | |
173 | if (null !== $methods) { | |
174 | $subCollection->setMethods($methods); | |
175 | } | |
176 | $subCollection->addDefaults($defaults); | |
177 | $subCollection->addRequirements($requirements); | |
178 | $subCollection->addOptions($options); | |
179 | ||
180 | $collection->addCollection($subCollection); | |
181 | } | |
182 | ||
183 | /** | |
184 | * Loads an XML file. | |
185 | * | |
186 | * @param string $file An XML file path | |
187 | * | |
188 | * @return \DOMDocument | |
189 | * | |
190 | * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors | |
191 | * or when the XML structure is not as expected by the scheme - | |
192 | * see validate() | |
193 | */ | |
194 | protected function loadFile($file) | |
195 | { | |
196 | return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); | |
197 | } | |
198 | ||
199 | /** | |
200 | * Parses the config elements (default, requirement, option). | |
201 | * | |
202 | * @param \DOMElement $node Element to parse that contains the configs | |
203 | * @param string $path Full path of the XML file being processed | |
204 | * | |
205 | * @return array An array with the defaults as first item, requirements as second and options as third. | |
206 | * | |
207 | * @throws \InvalidArgumentException When the XML is invalid | |
208 | */ | |
209 | private function parseConfigs(\DOMElement $node, $path) | |
210 | { | |
211 | $defaults = array(); | |
212 | $requirements = array(); | |
213 | $options = array(); | |
214 | ||
215 | foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { | |
216 | switch ($n->localName) { | |
217 | case 'default': | |
218 | if ($n->hasAttribute('xsi:nil') && 'true' == $n->getAttribute('xsi:nil')) { | |
219 | $defaults[$n->getAttribute('key')] = null; | |
220 | } else { | |
221 | $defaults[$n->getAttribute('key')] = trim($n->textContent); | |
222 | } | |
223 | ||
224 | break; | |
225 | case 'requirement': | |
226 | $requirements[$n->getAttribute('key')] = trim($n->textContent); | |
227 | break; | |
228 | case 'option': | |
229 | $options[$n->getAttribute('key')] = trim($n->textContent); | |
230 | break; | |
231 | default: | |
232 | throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path)); | |
233 | } | |
234 | } | |
235 | ||
236 | return array($defaults, $requirements, $options); | |
237 | } | |
238 | } |