2015-08-17 17:00:26 -07:00
< ? php
/*
* This file is part of the Symfony CMF package .
*
2016-10-06 15:16:20 -07:00
* ( c ) 2011 - 2015 Symfony CMF
2015-08-17 17:00:26 -07:00
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Cmf\Component\Routing ;
use Symfony\Component\Routing\RouterInterface ;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface ;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface ;
use Symfony\Component\Routing\RequestContext ;
use Symfony\Component\Routing\RequestContextAwareInterface ;
use Symfony\Component\Routing\Exception\ResourceNotFoundException ;
use Symfony\Component\Routing\Exception\RouteNotFoundException ;
use Symfony\Component\Routing\Exception\MethodNotAllowedException ;
use Symfony\Component\Routing\RouteCollection ;
use Symfony\Component\HttpFoundation\Request ;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface ;
use Psr\Log\LoggerInterface ;
/**
* The ChainRouter allows to combine several routers to try in a defined order .
*
* @ author Henrik Bjornskov < henrik @ bjrnskov . dk >
* @ author Magnus Nordlander < magnus @ e - butik . se >
*/
class ChainRouter implements ChainRouterInterface , WarmableInterface
{
/**
* @ var RequestContext
*/
private $context ;
/**
2016-10-06 15:16:20 -07:00
* Array of arrays of routers grouped by priority .
*
2015-08-17 17:00:26 -07:00
* @ var array
*/
private $routers = array ();
/**
* @ var RouterInterface [] Array of routers , sorted by priority
*/
private $sortedRouters ;
/**
* @ var RouteCollection
*/
private $routeCollection ;
/**
* @ var null | LoggerInterface
*/
protected $logger ;
/**
* @ param LoggerInterface $logger
*/
public function __construct ( LoggerInterface $logger = null )
{
$this -> logger = $logger ;
}
/**
* @ return RequestContext
*/
public function getContext ()
{
return $this -> context ;
}
/**
* { @ inheritdoc }
*/
public function add ( $router , $priority = 0 )
{
if ( ! $router instanceof RouterInterface
&& ! ( $router instanceof RequestMatcherInterface && $router instanceof UrlGeneratorInterface )
) {
throw new \InvalidArgumentException ( sprintf ( '%s is not a valid router.' , get_class ( $router )));
}
if ( empty ( $this -> routers [ $priority ])) {
$this -> routers [ $priority ] = array ();
}
$this -> routers [ $priority ][] = $router ;
$this -> sortedRouters = array ();
}
/**
* { @ inheritdoc }
*/
public function all ()
{
if ( empty ( $this -> sortedRouters )) {
$this -> sortedRouters = $this -> sortRouters ();
// setContext() is done here instead of in add() to avoid fatal errors when clearing and warming up caches
// See https://github.com/symfony-cmf/Routing/pull/18
$context = $this -> getContext ();
if ( null !== $context ) {
foreach ( $this -> sortedRouters as $router ) {
if ( $router instanceof RequestContextAwareInterface ) {
$router -> setContext ( $context );
}
}
}
}
return $this -> sortedRouters ;
}
/**
* Sort routers by priority .
2016-10-06 15:16:20 -07:00
* The highest priority number is the highest priority ( reverse sorting ) .
2015-08-17 17:00:26 -07:00
*
* @ return RouterInterface []
*/
protected function sortRouters ()
{
$sortedRouters = array ();
krsort ( $this -> routers );
foreach ( $this -> routers as $routers ) {
$sortedRouters = array_merge ( $sortedRouters , $routers );
}
return $sortedRouters ;
}
/**
* { @ inheritdoc }
*
* Loops through all routes and tries to match the passed url .
*
* Note : You should use matchRequest if you can .
*/
2016-10-06 15:16:20 -07:00
public function match ( $pathinfo )
2015-08-17 17:00:26 -07:00
{
2016-10-06 15:16:20 -07:00
return $this -> doMatch ( $pathinfo );
2015-08-17 17:00:26 -07:00
}
/**
* { @ inheritdoc }
*
* Loops through all routes and tries to match the passed request .
*/
public function matchRequest ( Request $request )
{
return $this -> doMatch ( $request -> getPathInfo (), $request );
}
/**
* Loops through all routers and tries to match the passed request or url .
*
* At least the url must be provided , if a request is additionally provided
* the request takes precedence .
*
2016-10-06 15:16:20 -07:00
* @ param string $pathinfo
2015-08-17 17:00:26 -07:00
* @ param Request $request
*
* @ return array An array of parameters
*
* @ throws ResourceNotFoundException If no router matched .
*/
2016-10-06 15:16:20 -07:00
private function doMatch ( $pathinfo , Request $request = null )
2015-08-17 17:00:26 -07:00
{
$methodNotAllowed = null ;
$requestForMatching = $request ;
foreach ( $this -> all () as $router ) {
try {
// the request/url match logic is the same as in Symfony/Component/HttpKernel/EventListener/RouterListener.php
// matching requests is more powerful than matching URLs only, so try that first
if ( $router instanceof RequestMatcherInterface ) {
if ( empty ( $requestForMatching )) {
2016-10-06 15:16:20 -07:00
$requestForMatching = $this -> rebuildRequest ( $pathinfo );
2015-08-17 17:00:26 -07:00
}
return $router -> matchRequest ( $requestForMatching );
}
2016-10-06 15:16:20 -07:00
2015-08-17 17:00:26 -07:00
// every router implements the match method
2016-10-06 15:16:20 -07:00
return $router -> match ( $pathinfo );
2015-08-17 17:00:26 -07:00
} catch ( ResourceNotFoundException $e ) {
if ( $this -> logger ) {
$this -> logger -> debug ( 'Router ' . get_class ( $router ) . ' was not able to match, message "' . $e -> getMessage () . '"' );
}
// Needs special care
} catch ( MethodNotAllowedException $e ) {
if ( $this -> logger ) {
$this -> logger -> debug ( 'Router ' . get_class ( $router ) . ' throws MethodNotAllowedException with message "' . $e -> getMessage () . '"' );
}
$methodNotAllowed = $e ;
}
}
$info = $request
? " this request \n $request "
2016-10-06 15:16:20 -07:00
: " url ' $pathinfo ' " ;
2015-08-17 17:00:26 -07:00
throw $methodNotAllowed ? : new ResourceNotFoundException ( " None of the routers in the chain matched $info " );
}
/**
* { @ inheritdoc }
*
* Loops through all registered routers and returns a router if one is found .
* It will always return the first route generated .
*/
2016-10-06 15:16:20 -07:00
public function generate ( $name , $parameters = array (), $absolute = UrlGeneratorInterface :: ABSOLUTE_PATH )
2015-08-17 17:00:26 -07:00
{
$debug = array ();
foreach ( $this -> all () as $router ) {
// if $router does not announce it is capable of handling
// non-string routes and $name is not a string, continue
if ( $name && ! is_string ( $name ) && ! $router instanceof VersatileGeneratorInterface ) {
continue ;
}
// If $router is versatile and doesn't support this route name, continue
if ( $router instanceof VersatileGeneratorInterface && ! $router -> supports ( $name )) {
continue ;
}
try {
return $router -> generate ( $name , $parameters , $absolute );
} catch ( RouteNotFoundException $e ) {
$hint = $this -> getErrorMessage ( $name , $router , $parameters );
$debug [] = $hint ;
if ( $this -> logger ) {
$this -> logger -> debug ( 'Router ' . get_class ( $router ) . " was unable to generate route. Reason: ' $hint ': " . $e -> getMessage ());
}
}
}
if ( $debug ) {
$debug = array_unique ( $debug );
$info = implode ( ', ' , $debug );
} else {
$info = $this -> getErrorMessage ( $name );
}
throw new RouteNotFoundException ( sprintf ( 'None of the chained routers were able to generate route: %s' , $info ));
}
2016-10-06 15:16:20 -07:00
/**
* Rebuild the request object from a URL with the help of the RequestContext .
*
* If the request context is not set , this simply returns the request object built from $uri .
*
* @ param string $pathinfo
*
* @ return Request
*/
private function rebuildRequest ( $pathinfo )
{
if ( ! $this -> context ) {
return Request :: create ( 'http://localhost' . $pathinfo );
}
$uri = $pathinfo ;
$server = array ();
if ( $this -> context -> getBaseUrl ()) {
$uri = $this -> context -> getBaseUrl () . $pathinfo ;
$server [ 'SCRIPT_FILENAME' ] = $this -> context -> getBaseUrl ();
$server [ 'PHP_SELF' ] = $this -> context -> getBaseUrl ();
}
$host = $this -> context -> getHost () ? : 'localhost' ;
if ( 'https' === $this -> context -> getScheme () && 443 !== $this -> context -> getHttpsPort ()) {
$host .= ':' . $this -> context -> getHttpsPort ();
}
if ( 'http' === $this -> context -> getScheme () && 80 !== $this -> context -> getHttpPort ()) {
$host .= ':' . $this -> context -> getHttpPort ();
}
$uri = $this -> context -> getScheme () . '://' . $host . $uri . '?' . $this -> context -> getQueryString ();
return Request :: create ( $uri , $this -> context -> getMethod (), $this -> context -> getParameters (), array (), array (), $server );
}
2015-08-17 17:00:26 -07:00
private function getErrorMessage ( $name , $router = null , $parameters = null )
{
if ( $router instanceof VersatileGeneratorInterface ) {
$displayName = $router -> getRouteDebugMessage ( $name , $parameters );
} elseif ( is_object ( $name )) {
$displayName = method_exists ( $name , '__toString' )
? ( string ) $name
: get_class ( $name )
;
} else {
$displayName = ( string ) $name ;
}
return " Route ' $displayName ' not found " ;
}
/**
* { @ inheritdoc }
*/
public function setContext ( RequestContext $context )
{
foreach ( $this -> all () as $router ) {
if ( $router instanceof RequestContextAwareInterface ) {
$router -> setContext ( $context );
}
}
$this -> context = $context ;
}
/**
* { @ inheritdoc }
*
* check for each contained router if it can warmup
*/
public function warmUp ( $cacheDir )
{
foreach ( $this -> all () as $router ) {
if ( $router instanceof WarmableInterface ) {
$router -> warmUp ( $cacheDir );
}
}
}
/**
* { @ inheritdoc }
*/
public function getRouteCollection ()
{
if ( ! $this -> routeCollection instanceof RouteCollection ) {
$this -> routeCollection = new ChainRouteCollection ();
foreach ( $this -> all () as $router ) {
$this -> routeCollection -> addCollection ( $router -> getRouteCollection ());
}
}
return $this -> routeCollection ;
}
2016-10-06 15:16:20 -07:00
/**
* Identify if any routers have been added into the chain yet .
*
* @ return bool
*/
public function hasRouters ()
{
return ! empty ( $this -> routers );
}
2015-08-17 17:00:26 -07:00
}