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\Matcher\Dumper ;
2015-08-27 19:03:05 +00:00
@ trigger_error ( 'The ' . __NAMESPACE__ . '\ApacheMatcherDumper class is deprecated since version 2.5 and will be removed in 3.0. It\'s hard to replicate the behaviour of the PHP implementation and the performance gains are minimal.' , E_USER_DEPRECATED );
2015-08-18 00:00:26 +00:00
use Symfony\Component\Routing\Route ;
/**
* Dumps a set of Apache mod_rewrite rules .
*
* @ deprecated since version 2.5 , to be removed in 3.0 .
* The performance gains are minimal and it ' s very hard to replicate
* the behavior of PHP implementation .
*
* @ author Fabien Potencier < fabien @ symfony . com >
* @ author Kris Wallsmith < kris @ symfony . com >
*/
class ApacheMatcherDumper extends MatcherDumper
{
/**
* Dumps a set of Apache mod_rewrite rules .
*
* Available options :
*
* * script_name : The script name ( app . php by default )
* * base_uri : The base URI ( " " by default )
*
* @ param array $options An array of options
*
* @ return string A string to be used as Apache rewrite rules
*
* @ throws \LogicException When the route regex is invalid
*/
public function dump ( array $options = array ())
{
$options = array_merge ( array (
'script_name' => 'app.php' ,
'base_uri' => '' ,
), $options );
$options [ 'script_name' ] = self :: escape ( $options [ 'script_name' ], ' ' , '\\' );
$rules = array ( " # skip \" real \" requests \n RewriteCond % { REQUEST_FILENAME} -f \n RewriteRule .* - [QSA,L] " );
$methodVars = array ();
$hostRegexUnique = 0 ;
$prevHostRegex = '' ;
foreach ( $this -> getRoutes () -> all () as $name => $route ) {
if ( $route -> getCondition ()) {
throw new \LogicException ( sprintf ( 'Unable to dump the routes for Apache as route "%s" has a condition.' , $name ));
}
$compiledRoute = $route -> compile ();
$hostRegex = $compiledRoute -> getHostRegex ();
if ( null !== $hostRegex && $prevHostRegex !== $hostRegex ) {
$prevHostRegex = $hostRegex ;
++ $hostRegexUnique ;
$rule = array ();
$regex = $this -> regexToApacheRegex ( $hostRegex );
$regex = self :: escape ( $regex , ' ' , '\\' );
$rule [] = sprintf ( 'RewriteCond %%{HTTP:Host} %s' , $regex );
$variables = array ();
$variables [] = sprintf ( 'E=__ROUTING_host_%s:1' , $hostRegexUnique );
foreach ( $compiledRoute -> getHostVariables () as $i => $variable ) {
$variables [] = sprintf ( 'E=__ROUTING_host_%s_%s:%%%d' , $hostRegexUnique , $variable , $i + 1 );
}
$variables = implode ( ',' , $variables );
$rule [] = sprintf ( 'RewriteRule .? - [%s]' , $variables );
$rules [] = implode ( " \n " , $rule );
}
$rules [] = $this -> dumpRoute ( $name , $route , $options , $hostRegexUnique );
$methodVars = array_merge ( $methodVars , $route -> getMethods ());
}
if ( 0 < count ( $methodVars )) {
$rule = array ( '# 405 Method Not Allowed' );
$methodVars = array_values ( array_unique ( $methodVars ));
if ( in_array ( 'GET' , $methodVars ) && ! in_array ( 'HEAD' , $methodVars )) {
$methodVars [] = 'HEAD' ;
}
foreach ( $methodVars as $i => $methodVar ) {
$rule [] = sprintf ( 'RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s' , $methodVar , isset ( $methodVars [ $i + 1 ]) ? ' [OR]' : '' );
}
$rule [] = sprintf ( 'RewriteRule .* %s [QSA,L]' , $options [ 'script_name' ]);
$rules [] = implode ( " \n " , $rule );
}
return implode ( " \n \n " , $rules ) . " \n " ;
}
/**
* Dumps a single route .
*
* @ param string $name Route name
* @ param Route $route The route
* @ param array $options Options
* @ param bool $hostRegexUnique Unique identifier for the host regex
*
* @ return string The compiled route
*/
private function dumpRoute ( $name , $route , array $options , $hostRegexUnique )
{
$compiledRoute = $route -> compile ();
// prepare the apache regex
$regex = $this -> regexToApacheRegex ( $compiledRoute -> getRegex ());
$regex = '^' . self :: escape ( preg_quote ( $options [ 'base_uri' ]) . substr ( $regex , 1 ), ' ' , '\\' );
$methods = $this -> getRouteMethods ( $route );
$hasTrailingSlash = ( ! $methods || in_array ( 'HEAD' , $methods )) && '/$' === substr ( $regex , - 2 ) && '^/$' !== $regex ;
$variables = array ( 'E=_ROUTING_route:' . $name );
foreach ( $compiledRoute -> getHostVariables () as $variable ) {
$variables [] = sprintf ( 'E=_ROUTING_param_%s:%%{ENV:__ROUTING_host_%s_%s}' , $variable , $hostRegexUnique , $variable );
}
foreach ( $compiledRoute -> getPathVariables () as $i => $variable ) {
$variables [] = 'E=_ROUTING_param_' . $variable . ':%' . ( $i + 1 );
}
foreach ( $this -> normalizeValues ( $route -> getDefaults ()) as $key => $value ) {
$variables [] = 'E=_ROUTING_default_' . $key . ':' . strtr ( $value , array (
':' => '\\:' ,
'=' => '\\=' ,
'\\' => '\\\\' ,
' ' => '\\ ' ,
));
}
$variables = implode ( ',' , $variables );
$rule = array ( " # $name " );
// method mismatch
if ( 0 < count ( $methods )) {
$allow = array ();
foreach ( $methods as $method ) {
$allow [] = 'E=_ROUTING_allow_' . $method . ':1' ;
}
if ( $compiledRoute -> getHostRegex ()) {
$rule [] = sprintf ( 'RewriteCond %%{ENV:__ROUTING_host_%s} =1' , $hostRegexUnique );
}
$rule [] = " RewriteCond % { REQUEST_URI} $regex " ;
$rule [] = sprintf ( 'RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]' , implode ( '|' , $methods ));
$rule [] = sprintf ( 'RewriteRule .* - [S=%d,%s]' , $hasTrailingSlash ? 2 : 1 , implode ( ',' , $allow ));
}
// redirect with trailing slash appended
if ( $hasTrailingSlash ) {
if ( $compiledRoute -> getHostRegex ()) {
$rule [] = sprintf ( 'RewriteCond %%{ENV:__ROUTING_host_%s} =1' , $hostRegexUnique );
}
$rule [] = 'RewriteCond %{REQUEST_URI} ' . substr ( $regex , 0 , - 2 ) . '$' ;
$rule [] = 'RewriteRule .* $0/ [QSA,L,R=301]' ;
}
// the main rule
if ( $compiledRoute -> getHostRegex ()) {
$rule [] = sprintf ( 'RewriteCond %%{ENV:__ROUTING_host_%s} =1' , $hostRegexUnique );
}
$rule [] = " RewriteCond % { REQUEST_URI} $regex " ;
$rule [] = " RewriteRule .* { $options [ 'script_name' ] } [QSA,L, $variables ] " ;
return implode ( " \n " , $rule );
}
/**
* Returns methods allowed for a route .
*
* @ param Route $route The route
*
* @ return array The methods
*/
private function getRouteMethods ( Route $route )
{
$methods = $route -> getMethods ();
// GET and HEAD are equivalent
if ( in_array ( 'GET' , $methods ) && ! in_array ( 'HEAD' , $methods )) {
$methods [] = 'HEAD' ;
}
return $methods ;
}
/**
* Converts a regex to make it suitable for mod_rewrite .
*
* @ param string $regex The regex
*
* @ return string The converted regex
*/
private function regexToApacheRegex ( $regex )
{
$regexPatternEnd = strrpos ( $regex , $regex [ 0 ]);
return preg_replace ( '/\?P<.+?>/' , '' , substr ( $regex , 1 , $regexPatternEnd - 1 ));
}
/**
* Escapes a string .
*
* @ param string $string The string to be escaped
* @ param string $char The character to be escaped
* @ param string $with The character to be used for escaping
*
* @ return string The escaped string
*/
private static function escape ( $string , $char , $with )
{
$escaped = false ;
$output = '' ;
foreach ( str_split ( $string ) as $symbol ) {
if ( $escaped ) {
$output .= $symbol ;
$escaped = false ;
continue ;
}
if ( $symbol === $char ) {
$output .= $with . $char ;
continue ;
}
if ( $symbol === $with ) {
$escaped = true ;
}
$output .= $symbol ;
}
return $output ;
}
/**
* Normalizes an array of values .
*
* @ param array $values
*
* @ return string []
*/
private function normalizeValues ( array $values )
{
$normalizedValues = array ();
foreach ( $values as $key => $value ) {
if ( is_array ( $value )) {
foreach ( $value as $index => $bit ) {
$normalizedValues [ sprintf ( '%s[%s]' , $key , $index )] = $bit ;
}
} else {
$normalizedValues [ $key ] = ( string ) $value ;
}
}
return $normalizedValues ;
}
}