Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

254 lines (209 sloc) 8.459 kB
<?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;
use Symfony\Component\Routing\Route;
/**
* Dumps a set of Apache mod_rewrite rules.
*
* @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\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]");
$methodVars = array();
$hostnameRegexUnique = 0;
$prevHostnameRegex = '';
foreach ($this->getRoutes()->all() as $name => $route) {
$compiledRoute = $route->compile();
$hostnameRegex = $compiledRoute->getHostnameRegex();
if (null !== $hostnameRegex && $prevHostnameRegex !== $hostnameRegex) {
$prevHostnameRegex = $hostnameRegex;
$hostnameRegexUnique++;
$rule = array();
$regex = $this->regexToApacheRegex($hostnameRegex);
$regex = self::escape($regex, ' ', '\\');
$rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex);
$variables = array();
$variables[] = sprintf('E=__ROUTING_hostname_%s:1', $hostnameRegexUnique);
foreach ($compiledRoute->getHostnameVariables() as $i => $variable) {
$variables[] = sprintf('E=__ROUTING_hostname_%s_%s:%%%d', $hostnameRegexUnique, $variable, $i+1);
}
$variables = implode(',', $variables);
$rule[] = sprintf('RewriteRule .? - [%s]', $variables);
$rules[] = implode("\n", $rule);
}
$rules[] = $this->dumpRoute($name, $route, $options, $hostnameRegexUnique);
if ($req = $route->getRequirement('_method')) {
$methods = explode('|', strtoupper($req));
$methodVars = array_merge($methodVars, $methods);
}
}
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 $hostnameRegexUnique Unique identifier for the hostname regex
*
* @return string The compiled route
*/
private function dumpRoute($name, $route, array $options, $hostnameRegexUnique)
{
$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->getHostnameVariables() as $variable) {
$variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_hostname_%s_%s}', $variable, $hostnameRegexUnique, $variable);
}
foreach ($compiledRoute->getPathVariables() as $i => $variable) {
$variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i + 1);
}
foreach ($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 ($hostnameRegex = $compiledRoute->getHostnameRegex()) {
$rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique);
}
$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 ($hostnameRegex = $compiledRoute->getHostnameRegex()) {
$rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique);
}
$rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$';
$rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]';
}
// the main rule
if ($hostnameRegex = $compiledRoute->getHostnameRegex()) {
$rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique);
}
$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 = array();
if ($req = $route->getRequirement('_method')) {
$methods = explode('|', strtoupper($req));
// 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('/\?<.+?>/', '', 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;
}
}
Jump to Line
Something went wrong with that request. Please try again.