2015-08-17 17:00:26 -07: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\Matcher ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface ;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage ;
use Symfony\Component\HttpFoundation\Request ;
2015-08-17 17:00:26 -07:00
use Symfony\Component\Routing\Exception\MethodNotAllowedException ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\Routing\Exception\NoConfigurationException ;
2015-08-17 17:00:26 -07:00
use Symfony\Component\Routing\Exception\ResourceNotFoundException ;
use Symfony\Component\Routing\RequestContext ;
use Symfony\Component\Routing\Route ;
2018-11-23 12:29:20 +00:00
use Symfony\Component\Routing\RouteCollection ;
2015-08-17 17:00:26 -07:00
/**
* UrlMatcher matches URL based on a set of routes .
*
* @ author Fabien Potencier < fabien @ symfony . com >
*/
class UrlMatcher implements UrlMatcherInterface , RequestMatcherInterface
{
const REQUIREMENT_MATCH = 0 ;
const REQUIREMENT_MISMATCH = 1 ;
const ROUTE_MATCH = 2 ;
protected $context ;
protected $allow = array ();
protected $routes ;
protected $request ;
protected $expressionLanguage ;
/**
* @ var ExpressionFunctionProviderInterface []
*/
protected $expressionLanguageProviders = array ();
public function __construct ( RouteCollection $routes , RequestContext $context )
{
$this -> routes = $routes ;
$this -> context = $context ;
}
/**
* { @ inheritdoc }
*/
public function setContext ( RequestContext $context )
{
$this -> context = $context ;
}
/**
* { @ inheritdoc }
*/
public function getContext ()
{
return $this -> context ;
}
/**
* { @ inheritdoc }
*/
public function match ( $pathinfo )
{
$this -> allow = array ();
if ( $ret = $this -> matchCollection ( rawurldecode ( $pathinfo ), $this -> routes )) {
return $ret ;
}
2018-11-23 12:29:20 +00:00
if ( '/' === $pathinfo && ! $this -> allow ) {
throw new NoConfigurationException ();
}
throw 0 < \count ( $this -> allow )
2015-08-17 17:00:26 -07:00
? new MethodNotAllowedException ( array_unique ( $this -> allow ))
: new ResourceNotFoundException ( sprintf ( 'No routes found for "%s".' , $pathinfo ));
}
/**
* { @ inheritdoc }
*/
public function matchRequest ( Request $request )
{
$this -> request = $request ;
$ret = $this -> match ( $request -> getPathInfo ());
$this -> request = null ;
return $ret ;
}
public function addExpressionLanguageProvider ( ExpressionFunctionProviderInterface $provider )
{
$this -> expressionLanguageProviders [] = $provider ;
}
/**
* Tries to match a URL with a set of routes .
*
* @ param string $pathinfo The path info to be parsed
* @ param RouteCollection $routes The set of routes
*
* @ return array An array of parameters
*
2018-11-23 12:29:20 +00:00
* @ throws NoConfigurationException If no routing configuration could be found
2015-08-17 17:00:26 -07:00
* @ throws ResourceNotFoundException If the resource could not be found
* @ throws MethodNotAllowedException If the resource was found but the request method is not allowed
*/
protected function matchCollection ( $pathinfo , RouteCollection $routes )
{
foreach ( $routes as $name => $route ) {
$compiledRoute = $route -> compile ();
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
if ( '' !== $compiledRoute -> getStaticPrefix () && 0 !== strpos ( $pathinfo , $compiledRoute -> getStaticPrefix ())) {
continue ;
}
if ( ! preg_match ( $compiledRoute -> getRegex (), $pathinfo , $matches )) {
continue ;
}
$hostMatches = array ();
if ( $compiledRoute -> getHostRegex () && ! preg_match ( $compiledRoute -> getHostRegex (), $this -> context -> getHost (), $hostMatches )) {
continue ;
}
2018-11-23 12:29:20 +00:00
$status = $this -> handleRouteRequirements ( $pathinfo , $name , $route );
if ( self :: REQUIREMENT_MISMATCH === $status [ 0 ]) {
continue ;
}
2015-08-17 17:00:26 -07:00
// check HTTP method requirement
if ( $requiredMethods = $route -> getMethods ()) {
// HEAD and GET are equivalent as per RFC
if ( 'HEAD' === $method = $this -> context -> getMethod ()) {
$method = 'GET' ;
}
2018-11-23 12:29:20 +00:00
if ( ! \in_array ( $method , $requiredMethods )) {
if ( self :: REQUIREMENT_MATCH === $status [ 0 ]) {
$this -> allow = array_merge ( $this -> allow , $requiredMethods );
}
2015-08-17 17:00:26 -07:00
continue ;
}
}
2018-11-23 12:29:20 +00:00
return $this -> getAttributes ( $route , $name , array_replace ( $matches , $hostMatches , isset ( $status [ 1 ]) ? $status [ 1 ] : array ()));
2015-08-17 17:00:26 -07:00
}
}
/**
* Returns an array of values to use as request attributes .
*
* As this method requires the Route object , it is not available
* in matchers that do not have access to the matched Route instance
* ( like the PHP and Apache matcher dumpers ) .
*
* @ param Route $route The route we are matching against
* @ param string $name The name of the route
* @ param array $attributes An array of attributes from the matcher
*
* @ return array An array of parameters
*/
protected function getAttributes ( Route $route , $name , array $attributes )
{
$attributes [ '_route' ] = $name ;
return $this -> mergeDefaults ( $attributes , $route -> getDefaults ());
}
/**
* Handles specific route requirements .
*
* @ param string $pathinfo The path
* @ param string $name The route name
* @ param Route $route The route
*
* @ return array The first element represents the status , the second contains additional information
*/
protected function handleRouteRequirements ( $pathinfo , $name , Route $route )
{
// expression condition
2018-11-23 12:29:20 +00:00
if ( $route -> getCondition () && ! $this -> getExpressionLanguage () -> evaluate ( $route -> getCondition (), array ( 'context' => $this -> context , 'request' => $this -> request ? : $this -> createRequest ( $pathinfo )))) {
2015-08-17 17:00:26 -07:00
return array ( self :: REQUIREMENT_MISMATCH , null );
}
// check HTTP scheme requirement
$scheme = $this -> context -> getScheme ();
$status = $route -> getSchemes () && ! $route -> hasScheme ( $scheme ) ? self :: REQUIREMENT_MISMATCH : self :: REQUIREMENT_MATCH ;
return array ( $status , null );
}
/**
* Get merged default parameters .
*
* @ param array $params The parameters
* @ param array $defaults The defaults
*
* @ return array Merged default parameters
*/
protected function mergeDefaults ( $params , $defaults )
{
foreach ( $params as $key => $value ) {
2018-11-23 12:29:20 +00:00
if ( ! \is_int ( $key )) {
2015-08-17 17:00:26 -07:00
$defaults [ $key ] = $value ;
}
}
return $defaults ;
}
protected function getExpressionLanguage ()
{
if ( null === $this -> expressionLanguage ) {
if ( ! class_exists ( 'Symfony\Component\ExpressionLanguage\ExpressionLanguage' )) {
throw new \RuntimeException ( 'Unable to use expressions as the Symfony ExpressionLanguage component is not installed.' );
}
$this -> expressionLanguage = new ExpressionLanguage ( null , $this -> expressionLanguageProviders );
}
return $this -> expressionLanguage ;
}
2018-11-23 12:29:20 +00:00
/**
* @ internal
*/
protected function createRequest ( $pathinfo )
{
if ( ! class_exists ( 'Symfony\Component\HttpFoundation\Request' )) {
return null ;
}
return Request :: create ( $this -> context -> getScheme () . '://' . $this -> context -> getHost () . $this -> context -> getBaseUrl () . $pathinfo , $this -> context -> getMethod (), $this -> context -> getParameters (), array (), array (), array (
'SCRIPT_FILENAME' => $this -> context -> getBaseUrl (),
'SCRIPT_NAME' => $this -> context -> getBaseUrl (),
));
}
2015-08-17 17:00:26 -07:00
}