This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Components/Routing/RouteCompiler.php

237 lines
8.2 KiB
PHP
Raw Normal View History

2010-02-17 13:53:31 +00:00
<?php
namespace Symfony\Components\Routing;
/*
* This file is part of the Symfony framework.
2010-02-17 13:53:31 +00:00
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* RouteCompiler compiles Route instances to CompiledRoute instances.
*
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class RouteCompiler implements RouteCompilerInterface
{
protected $options;
protected $route;
protected $variables;
protected $firstOptional;
protected $segments;
protected $tokens;
protected $staticPrefix;
protected $regex;
/**
* Compiles the current route instance.
*
* @param Route $route A Route instance
*
2010-07-01 19:17:03 +01:00
* @return CompiledRoute A CompiledRoute instance
*/
public function compile(Route $route)
2010-02-17 13:53:31 +00:00
{
$this->route = $route;
$this->firstOptional = 0;
$this->segments = array();
$this->variables = array();
$this->tokens = array();
$this->staticPrefix = '';
$this->regex = '';
$this->options = $this->getOptions();
$this->preCompile();
$this->tokenize();
foreach ($this->tokens as $token) {
call_user_func_array(array($this, 'compileFor'.ucfirst(array_shift($token))), $token);
}
$this->postCompile();
$separator = '';
if (count($this->tokens)) {
$lastToken = $this->tokens[count($this->tokens) - 1];
$separator = 'separator' == $lastToken[0] ? $lastToken[2] : '';
}
$this->regex = "#^".implode("", $this->segments)."".preg_quote($separator, '#')."$#x";
// optimize tokens for generation
$tokens = array();
foreach ($this->tokens as $i => $token) {
if ($i + 1 === count($this->tokens) && 'separator' === $token[0]) {
// trailing /
$tokens[] = array('text', $token[2], '', null);
} elseif ('separator' !== $token[0]) {
$tokens[] = $token;
}
}
$tokens = array_reverse($tokens);
return new CompiledRoute($this->route, $this->staticPrefix, $this->regex, $tokens, $this->variables);
2010-02-17 13:53:31 +00:00
}
/**
* Pre-compiles a route.
*/
protected function preCompile()
2010-02-17 13:53:31 +00:00
{
}
/**
* Post-compiles a route.
*/
protected function postCompile()
2010-02-17 13:53:31 +00:00
{
// all segments after the last static segment are optional
// be careful, the n-1 is optional only if n is empty
for ($i = $this->firstOptional, $max = count($this->segments); $i < $max; $i++) {
$this->segments[$i] = (0 == $i ? '/?' : '').str_repeat(' ', $i - $this->firstOptional).'(?:'.$this->segments[$i];
$this->segments[] = str_repeat(' ', $max - $i - 1).')?';
}
$this->staticPrefix = '';
foreach ($this->tokens as $token) {
switch ($token[0]) {
case 'separator':
break;
case 'text':
// text is static
$this->staticPrefix .= $token[1].$token[2];
break;
default:
// everything else indicates variable parts. break switch and for loop
break 2;
}
}
2010-02-17 13:53:31 +00:00
}
/**
* Tokenizes the route.
*
* @throws \InvalidArgumentException When route can't be parsed
*/
protected function tokenize()
2010-02-17 13:53:31 +00:00
{
$this->tokens = array();
$buffer = $this->route->getPattern();
$afterASeparator = false;
$currentSeparator = '';
// a route is an array of (separator + variable) or (separator + text) segments
while (strlen($buffer)) {
if (false !== $this->tokenizeBufferBefore($buffer, $tokens, $afterASeparator, $currentSeparator)) {
// a custom token
$this->customToken = true;
} else if ($afterASeparator && preg_match('#^'.$this->options['variable_prefix_regex'].'('.$this->options['variable_regex'].')#', $buffer, $match)) {
// a variable
$this->tokens[] = array('variable', $currentSeparator, $match[0], $match[1]);
$currentSeparator = '';
$buffer = substr($buffer, strlen($match[0]));
$afterASeparator = false;
} else if ($afterASeparator && preg_match('#^('.$this->options['text_regex'].')(?:'.$this->options['segment_separators_regex'].'|$)#', $buffer, $match)) {
// a text
$this->tokens[] = array('text', $currentSeparator, $match[1], null);
$currentSeparator = '';
$buffer = substr($buffer, strlen($match[1]));
$afterASeparator = false;
} else if (!$afterASeparator && preg_match('#^'.$this->options['segment_separators_regex'].'#', $buffer, $match)) {
// a separator
$this->tokens[] = array('separator', $currentSeparator, $match[0], null);
$currentSeparator = $match[0];
$buffer = substr($buffer, strlen($match[0]));
$afterASeparator = true;
} else if (false !== $this->tokenizeBufferAfter($buffer, $tokens, $afterASeparator, $currentSeparator)) {
// a custom token
$this->customToken = true;
} else {
// parsing problem
throw new \InvalidArgumentException(sprintf('Unable to parse "%s" route near "%s".', $this->route->getPattern(), $buffer));
}
}
2010-02-17 13:53:31 +00:00
}
/**
* Tokenizes the buffer before default logic is applied.
*
* This method must return false if the buffer has not been parsed.
*
* @param string $buffer The current route buffer
* @param array $tokens An array of current tokens
* @param Boolean $afterASeparator Whether the buffer is just after a separator
* @param string $currentSeparator The last matched separator
*
* @return Boolean true if a token has been generated, false otherwise
*/
protected function tokenizeBufferBefore(&$buffer, &$tokens, &$afterASeparator, &$currentSeparator)
2010-02-17 13:53:31 +00:00
{
return false;
2010-02-17 13:53:31 +00:00
}
/**
* Tokenizes the buffer after default logic is applied.
*
* This method must return false if the buffer has not been parsed.
*
* @param string $buffer The current route buffer
* @param array $tokens An array of current tokens
* @param Boolean $afterASeparator Whether the buffer is just after a separator
* @param string $currentSeparator The last matched separator
*
* @return Boolean true if a token has been generated, false otherwise
*/
protected function tokenizeBufferAfter(&$buffer, &$tokens, &$afterASeparator, &$currentSeparator)
2010-02-17 13:53:31 +00:00
{
return false;
}
2010-02-17 13:53:31 +00:00
protected function compileForText($separator, $text)
{
$this->firstOptional = count($this->segments) + 1;
2010-02-17 13:53:31 +00:00
$this->segments[] = preg_quote($separator, '#').preg_quote($text, '#');
2010-02-17 13:53:31 +00:00
}
protected function compileForVariable($separator, $name, $variable)
2010-02-17 13:53:31 +00:00
{
if (null === $requirement = $this->route->getRequirement($variable)) {
$requirement = $this->options['variable_content_regex'];
}
$this->segments[] = preg_quote($separator, '#').'(?P<'.$variable.'>'.$requirement.')';
$this->variables[$variable] = $name;
if (!$this->route->getDefault($variable)) {
$this->firstOptional = count($this->segments);
}
2010-02-17 13:53:31 +00:00
}
protected function compileForSeparator($separator, $regexSeparator)
2010-02-17 13:53:31 +00:00
{
}
protected function getOptions()
{
$options = $this->route->getOptions();
2010-02-17 13:53:31 +00:00
// compute some regexes
$quoter = function ($a) { return preg_quote($a, '#'); };
$options['variable_prefix_regex'] = '(?:'.implode('|', array_map($quoter, $options['variable_prefixes'])).')';
$options['segment_separators_regex'] = '(?:'.implode('|', array_map($quoter, $options['segment_separators'])).')';
$options['variable_content_regex'] = '[^'.implode('', array_map($quoter, $options['segment_separators'])).']+?';
2010-02-17 13:53:31 +00:00
return $options;
}
2010-02-17 13:53:31 +00:00
}