2015-08-18 00:00:26 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Routing\Loader ;
use Symfony\Component\Config\Loader\FileLoader ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\Config\Resource\FileResource ;
2015-08-18 00:00:26 +00:00
use Symfony\Component\Config\Util\XmlUtils ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\Routing\Route ;
use Symfony\Component\Routing\RouteCollection ;
2015-08-18 00:00:26 +00:00
/**
* XmlFileLoader loads XML routing files .
*
* @ author Fabien Potencier < fabien @ symfony . com >
* @ author Tobias Schultze < http :// tobion . de >
*/
class XmlFileLoader extends FileLoader
{
const NAMESPACE_URI = 'http://symfony.com/schema/routing' ;
const SCHEME_PATH = '/schema/routing/routing-1.0.xsd' ;
/**
* Loads an XML file .
*
* @ param string $file An XML file path
* @ param string | null $type The resource type
*
* @ return RouteCollection A RouteCollection instance
*
2018-11-23 12:29:20 +00:00
* @ throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be
* parsed because it does not validate against the scheme
2015-08-18 00:00:26 +00:00
*/
public function load ( $file , $type = null )
{
$path = $this -> locator -> locate ( $file );
$xml = $this -> loadFile ( $path );
$collection = new RouteCollection ();
$collection -> addResource ( new FileResource ( $path ));
// process routes and imports
foreach ( $xml -> documentElement -> childNodes as $node ) {
if ( ! $node instanceof \DOMElement ) {
continue ;
}
$this -> parseNode ( $collection , $node , $path , $file );
}
return $collection ;
}
/**
* Parses a node from a loaded XML file .
*
* @ param RouteCollection $collection Collection to associate with the node
* @ param \DOMElement $node Element to parse
* @ param string $path Full path of the XML file being processed
* @ param string $file Loaded file name
*
* @ throws \InvalidArgumentException When the XML is invalid
*/
protected function parseNode ( RouteCollection $collection , \DOMElement $node , $path , $file )
{
if ( self :: NAMESPACE_URI !== $node -> namespaceURI ) {
return ;
}
switch ( $node -> localName ) {
case 'route' :
$this -> parseRoute ( $collection , $node , $path );
break ;
case 'import' :
$this -> parseImport ( $collection , $node , $path , $file );
break ;
default :
throw new \InvalidArgumentException ( sprintf ( 'Unknown tag "%s" used in file "%s". Expected "route" or "import".' , $node -> localName , $path ));
}
}
/**
* { @ inheritdoc }
*/
public function supports ( $resource , $type = null )
{
2018-11-23 12:29:20 +00:00
return \is_string ( $resource ) && 'xml' === pathinfo ( $resource , PATHINFO_EXTENSION ) && ( ! $type || 'xml' === $type );
2015-08-18 00:00:26 +00:00
}
/**
* Parses a route and adds it to the RouteCollection .
*
* @ param RouteCollection $collection RouteCollection instance
* @ param \DOMElement $node Element to parse that represents a Route
* @ param string $path Full path of the XML file being processed
*
* @ throws \InvalidArgumentException When the XML is invalid
*/
protected function parseRoute ( RouteCollection $collection , \DOMElement $node , $path )
{
2018-11-23 12:29:20 +00:00
if ( '' === ( $id = $node -> getAttribute ( 'id' )) || ! $node -> hasAttribute ( 'path' )) {
2015-08-18 00:00:26 +00:00
throw new \InvalidArgumentException ( sprintf ( 'The <route> element in file "%s" must have an "id" and a "path" attribute.' , $path ));
}
$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 ) = $this -> parseConfigs ( $node , $path );
$route = new Route ( $node -> getAttribute ( 'path' ), $defaults , $requirements , $options , $node -> getAttribute ( 'host' ), $schemes , $methods , $condition );
$collection -> add ( $id , $route );
}
/**
* Parses an import and adds the routes in the resource to the RouteCollection .
*
* @ param RouteCollection $collection RouteCollection instance
* @ param \DOMElement $node Element to parse that represents a Route
* @ param string $path Full path of the XML file being processed
* @ param string $file Loaded file name
*
* @ throws \InvalidArgumentException When the XML is invalid
*/
protected function parseImport ( RouteCollection $collection , \DOMElement $node , $path , $file )
{
if ( '' === $resource = $node -> getAttribute ( 'resource' )) {
throw new \InvalidArgumentException ( sprintf ( 'The <import> element in file "%s" must have a "resource" attribute.' , $path ));
}
$type = $node -> getAttribute ( 'type' );
$prefix = $node -> getAttribute ( 'prefix' );
$host = $node -> hasAttribute ( 'host' ) ? $node -> getAttribute ( 'host' ) : null ;
$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 ;
list ( $defaults , $requirements , $options , $condition ) = $this -> parseConfigs ( $node , $path );
2018-11-23 12:29:20 +00:00
$this -> setCurrentDir ( \dirname ( $path ));
2015-08-18 00:00:26 +00:00
2018-11-23 12:29:20 +00:00
$imported = $this -> import ( $resource , ( '' !== $type ? $type : null ), false , $file );
if ( ! \is_array ( $imported )) {
$imported = array ( $imported );
2015-08-18 00:00:26 +00:00
}
2018-11-23 12:29:20 +00:00
foreach ( $imported as $subCollection ) {
/* @var $subCollection RouteCollection */
$subCollection -> addPrefix ( $prefix );
if ( null !== $host ) {
$subCollection -> setHost ( $host );
}
if ( null !== $condition ) {
$subCollection -> setCondition ( $condition );
}
if ( null !== $schemes ) {
$subCollection -> setSchemes ( $schemes );
}
if ( null !== $methods ) {
$subCollection -> setMethods ( $methods );
}
$subCollection -> addDefaults ( $defaults );
$subCollection -> addRequirements ( $requirements );
$subCollection -> addOptions ( $options );
$collection -> addCollection ( $subCollection );
}
2015-08-18 00:00:26 +00:00
}
/**
* Loads an XML file .
*
* @ param string $file An XML file path
*
* @ return \DOMDocument
*
* @ throws \InvalidArgumentException When loading of XML file fails because of syntax errors
* or when the XML structure is not as expected by the scheme -
* see validate ()
*/
protected function loadFile ( $file )
{
return XmlUtils :: loadFile ( $file , __DIR__ . static :: SCHEME_PATH );
}
/**
* Parses the config elements ( default , requirement , option ) .
*
* @ param \DOMElement $node Element to parse that contains the configs
* @ param string $path Full path of the XML file being processed
*
2017-02-03 00:28:38 +00:00
* @ return array An array with the defaults as first item , requirements as second and options as third
2015-08-18 00:00:26 +00:00
*
* @ throws \InvalidArgumentException When the XML is invalid
*/
private function parseConfigs ( \DOMElement $node , $path )
{
$defaults = array ();
$requirements = array ();
$options = array ();
$condition = null ;
foreach ( $node -> getElementsByTagNameNS ( self :: NAMESPACE_URI , '*' ) as $n ) {
2018-11-23 12:29:20 +00:00
if ( $node !== $n -> parentNode ) {
continue ;
}
2015-08-18 00:00:26 +00:00
switch ( $n -> localName ) {
case 'default' :
if ( $this -> isElementValueNull ( $n )) {
$defaults [ $n -> getAttribute ( 'key' )] = null ;
} else {
2018-11-23 12:29:20 +00:00
$defaults [ $n -> getAttribute ( 'key' )] = $this -> parseDefaultsConfig ( $n , $path );
2015-08-18 00:00:26 +00:00
}
break ;
case 'requirement' :
$requirements [ $n -> getAttribute ( 'key' )] = trim ( $n -> textContent );
break ;
case 'option' :
$options [ $n -> getAttribute ( 'key' )] = trim ( $n -> textContent );
break ;
case 'condition' :
$condition = trim ( $n -> textContent );
break ;
default :
2018-11-23 12:29:20 +00:00
throw new \InvalidArgumentException ( sprintf ( 'Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".' , $n -> localName , $path ));
}
}
if ( $controller = $node -> getAttribute ( 'controller' )) {
if ( isset ( $defaults [ '_controller' ])) {
$name = $node -> hasAttribute ( 'id' ) ? sprintf ( '"%s"' , $node -> getAttribute ( 'id' )) : sprintf ( 'the "%s" tag' , $node -> tagName );
throw new \InvalidArgumentException ( sprintf ( 'The routing file "%s" must not specify both the "controller" attribute and the defaults key "_controller" for %s.' , $path , $name ));
2015-08-18 00:00:26 +00:00
}
2018-11-23 12:29:20 +00:00
$defaults [ '_controller' ] = $controller ;
2015-08-18 00:00:26 +00:00
}
return array ( $defaults , $requirements , $options , $condition );
}
2018-11-23 12:29:20 +00:00
/**
* Parses the " default " elements .
*
* @ param \DOMElement $element The " default " element to parse
* @ param string $path Full path of the XML file being processed
*
* @ return array | bool | float | int | string | null The parsed value of the " default " element
*/
private function parseDefaultsConfig ( \DOMElement $element , $path )
{
if ( $this -> isElementValueNull ( $element )) {
return ;
}
// Check for existing element nodes in the default element. There can
// only be a single element inside a default element. So this element
// (if one was found) can safely be returned.
foreach ( $element -> childNodes as $child ) {
if ( ! $child instanceof \DOMElement ) {
continue ;
}
if ( self :: NAMESPACE_URI !== $child -> namespaceURI ) {
continue ;
}
return $this -> parseDefaultNode ( $child , $path );
}
// If the default element doesn't contain a nested "bool", "int", "float",
// "string", "list", or "map" element, the element contents will be treated
// as the string value of the associated default option.
return trim ( $element -> textContent );
}
/**
* Recursively parses the value of a " default " element .
*
* @ param \DOMElement $node The node value
* @ param string $path Full path of the XML file being processed
*
* @ return array | bool | float | int | string The parsed value
*
* @ throws \InvalidArgumentException when the XML is invalid
*/
private function parseDefaultNode ( \DOMElement $node , $path )
{
if ( $this -> isElementValueNull ( $node )) {
return ;
}
switch ( $node -> localName ) {
case 'bool' :
return 'true' === trim ( $node -> nodeValue ) || '1' === trim ( $node -> nodeValue );
case 'int' :
return ( int ) trim ( $node -> nodeValue );
case 'float' :
return ( float ) trim ( $node -> nodeValue );
case 'string' :
return trim ( $node -> nodeValue );
case 'list' :
$list = array ();
foreach ( $node -> childNodes as $element ) {
if ( ! $element instanceof \DOMElement ) {
continue ;
}
if ( self :: NAMESPACE_URI !== $element -> namespaceURI ) {
continue ;
}
$list [] = $this -> parseDefaultNode ( $element , $path );
}
return $list ;
case 'map' :
$map = array ();
foreach ( $node -> childNodes as $element ) {
if ( ! $element instanceof \DOMElement ) {
continue ;
}
if ( self :: NAMESPACE_URI !== $element -> namespaceURI ) {
continue ;
}
$map [ $element -> getAttribute ( 'key' )] = $this -> parseDefaultNode ( $element , $path );
}
return $map ;
default :
throw new \InvalidArgumentException ( sprintf ( 'Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".' , $node -> localName , $path ));
}
}
2015-08-18 00:00:26 +00:00
private function isElementValueNull ( \DOMElement $element )
{
$namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance' ;
if ( ! $element -> hasAttributeNS ( $namespaceUri , 'nil' )) {
return false ;
}
return 'true' === $element -> getAttributeNS ( $namespaceUri , 'nil' ) || '1' === $element -> getAttributeNS ( $namespaceUri , 'nil' );
}
}