2010-02-17 13:53:31 +00:00
< ? php
/*
2011-01-15 13:29:43 +00:00
* This file is part of the Symfony package .
2010-02-17 13:53:31 +00:00
*
2011-03-06 11:40:06 +00:00
* ( c ) Fabien Potencier < fabien @ symfony . com >
2010-02-17 13:53:31 +00:00
*
2011-01-15 13:29:43 +00:00
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
2010-02-17 13:53:31 +00:00
*/
2011-01-15 13:29:43 +00:00
namespace Symfony\Component\Routing ;
2010-02-17 13:53:31 +00:00
/**
* RouteCompiler compiles Route instances to CompiledRoute instances .
*
2011-03-06 11:40:06 +00:00
* @ author Fabien Potencier < fabien @ symfony . com >
2010-02-17 13:53:31 +00:00
*/
class RouteCompiler implements RouteCompilerInterface
{
2012-04-12 02:30:01 +01:00
const REGEX_DELIMITER = '#' ;
2010-05-06 12:25:53 +01:00
/**
2012-05-05 00:43:00 +01:00
* { @ inheritDoc }
2012-04-27 14:39:00 +01:00
*
2012-08-31 07:36:21 +01:00
* @ throws \LogicException If a variable is referenced more than once or if a required variable
* has a default value that doesn ' t meet its own requirement .
2010-05-06 12:25:53 +01:00
*/
public function compile ( Route $route )
2010-02-17 13:53:31 +00:00
{
2011-04-25 11:03:41 +01:00
$pattern = $route -> getPattern ();
$len = strlen ( $pattern );
2010-05-06 12:25:53 +01:00
$tokens = array ();
2011-04-25 11:03:41 +01:00
$variables = array ();
$pos = 0 ;
2012-04-27 14:39:00 +01:00
preg_match_all ( '#.\{(\w+)\}#' , $pattern , $matches , PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
2011-04-25 11:03:41 +01:00
foreach ( $matches as $match ) {
if ( $text = substr ( $pattern , $pos , $match [ 0 ][ 1 ] - $pos )) {
$tokens [] = array ( 'text' , $text );
2010-05-06 12:25:53 +01:00
}
2012-04-27 14:39:00 +01:00
2011-04-25 11:03:41 +01:00
$pos = $match [ 0 ][ 1 ] + strlen ( $match [ 0 ][ 0 ]);
$var = $match [ 1 ][ 0 ];
2010-02-17 13:53:31 +00:00
2011-04-25 11:03:41 +01:00
if ( $req = $route -> getRequirement ( $var )) {
$regexp = $req ;
} else {
2012-05-07 17:02:33 +01:00
// Use the character preceding the variable as a separator
$separators = array ( $match [ 0 ][ 0 ][ 0 ]);
if ( $pos !== $len ) {
// Use the character following the variable as the separator when available
$separators [] = $pattern [ $pos ];
}
$regexp = sprintf ( '[^%s]+' , preg_quote ( implode ( '' , array_unique ( $separators )), self :: REGEX_DELIMITER ));
2011-04-25 11:03:41 +01:00
}
2010-02-17 13:53:31 +00:00
2011-04-25 11:03:41 +01:00
$tokens [] = array ( 'variable' , $match [ 0 ][ 0 ][ 0 ], $regexp , $var );
2012-02-14 10:41:45 +00:00
if ( in_array ( $var , $variables )) {
throw new \LogicException ( sprintf ( 'Route pattern "%s" cannot reference variable name "%s" more than once.' , $route -> getPattern (), $var ));
}
2011-04-25 11:03:41 +01:00
$variables [] = $var ;
2010-05-06 12:25:53 +01:00
}
2011-04-25 11:03:41 +01:00
if ( $pos < $len ) {
$tokens [] = array ( 'text' , substr ( $pattern , $pos ));
2010-05-06 12:25:53 +01:00
}
2012-08-31 07:36:21 +01:00
// find the first optional token and validate the default values for non-optional variables
$optional = true ;
2011-04-25 11:03:41 +01:00
$firstOptional = INF ;
for ( $i = count ( $tokens ) - 1 ; $i >= 0 ; $i -- ) {
2012-04-10 13:42:59 +01:00
$token = $tokens [ $i ];
if ( 'variable' === $token [ 0 ] && $route -> hasDefault ( $token [ 3 ])) {
2012-08-31 07:36:21 +01:00
if ( $optional ) {
$firstOptional = $i ;
} elseif ( ! preg_match ( '#^' . $token [ 2 ] . '$#' , $route -> getDefault ( $token [ 3 ]))) {
throw new \LogicException ( sprintf ( 'The default value "%s" of the required variable "%s" in pattern "%s" does not match the requirement "%s". ' .
'This route definition makes no sense because this default can neither be used as default for generating URLs nor can it ever be returned by the matching process. ' .
'You should change the default to something that meets the requirement or remove it.' ,
$route -> getDefault ( $token [ 3 ]), $token [ 3 ], $route -> getPattern (), $token [ 2 ]
));
}
2010-05-07 15:09:11 +01:00
} else {
2012-08-31 07:36:21 +01:00
$optional = false ;
2010-05-06 12:25:53 +01:00
}
}
2010-02-17 13:53:31 +00:00
2011-04-25 11:03:41 +01:00
// compute the matching regexp
2012-04-10 13:42:59 +01:00
$regexp = '' ;
for ( $i = 0 , $nbToken = count ( $tokens ); $i < $nbToken ; $i ++ ) {
$regexp .= $this -> computeRegexp ( $tokens , $i , $firstOptional );
2010-05-06 12:25:53 +01:00
}
2010-02-17 13:53:31 +00:00
2011-04-25 11:03:41 +01:00
return new CompiledRoute (
'text' === $tokens [ 0 ][ 0 ] ? $tokens [ 0 ][ 1 ] : '' ,
2012-04-12 02:30:01 +01:00
self :: REGEX_DELIMITER . '^' . $regexp . '$' . self :: REGEX_DELIMITER . 's' ,
2011-04-25 11:03:41 +01:00
array_reverse ( $tokens ),
$variables
);
2010-05-06 12:25:53 +01:00
}
2012-04-10 13:42:59 +01:00
/**
2012-04-12 02:30:01 +01:00
* Computes the regexp used to match a specific token . It can be static text or a subpattern .
2012-04-10 13:42:59 +01:00
*
* @ param array $tokens The route tokens
* @ param integer $index The index of the current token
* @ param integer $firstOptional The index of the first optional token
*
2012-04-12 02:30:01 +01:00
* @ return string The regexp pattern for a single token
2012-04-10 13:42:59 +01:00
*/
private function computeRegexp ( array $tokens , $index , $firstOptional )
{
$token = $tokens [ $index ];
2012-05-21 21:27:15 +01:00
if ( 'text' === $token [ 0 ]) {
2012-04-10 13:42:59 +01:00
// Text tokens
2012-04-12 02:30:01 +01:00
return preg_quote ( $token [ 1 ], self :: REGEX_DELIMITER );
2012-04-10 13:42:59 +01:00
} else {
// Variable tokens
2012-05-20 04:43:44 +01:00
if ( 0 === $index && 0 === $firstOptional ) {
2012-04-10 13:42:59 +01:00
// When the only token is an optional variable token, the separator is required
2012-04-12 02:30:01 +01:00
return sprintf ( '%s(?<%s>%s)?' , preg_quote ( $token [ 1 ], self :: REGEX_DELIMITER ), $token [ 3 ], $token [ 2 ]);
2012-04-10 13:42:59 +01:00
} else {
2012-04-12 02:30:01 +01:00
$regexp = sprintf ( '%s(?<%s>%s)' , preg_quote ( $token [ 1 ], self :: REGEX_DELIMITER ), $token [ 3 ], $token [ 2 ]);
2012-04-10 13:42:59 +01:00
if ( $index >= $firstOptional ) {
2012-04-12 02:30:01 +01:00
// Enclose each optional token in a subpattern to make it optional.
// "?:" means it is non-capturing, i.e. the portion of the subject string that
// matched the optional subpattern is not passed back.
2012-04-10 13:42:59 +01:00
$regexp = " (?: $regexp " ;
2012-04-12 02:30:01 +01:00
$nbTokens = count ( $tokens );
2012-04-10 13:42:59 +01:00
if ( $nbTokens - 1 == $index ) {
// Close the optional subpatterns
2012-05-20 04:43:44 +01:00
$regexp .= str_repeat ( " )? " , $nbTokens - $firstOptional - ( 0 === $firstOptional ? 1 : 0 ));
2012-04-10 13:42:59 +01:00
}
}
2012-04-18 09:41:11 +01:00
2012-04-10 13:42:59 +01:00
return $regexp ;
}
}
}
2010-02-17 13:53:31 +00:00
}