Browse Source
Include copy of Symfony routing component, and don't use composer
remotes/origin/stable5
Include copy of Symfony routing component, and don't use composer
remotes/origin/stable5
committed by
Robin Appelman
44 changed files with 4037 additions and 22 deletions
-
4.gitignore
-
BIN3rdparty/bin/composer
-
1033rdparty/symfony/routing/Symfony/Component/Routing/Annotation/Route.php
-
1343rdparty/symfony/routing/Symfony/Component/Routing/CompiledRoute.php
-
233rdparty/symfony/routing/Symfony/Component/Routing/Exception/ExceptionInterface.php
-
233rdparty/symfony/routing/Symfony/Component/Routing/Exception/InvalidParameterException.php
-
383rdparty/symfony/routing/Symfony/Component/Routing/Exception/MethodNotAllowedException.php
-
243rdparty/symfony/routing/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php
-
253rdparty/symfony/routing/Symfony/Component/Routing/Exception/ResourceNotFoundException.php
-
233rdparty/symfony/routing/Symfony/Component/Routing/Exception/RouteNotFoundException.php
-
393rdparty/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumper.php
-
453rdparty/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumperInterface.php
-
1503rdparty/symfony/routing/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php
-
1763rdparty/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php
-
373rdparty/symfony/routing/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php
-
193rdparty/symfony/routing/Symfony/Component/Routing/LICENSE
-
2133rdparty/symfony/routing/Symfony/Component/Routing/Loader/AnnotationClassLoader.php
-
773rdparty/symfony/routing/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php
-
1253rdparty/symfony/routing/Symfony/Component/Routing/Loader/AnnotationFileLoader.php
-
543rdparty/symfony/routing/Symfony/Component/Routing/Loader/ClosureLoader.php
-
643rdparty/symfony/routing/Symfony/Component/Routing/Loader/PhpFileLoader.php
-
2243rdparty/symfony/routing/Symfony/Component/Routing/Loader/XmlFileLoader.php
-
1423rdparty/symfony/routing/Symfony/Component/Routing/Loader/YamlFileLoader.php
-
383rdparty/symfony/routing/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd
-
763rdparty/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php
-
1553rdparty/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php
-
443rdparty/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php
-
413rdparty/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php
-
2933rdparty/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
-
533rdparty/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php
-
353rdparty/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php
-
1513rdparty/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php
-
383rdparty/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php
-
323rdparty/symfony/routing/Symfony/Component/Routing/README.md
-
2503rdparty/symfony/routing/Symfony/Component/Routing/RequestContext.php
-
273rdparty/symfony/routing/Symfony/Component/Routing/RequestContextAwareInterface.php
-
3123rdparty/symfony/routing/Symfony/Component/Routing/Route.php
-
2593rdparty/symfony/routing/Symfony/Component/Routing/RouteCollection.php
-
1283rdparty/symfony/routing/Symfony/Component/Routing/RouteCompiler.php
-
293rdparty/symfony/routing/Symfony/Component/Routing/RouteCompilerInterface.php
-
2633rdparty/symfony/routing/Symfony/Component/Routing/Router.php
-
263rdparty/symfony/routing/Symfony/Component/Routing/RouterInterface.php
-
293rdparty/symfony/routing/Symfony/Component/Routing/composer.json
-
18composer.json
@ -0,0 +1,103 @@ |
|||
<?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\Annotation; |
|||
|
|||
/** |
|||
* Annotation class for @Route(). |
|||
* |
|||
* @Annotation |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Route |
|||
{ |
|||
private $pattern; |
|||
private $name; |
|||
private $requirements; |
|||
private $options; |
|||
private $defaults; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param array $data An array of key/value parameters. |
|||
*/ |
|||
public function __construct(array $data) |
|||
{ |
|||
$this->requirements = array(); |
|||
$this->options = array(); |
|||
$this->defaults = array(); |
|||
|
|||
if (isset($data['value'])) { |
|||
$data['pattern'] = $data['value']; |
|||
unset($data['value']); |
|||
} |
|||
|
|||
foreach ($data as $key => $value) { |
|||
$method = 'set'.$key; |
|||
if (!method_exists($this, $method)) { |
|||
throw new \BadMethodCallException(sprintf("Unknown property '%s' on annotation '%s'.", $key, get_class($this))); |
|||
} |
|||
$this->$method($value); |
|||
} |
|||
} |
|||
|
|||
public function setPattern($pattern) |
|||
{ |
|||
$this->pattern = $pattern; |
|||
} |
|||
|
|||
public function getPattern() |
|||
{ |
|||
return $this->pattern; |
|||
} |
|||
|
|||
public function setName($name) |
|||
{ |
|||
$this->name = $name; |
|||
} |
|||
|
|||
public function getName() |
|||
{ |
|||
return $this->name; |
|||
} |
|||
|
|||
public function setRequirements($requirements) |
|||
{ |
|||
$this->requirements = $requirements; |
|||
} |
|||
|
|||
public function getRequirements() |
|||
{ |
|||
return $this->requirements; |
|||
} |
|||
|
|||
public function setOptions($options) |
|||
{ |
|||
$this->options = $options; |
|||
} |
|||
|
|||
public function getOptions() |
|||
{ |
|||
return $this->options; |
|||
} |
|||
|
|||
public function setDefaults($defaults) |
|||
{ |
|||
$this->defaults = $defaults; |
|||
} |
|||
|
|||
public function getDefaults() |
|||
{ |
|||
return $this->defaults; |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* CompiledRoutes are returned by the RouteCompiler class. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class CompiledRoute |
|||
{ |
|||
private $route; |
|||
private $variables; |
|||
private $tokens; |
|||
private $staticPrefix; |
|||
private $regex; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param Route $route A original Route instance |
|||
* @param string $staticPrefix The static prefix of the compiled route |
|||
* @param string $regex The regular expression to use to match this route |
|||
* @param array $tokens An array of tokens to use to generate URL for this route |
|||
* @param array $variables An array of variables |
|||
*/ |
|||
public function __construct(Route $route, $staticPrefix, $regex, array $tokens, array $variables) |
|||
{ |
|||
$this->route = $route; |
|||
$this->staticPrefix = $staticPrefix; |
|||
$this->regex = $regex; |
|||
$this->tokens = $tokens; |
|||
$this->variables = $variables; |
|||
} |
|||
|
|||
/** |
|||
* Returns the Route instance. |
|||
* |
|||
* @return Route A Route instance |
|||
*/ |
|||
public function getRoute() |
|||
{ |
|||
return $this->route; |
|||
} |
|||
|
|||
/** |
|||
* Returns the static prefix. |
|||
* |
|||
* @return string The static prefix |
|||
*/ |
|||
public function getStaticPrefix() |
|||
{ |
|||
return $this->staticPrefix; |
|||
} |
|||
|
|||
/** |
|||
* Returns the regex. |
|||
* |
|||
* @return string The regex |
|||
*/ |
|||
public function getRegex() |
|||
{ |
|||
return $this->regex; |
|||
} |
|||
|
|||
/** |
|||
* Returns the tokens. |
|||
* |
|||
* @return array The tokens |
|||
*/ |
|||
public function getTokens() |
|||
{ |
|||
return $this->tokens; |
|||
} |
|||
|
|||
/** |
|||
* Returns the variables. |
|||
* |
|||
* @return array The variables |
|||
*/ |
|||
public function getVariables() |
|||
{ |
|||
return $this->variables; |
|||
} |
|||
|
|||
/** |
|||
* Returns the pattern. |
|||
* |
|||
* @return string The pattern |
|||
*/ |
|||
public function getPattern() |
|||
{ |
|||
return $this->route->getPattern(); |
|||
} |
|||
|
|||
/** |
|||
* Returns the options. |
|||
* |
|||
* @return array The options |
|||
*/ |
|||
public function getOptions() |
|||
{ |
|||
return $this->route->getOptions(); |
|||
} |
|||
|
|||
/** |
|||
* Returns the defaults. |
|||
* |
|||
* @return array The defaults |
|||
*/ |
|||
public function getDefaults() |
|||
{ |
|||
return $this->route->getDefaults(); |
|||
} |
|||
|
|||
/** |
|||
* Returns the requirements. |
|||
* |
|||
* @return array The requirements |
|||
*/ |
|||
public function getRequirements() |
|||
{ |
|||
return $this->route->getRequirements(); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<?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\Exception; |
|||
|
|||
/** |
|||
* ExceptionInterface |
|||
* |
|||
* @author Alexandre Salomé <alexandre.salome@gmail.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
interface ExceptionInterface |
|||
{ |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<?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\Exception; |
|||
|
|||
/** |
|||
* Exception thrown when a parameter is not valid |
|||
* |
|||
* @author Alexandre Salomé <alexandre.salome@gmail.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface |
|||
{ |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
<?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\Exception; |
|||
|
|||
/** |
|||
* The resource was found but the request method is not allowed. |
|||
* |
|||
* This exception should trigger an HTTP 405 response in your application code. |
|||
* |
|||
* @author Kris Wallsmith <kris@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface |
|||
{ |
|||
protected $allowedMethods; |
|||
|
|||
public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null) |
|||
{ |
|||
$this->allowedMethods = array_map('strtoupper', $allowedMethods); |
|||
|
|||
parent::__construct($message, $code, $previous); |
|||
} |
|||
|
|||
public function getAllowedMethods() |
|||
{ |
|||
return $this->allowedMethods; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<?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\Exception; |
|||
|
|||
/** |
|||
* Exception thrown when a route cannot be generated because of missing |
|||
* mandatory parameters. |
|||
* |
|||
* @author Alexandre Salomé <alexandre.salome@gmail.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface |
|||
{ |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
<?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\Exception; |
|||
|
|||
/** |
|||
* The resource was not found. |
|||
* |
|||
* This exception should trigger an HTTP 404 response in your application code. |
|||
* |
|||
* @author Kris Wallsmith <kris@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface |
|||
{ |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<?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\Exception; |
|||
|
|||
/** |
|||
* Exception thrown when a route does not exists |
|||
* |
|||
* @author Alexandre Salomé <alexandre.salome@gmail.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface |
|||
{ |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?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\Generator\Dumper; |
|||
|
|||
use Symfony\Component\Routing\RouteCollection; |
|||
|
|||
/** |
|||
* GeneratorDumper is the base class for all built-in generator dumpers. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
abstract class GeneratorDumper implements GeneratorDumperInterface |
|||
{ |
|||
private $routes; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param RouteCollection $routes The RouteCollection to dump |
|||
*/ |
|||
public function __construct(RouteCollection $routes) |
|||
{ |
|||
$this->routes = $routes; |
|||
} |
|||
|
|||
public function getRoutes() |
|||
{ |
|||
return $this->routes; |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
<?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\Generator\Dumper; |
|||
|
|||
use Symfony\Component\Routing\RouteCollection; |
|||
|
|||
/** |
|||
* GeneratorDumperInterface is the interface that all generator dumper classes must implement. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
interface GeneratorDumperInterface |
|||
{ |
|||
/** |
|||
* Dumps a set of routes to a PHP class. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * class: The class name |
|||
* * base_class: The base class name |
|||
* |
|||
* @param array $options An array of options |
|||
* |
|||
* @return string A PHP class representing the generator class |
|||
*/ |
|||
public function dump(array $options = array()); |
|||
|
|||
/** |
|||
* Gets the routes to dump. |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
*/ |
|||
public function getRoutes(); |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
<?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\Generator\Dumper; |
|||
|
|||
use Symfony\Component\Routing\Route; |
|||
|
|||
/** |
|||
* PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class PhpGeneratorDumper extends GeneratorDumper |
|||
{ |
|||
/** |
|||
* Dumps a set of routes to a PHP class. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * class: The class name |
|||
* * base_class: The base class name |
|||
* |
|||
* @param array $options An array of options |
|||
* |
|||
* @return string A PHP class representing the generator class |
|||
* |
|||
* @api |
|||
*/ |
|||
public function dump(array $options = array()) |
|||
{ |
|||
$options = array_merge(array( |
|||
'class' => 'ProjectUrlGenerator', |
|||
'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', |
|||
), $options); |
|||
|
|||
return |
|||
$this->startClass($options['class'], $options['base_class']). |
|||
$this->addConstructor(). |
|||
$this->addGenerator(). |
|||
$this->endClass() |
|||
; |
|||
} |
|||
|
|||
private function addGenerator() |
|||
{ |
|||
$methods = array(); |
|||
foreach ($this->getRoutes()->all() as $name => $route) { |
|||
$compiledRoute = $route->compile(); |
|||
|
|||
$variables = str_replace("\n", '', var_export($compiledRoute->getVariables(), true)); |
|||
$defaults = str_replace("\n", '', var_export($compiledRoute->getDefaults(), true)); |
|||
$requirements = str_replace("\n", '', var_export($compiledRoute->getRequirements(), true)); |
|||
$tokens = str_replace("\n", '', var_export($compiledRoute->getTokens(), true)); |
|||
|
|||
$escapedName = str_replace('.', '__', $name); |
|||
|
|||
$methods[] = <<<EOF |
|||
private function get{$escapedName}RouteInfo() |
|||
{ |
|||
return array($variables, $defaults, $requirements, $tokens); |
|||
} |
|||
|
|||
EOF |
|||
; |
|||
} |
|||
|
|||
$methods = implode("\n", $methods); |
|||
|
|||
return <<<EOF |
|||
|
|||
public function generate(\$name, \$parameters = array(), \$absolute = false) |
|||
{ |
|||
if (!isset(self::\$declaredRouteNames[\$name])) { |
|||
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', \$name)); |
|||
} |
|||
|
|||
\$escapedName = str_replace('.', '__', \$name); |
|||
|
|||
list(\$variables, \$defaults, \$requirements, \$tokens) = \$this->{'get'.\$escapedName.'RouteInfo'}(); |
|||
|
|||
return \$this->doGenerate(\$variables, \$defaults, \$requirements, \$tokens, \$parameters, \$name, \$absolute); |
|||
} |
|||
|
|||
$methods |
|||
EOF; |
|||
} |
|||
|
|||
private function startClass($class, $baseClass) |
|||
{ |
|||
$routes = array(); |
|||
foreach ($this->getRoutes()->all() as $name => $route) { |
|||
$routes[] = " '$name' => true,"; |
|||
} |
|||
$routes = implode("\n", $routes); |
|||
|
|||
return <<<EOF |
|||
<?php |
|||
|
|||
use Symfony\Component\Routing\RequestContext; |
|||
use Symfony\Component\Routing\Exception\RouteNotFoundException; |
|||
|
|||
|
|||
/** |
|||
* $class |
|||
* |
|||
* This class has been auto-generated |
|||
* by the Symfony Routing Component. |
|||
*/ |
|||
class $class extends $baseClass |
|||
{ |
|||
static private \$declaredRouteNames = array( |
|||
$routes |
|||
); |
|||
|
|||
|
|||
EOF; |
|||
} |
|||
|
|||
private function addConstructor() |
|||
{ |
|||
return <<<EOF |
|||
/** |
|||
* Constructor. |
|||
*/ |
|||
public function __construct(RequestContext \$context) |
|||
{ |
|||
\$this->context = \$context; |
|||
} |
|||
|
|||
EOF; |
|||
} |
|||
|
|||
private function endClass() |
|||
{ |
|||
return <<<EOF |
|||
} |
|||
|
|||
EOF; |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
<?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\Generator; |
|||
|
|||
use Symfony\Component\Routing\Route; |
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Routing\RequestContext; |
|||
use Symfony\Component\Routing\Exception\InvalidParameterException; |
|||
use Symfony\Component\Routing\Exception\RouteNotFoundException; |
|||
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; |
|||
|
|||
/** |
|||
* UrlGenerator generates URL based on a set of routes. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class UrlGenerator implements UrlGeneratorInterface |
|||
{ |
|||
protected $context; |
|||
protected $decodedChars = array( |
|||
// %2F is not valid in a URL, so we don't encode it (which is fine as the requirements explicitly allowed it)
|
|||
'%2F' => '/', |
|||
); |
|||
|
|||
protected $routes; |
|||
protected $cache; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param RouteCollection $routes A RouteCollection instance |
|||
* @param RequestContext $context The context |
|||
* |
|||
* @api |
|||
*/ |
|||
public function __construct(RouteCollection $routes, RequestContext $context) |
|||
{ |
|||
$this->routes = $routes; |
|||
$this->context = $context; |
|||
$this->cache = array(); |
|||
} |
|||
|
|||
/** |
|||
* Sets the request context. |
|||
* |
|||
* @param RequestContext $context The context |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setContext(RequestContext $context) |
|||
{ |
|||
$this->context = $context; |
|||
} |
|||
|
|||
/** |
|||
* Gets the request context. |
|||
* |
|||
* @return RequestContext The context |
|||
*/ |
|||
public function getContext() |
|||
{ |
|||
return $this->context; |
|||
} |
|||
|
|||
/** |
|||
* Generates a URL from the given parameters. |
|||
* |
|||
* @param string $name The name of the route |
|||
* @param mixed $parameters An array of parameters |
|||
* @param Boolean $absolute Whether to generate an absolute URL |
|||
* |
|||
* @return string The generated URL |
|||
* |
|||
* @throws Symfony\Component\Routing\Exception\RouteNotFoundException When route doesn't exist |
|||
* |
|||
* @api |
|||
*/ |
|||
public function generate($name, $parameters = array(), $absolute = false) |
|||
{ |
|||
if (null === $route = $this->routes->get($name)) { |
|||
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name)); |
|||
} |
|||
|
|||
if (!isset($this->cache[$name])) { |
|||
$this->cache[$name] = $route->compile(); |
|||
} |
|||
|
|||
return $this->doGenerate($this->cache[$name]->getVariables(), $route->getDefaults(), $route->getRequirements(), $this->cache[$name]->getTokens(), $parameters, $name, $absolute); |
|||
} |
|||
|
|||
/** |
|||
* @throws Symfony\Component\Routing\Exception\MissingMandatoryParametersException When route has some missing mandatory parameters |
|||
* @throws Symfony\Component\Routing\Exception\InvalidParameterException When a parameter value is not correct |
|||
*/ |
|||
protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $absolute) |
|||
{ |
|||
$variables = array_flip($variables); |
|||
|
|||
$originParameters = $parameters; |
|||
$parameters = array_replace($this->context->getParameters(), $parameters); |
|||
$tparams = array_replace($defaults, $parameters); |
|||
|
|||
// all params must be given
|
|||
if ($diff = array_diff_key($variables, $tparams)) { |
|||
throw new MissingMandatoryParametersException(sprintf('The "%s" route has some missing mandatory parameters ("%s").', $name, implode('", "', array_keys($diff)))); |
|||
} |
|||
|
|||
$url = ''; |
|||
$optional = true; |
|||
foreach ($tokens as $token) { |
|||
if ('variable' === $token[0]) { |
|||
if (false === $optional || !array_key_exists($token[3], $defaults) || (isset($parameters[$token[3]]) && (string) $parameters[$token[3]] != (string) $defaults[$token[3]])) { |
|||
if (!$isEmpty = in_array($tparams[$token[3]], array(null, '', false), true)) { |
|||
// check requirement
|
|||
if ($tparams[$token[3]] && !preg_match('#^'.$token[2].'$#', $tparams[$token[3]])) { |
|||
throw new InvalidParameterException(sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given).', $token[3], $name, $token[2], $tparams[$token[3]])); |
|||
} |
|||
} |
|||
|
|||
if (!$isEmpty || !$optional) { |
|||
$url = $token[1].strtr(rawurlencode($tparams[$token[3]]), $this->decodedChars).$url; |
|||
} |
|||
|
|||
$optional = false; |
|||
} |
|||
} elseif ('text' === $token[0]) { |
|||
$url = $token[1].$url; |
|||
$optional = false; |
|||
} |
|||
} |
|||
|
|||
if (!$url) { |
|||
$url = '/'; |
|||
} |
|||
|
|||
// add a query string if needed
|
|||
$extra = array_diff_key($originParameters, $variables, $defaults); |
|||
if ($extra && $query = http_build_query($extra, '', '&')) { |
|||
$url .= '?'.$query; |
|||
} |
|||
|
|||
$url = $this->context->getBaseUrl().$url; |
|||
|
|||
if ($this->context->getHost()) { |
|||
$scheme = $this->context->getScheme(); |
|||
if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme != $req) { |
|||
$absolute = true; |
|||
$scheme = $req; |
|||
} |
|||
|
|||
if ($absolute) { |
|||
$port = ''; |
|||
if ('http' === $scheme && 80 != $this->context->getHttpPort()) { |
|||
$port = ':'.$this->context->getHttpPort(); |
|||
} elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { |
|||
$port = ':'.$this->context->getHttpsPort(); |
|||
} |
|||
|
|||
$url = $scheme.'://'.$this->context->getHost().$port.$url; |
|||
} |
|||
} |
|||
|
|||
return $url; |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
<?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\Generator; |
|||
|
|||
use Symfony\Component\Routing\RequestContextAwareInterface; |
|||
|
|||
/** |
|||
* UrlGeneratorInterface is the interface that all URL generator classes must implements. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
interface UrlGeneratorInterface extends RequestContextAwareInterface |
|||
{ |
|||
/** |
|||
* Generates a URL from the given parameters. |
|||
* |
|||
* @param string $name The name of the route |
|||
* @param mixed $parameters An array of parameters |
|||
* @param Boolean $absolute Whether to generate an absolute URL |
|||
* |
|||
* @return string The generated URL |
|||
* |
|||
* @api |
|||
*/ |
|||
public function generate($name, $parameters = array(), $absolute = false); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2004-2012 Fabien Potencier |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is furnished |
|||
to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
|||
@ -0,0 +1,213 @@ |
|||
<?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\Loader; |
|||
|
|||
use Doctrine\Common\Annotations\Reader; |
|||
use Symfony\Component\Config\Resource\FileResource; |
|||
use Symfony\Component\Routing\Route; |
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Config\Loader\LoaderInterface; |
|||
use Symfony\Component\Config\Loader\LoaderResolver; |
|||
|
|||
/** |
|||
* AnnotationClassLoader loads routing information from a PHP class and its methods. |
|||
* |
|||
* You need to define an implementation for the getRouteDefaults() method. Most of the |
|||
* time, this method should define some PHP callable to be called for the route |
|||
* (a controller in MVC speak). |
|||
* |
|||
* The @Route annotation can be set on the class (for global parameters), |
|||
* and on each method. |
|||
* |
|||
* The @Route annotation main value is the route pattern. The annotation also |
|||
* recognizes three parameters: requirements, options, and name. The name parameter |
|||
* is mandatory. Here is an example of how you should be able to use it: |
|||
* |
|||
* /** |
|||
* * @Route("/Blog") |
|||
* * / |
|||
* class Blog |
|||
* { |
|||
* /** |
|||
* * @Route("/", name="blog_index") |
|||
* * / |
|||
* public function index() |
|||
* { |
|||
* } |
|||
* |
|||
* /** |
|||
* * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) |
|||
* * / |
|||
* public function show() |
|||
* { |
|||
* } |
|||
* } |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
abstract class AnnotationClassLoader implements LoaderInterface |
|||
{ |
|||
protected $reader; |
|||
protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; |
|||
protected $defaultRouteIndex; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param Reader $reader |
|||
*/ |
|||
public function __construct(Reader $reader) |
|||
{ |
|||
$this->reader = $reader; |
|||
} |
|||
|
|||
/** |
|||
* Sets the annotation class to read route properties from. |
|||
* |
|||
* @param string $class A fully-qualified class name |
|||
*/ |
|||
public function setRouteAnnotationClass($class) |
|||
{ |
|||
$this->routeAnnotationClass = $class; |
|||
} |
|||
|
|||
/** |
|||
* Loads from annotations from a class. |
|||
* |
|||
* @param string $class A class name |
|||
* @param string $type The resource type |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
* |
|||
* @throws \InvalidArgumentException When route can't be parsed |
|||
*/ |
|||
public function load($class, $type = null) |
|||
{ |
|||
if (!class_exists($class)) { |
|||
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); |
|||
} |
|||
|
|||
$globals = array( |
|||
'pattern' => '', |
|||
'requirements' => array(), |
|||
'options' => array(), |
|||
'defaults' => array(), |
|||
); |
|||
|
|||
$class = new \ReflectionClass($class); |
|||
if ($class->isAbstract()) { |
|||
throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class)); |
|||
} |
|||
|
|||
if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { |
|||
if (null !== $annot->getPattern()) { |
|||
$globals['pattern'] = $annot->getPattern(); |
|||
} |
|||
|
|||
if (null !== $annot->getRequirements()) { |
|||
$globals['requirements'] = $annot->getRequirements(); |
|||
} |
|||
|
|||
if (null !== $annot->getOptions()) { |
|||
$globals['options'] = $annot->getOptions(); |
|||
} |
|||
|
|||
if (null !== $annot->getDefaults()) { |
|||
$globals['defaults'] = $annot->getDefaults(); |
|||
} |
|||
} |
|||
|
|||
$collection = new RouteCollection(); |
|||
$collection->addResource(new FileResource($class->getFileName())); |
|||
|
|||
foreach ($class->getMethods() as $method) { |
|||
$this->defaultRouteIndex = 0; |
|||
foreach ($this->reader->getMethodAnnotations($method) as $annot) { |
|||
if ($annot instanceof $this->routeAnnotationClass) { |
|||
$this->addRoute($collection, $annot, $globals, $class, $method); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $collection; |
|||
} |
|||
|
|||
protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) |
|||
{ |
|||
$name = $annot->getName(); |
|||
if (null === $name) { |
|||
$name = $this->getDefaultRouteName($class, $method); |
|||
} |
|||
|
|||
$defaults = array_merge($globals['defaults'], $annot->getDefaults()); |
|||
$requirements = array_merge($globals['requirements'], $annot->getRequirements()); |
|||
$options = array_merge($globals['options'], $annot->getOptions()); |
|||
|
|||
$route = new Route($globals['pattern'].$annot->getPattern(), $defaults, $requirements, $options); |
|||
|
|||
$this->configureRoute($route, $class, $method, $annot); |
|||
|
|||
$collection->add($name, $route); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this class supports the given resource. |
|||
* |
|||
* @param mixed $resource A resource |
|||
* @param string $type The resource type |
|||
* |
|||
* @return Boolean True if this class supports the given resource, false otherwise |
|||
*/ |
|||
public function supports($resource, $type = null) |
|||
{ |
|||
return is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); |
|||
} |
|||
|
|||
/** |
|||
* Sets the loader resolver. |
|||
* |
|||
* @param LoaderResolver $resolver A LoaderResolver instance |
|||
*/ |
|||
public function setResolver(LoaderResolver $resolver) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* Gets the loader resolver. |
|||
* |
|||
* @return LoaderResolver A LoaderResolver instance |
|||
*/ |
|||
public function getResolver() |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* Gets the default route name for a class method. |
|||
* |
|||
* @param \ReflectionClass $class |
|||
* @param \ReflectionMethod $method |
|||
* |
|||
* @return string |
|||
*/ |
|||
protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) |
|||
{ |
|||
$name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name); |
|||
if ($this->defaultRouteIndex > 0) { |
|||
$name .= '_'.$this->defaultRouteIndex; |
|||
} |
|||
$this->defaultRouteIndex++; |
|||
|
|||
return $name; |
|||
} |
|||
|
|||
abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
<?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\Loader; |
|||
|
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Config\Resource\DirectoryResource; |
|||
|
|||
/** |
|||
* AnnotationDirectoryLoader loads routing information from annotations set |
|||
* on PHP classes and methods. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class AnnotationDirectoryLoader extends AnnotationFileLoader |
|||
{ |
|||
/** |
|||
* Loads from annotations from a directory. |
|||
* |
|||
* @param string $path A directory path |
|||
* @param string $type The resource type |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
* |
|||
* @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed |
|||
*/ |
|||
public function load($path, $type = null) |
|||
{ |
|||
$dir = $this->locator->locate($path); |
|||
|
|||
$collection = new RouteCollection(); |
|||
$collection->addResource(new DirectoryResource($dir, '/\.php$/')); |
|||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { |
|||
if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) { |
|||
continue; |
|||
} |
|||
|
|||
if ($class = $this->findClass($file)) { |
|||
$refl = new \ReflectionClass($class); |
|||
if ($refl->isAbstract()) { |
|||
continue; |
|||
} |
|||
|
|||
$collection->addCollection($this->loader->load($class, $type)); |
|||
} |
|||
} |
|||
|
|||
return $collection; |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this class supports the given resource. |
|||
* |
|||
* @param mixed $resource A resource |
|||
* @param string $type The resource type |
|||
* |
|||
* @return Boolean True if this class supports the given resource, false otherwise |
|||
*/ |
|||
public function supports($resource, $type = null) |
|||
{ |
|||
try { |
|||
$path = $this->locator->locate($resource); |
|||
} catch (\Exception $e) { |
|||
return false; |
|||
} |
|||
|
|||
return is_string($resource) && is_dir($path) && (!$type || 'annotation' === $type); |
|||
} |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
<?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\Loader; |
|||
|
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Config\Resource\FileResource; |
|||
use Symfony\Component\Config\Loader\FileLoader; |
|||
use Symfony\Component\Config\FileLocator; |
|||
|
|||
/** |
|||
* AnnotationFileLoader loads routing information from annotations set |
|||
* on a PHP class and its methods. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class AnnotationFileLoader extends FileLoader |
|||
{ |
|||
protected $loader; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param FileLocator $locator A FileLocator instance |
|||
* @param AnnotationClassLoader $loader An AnnotationClassLoader instance |
|||
* @param string|array $paths A path or an array of paths where to look for resources |
|||
*/ |
|||
public function __construct(FileLocator $locator, AnnotationClassLoader $loader, $paths = array()) |
|||
{ |
|||
if (!function_exists('token_get_all')) { |
|||
throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.'); |
|||
} |
|||
|
|||
parent::__construct($locator, $paths); |
|||
|
|||
$this->loader = $loader; |
|||
} |
|||
|
|||
/** |
|||
* Loads from annotations from a file. |
|||
* |
|||
* @param string $file A PHP file path |
|||
* @param string $type The resource type |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
* |
|||
* @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed |
|||
*/ |
|||
public function load($file, $type = null) |
|||
{ |
|||
$path = $this->locator->locate($file); |
|||
|
|||
$collection = new RouteCollection(); |
|||
if ($class = $this->findClass($path)) { |
|||
$collection->addResource(new FileResource($path)); |
|||
$collection->addCollection($this->loader->load($class, $type)); |
|||
} |
|||
|
|||
return $collection; |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this class supports the given resource. |
|||
* |
|||
* @param mixed $resource A resource |
|||
* @param string $type The resource type |
|||
* |
|||
* @return Boolean True if this class supports the given resource, false otherwise |
|||
*/ |
|||
public function supports($resource, $type = null) |
|||
{ |
|||
return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); |
|||
} |
|||
|
|||
/** |
|||
* Returns the full class name for the first class in the file. |
|||
* |
|||
* @param string $file A PHP file path |
|||
* |
|||
* @return string|false Full class name if found, false otherwise |
|||
*/ |
|||
protected function findClass($file) |
|||
{ |
|||
$class = false; |
|||
$namespace = false; |
|||
$tokens = token_get_all(file_get_contents($file)); |
|||
for ($i = 0, $count = count($tokens); $i < $count; $i++) { |
|||
$token = $tokens[$i]; |
|||
|
|||
if (!is_array($token)) { |
|||
continue; |
|||
} |
|||
|
|||
if (true === $class && T_STRING === $token[0]) { |
|||
return $namespace.'\\'.$token[1]; |
|||
} |
|||
|
|||
if (true === $namespace && T_STRING === $token[0]) { |
|||
$namespace = ''; |
|||
do { |
|||
$namespace .= $token[1]; |
|||
$token = $tokens[++$i]; |
|||
} while ($i < $count && is_array($token) && in_array($token[0], array(T_NS_SEPARATOR, T_STRING))); |
|||
} |
|||
|
|||
if (T_CLASS === $token[0]) { |
|||
$class = true; |
|||
} |
|||
|
|||
if (T_NAMESPACE === $token[0]) { |
|||
$namespace = true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
<?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\Loader; |
|||
|
|||
use Symfony\Component\Config\Loader\Loader; |
|||
|
|||
/** |
|||
* ClosureLoader loads routes from a PHP closure. |
|||
* |
|||
* The Closure must return a RouteCollection instance. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class ClosureLoader extends Loader |
|||
{ |
|||
/** |
|||
* Loads a Closure. |
|||
* |
|||
* @param \Closure $closure A Closure |
|||
* @param string $type The resource type |
|||
* |
|||
* @api |
|||
*/ |
|||
public function load($closure, $type = null) |
|||
{ |
|||
return call_user_func($closure); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this class supports the given resource. |
|||
* |
|||
* @param mixed $resource A resource |
|||
* @param string $type The resource type |
|||
* |
|||
* @return Boolean True if this class supports the given resource, false otherwise |
|||
* |
|||
* @api |
|||
*/ |
|||
public function supports($resource, $type = null) |
|||
{ |
|||
return $resource instanceof \Closure && (!$type || 'closure' === $type); |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
<?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\Loader; |
|||
|
|||
use Symfony\Component\Config\Resource\FileResource; |
|||
use Symfony\Component\Config\Loader\FileLoader; |
|||
|
|||
/** |
|||
* PhpFileLoader loads routes from a PHP file. |
|||
* |
|||
* The file must return a RouteCollection instance. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class PhpFileLoader extends FileLoader |
|||
{ |
|||
/** |
|||
* Loads a PHP file. |
|||
* |
|||
* @param mixed $file A PHP file path |
|||
* @param string $type The resource type |
|||
* |
|||
* @api |
|||
*/ |
|||
public function load($file, $type = null) |
|||
{ |
|||
// the loader variable is exposed to the included file below
|
|||
$loader = $this; |
|||
|
|||
$path = $this->locator->locate($file); |
|||
$this->setCurrentDir(dirname($path)); |
|||
|
|||
$collection = include $path; |
|||
$collection->addResource(new FileResource($path)); |
|||
|
|||
return $collection; |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this class supports the given resource. |
|||
* |
|||
* @param mixed $resource A resource |
|||
* @param string $type The resource type |
|||
* |
|||
* @return Boolean True if this class supports the given resource, false otherwise |
|||
* |
|||
* @api |
|||
*/ |
|||
public function supports($resource, $type = null) |
|||
{ |
|||
return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type); |
|||
} |
|||
} |
|||
@ -0,0 +1,224 @@ |
|||
<?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\Loader; |
|||
|
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Routing\Route; |
|||
use Symfony\Component\Config\Resource\FileResource; |
|||
use Symfony\Component\Config\Loader\FileLoader; |
|||
|
|||
/** |
|||
* XmlFileLoader loads XML routing files. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class XmlFileLoader extends FileLoader |
|||
{ |
|||
/** |
|||
* Loads an XML file. |
|||
* |
|||
* @param string $file An XML file path |
|||
* @param string $type The resource type |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
* |
|||
* @throws \InvalidArgumentException When a tag can't be parsed |
|||
* |
|||
* @api |
|||
*/ |
|||
public function load($file, $type = null) |
|||
{ |
|||
$path = $this->locator->locate($file); |
|||
|
|||
$xml = $this->loadFile($path); |
|||
|
|||
$collection = new RouteCollection(); |
|||
$collection->addResource(new FileResource($path)); |
|||
|
|||
// process routes and imports
|
|||
foreach ($xml->documentElement->childNodes as $node) { |
|||
if (!$node instanceof \DOMElement) { |
|||
continue; |
|||
} |
|||
|
|||
$this->parseNode($collection, $node, $path, $file); |
|||
} |
|||
|
|||
return $collection; |
|||
} |
|||
|
|||
/** |
|||
* Parses a node from a loaded XML file. |
|||
* |
|||
* @param RouteCollection $collection the collection to associate with the node |
|||
* @param DOMElement $node the node to parse |
|||
* @param string $path the path of the XML file being processed |
|||
* @param string $file |
|||
*/ |
|||
protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) |
|||
{ |
|||
switch ($node->tagName) { |
|||
case 'route': |
|||
$this->parseRoute($collection, $node, $path); |
|||
break; |
|||
case 'import': |
|||
$resource = (string) $node->getAttribute('resource'); |
|||
$type = (string) $node->getAttribute('type'); |
|||
$prefix = (string) $node->getAttribute('prefix'); |
|||
$this->setCurrentDir(dirname($path)); |
|||
$collection->addCollection($this->import($resource, ('' !== $type ? $type : null), false, $file), $prefix); |
|||
break; |
|||
default: |
|||
throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName)); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this class supports the given resource. |
|||
* |
|||
* @param mixed $resource A resource |
|||
* @param string $type The resource type |
|||
* |
|||
* @return Boolean True if this class supports the given resource, false otherwise |
|||
* |
|||
* @api |
|||
*/ |
|||
public function supports($resource, $type = null) |
|||
{ |
|||
return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type); |
|||
} |
|||
|
|||
/** |
|||
* Parses a route and adds it to the RouteCollection. |
|||
* |
|||
* @param RouteCollection $collection A RouteCollection instance |
|||
* @param \DOMElement $definition Route definition |
|||
* @param string $file An XML file path |
|||
* |
|||
* @throws \InvalidArgumentException When the definition cannot be parsed |
|||
*/ |
|||
protected function parseRoute(RouteCollection $collection, \DOMElement $definition, $file) |
|||
{ |
|||
$defaults = array(); |
|||
$requirements = array(); |
|||
$options = array(); |
|||
|
|||
foreach ($definition->childNodes as $node) { |
|||
if (!$node instanceof \DOMElement) { |
|||
continue; |
|||
} |
|||
|
|||
switch ($node->tagName) { |
|||
case 'default': |
|||
$defaults[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue); |
|||
break; |
|||
case 'option': |
|||
$options[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue); |
|||
break; |
|||
case 'requirement': |
|||
$requirements[(string) $node->getAttribute('key')] = trim((string) $node->nodeValue); |
|||
break; |
|||
default: |
|||
throw new \InvalidArgumentException(sprintf('Unable to parse tag "%s"', $node->tagName)); |
|||
} |
|||
} |
|||
|
|||
$route = new Route((string) $definition->getAttribute('pattern'), $defaults, $requirements, $options); |
|||
|
|||
$collection->add((string) $definition->getAttribute('id'), $route); |
|||
} |
|||
|
|||
/** |
|||
* Loads an XML file. |
|||
* |
|||
* @param string $file An XML file path |
|||
* |
|||
* @return \DOMDocument |
|||
* |
|||
* @throws \InvalidArgumentException When loading of XML file returns error |
|||
*/ |
|||
protected function loadFile($file) |
|||
{ |
|||
$internalErrors = libxml_use_internal_errors(true); |
|||
$disableEntities = libxml_disable_entity_loader(true); |
|||
libxml_clear_errors(); |
|||
|
|||
$dom = new \DOMDocument(); |
|||
$dom->validateOnParse = true; |
|||
if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { |
|||
libxml_disable_entity_loader($disableEntities); |
|||
|
|||
throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($internalErrors))); |
|||
} |
|||
$dom->normalizeDocument(); |
|||
|
|||
libxml_use_internal_errors($internalErrors); |
|||
libxml_disable_entity_loader($disableEntities); |
|||
|
|||
foreach ($dom->childNodes as $child) { |
|||
if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { |
|||
throw new \InvalidArgumentException('Document types are not allowed.'); |
|||
} |
|||
} |
|||
|
|||
$this->validate($dom); |
|||
|
|||
return $dom; |
|||
} |
|||
|
|||
/** |
|||
* Validates a loaded XML file. |
|||
* |
|||
* @param \DOMDocument $dom A loaded XML file |
|||
* |
|||
* @throws \InvalidArgumentException When XML doesn't validate its XSD schema |
|||
*/ |
|||
protected function validate(\DOMDocument $dom) |
|||
{ |
|||
$location = __DIR__.'/schema/routing/routing-1.0.xsd'; |
|||
|
|||
$current = libxml_use_internal_errors(true); |
|||
libxml_clear_errors(); |
|||
|
|||
if (!$dom->schemaValidate($location)) { |
|||
throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($current))); |
|||
} |
|||
libxml_use_internal_errors($current); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves libxml errors and clears them. |
|||
* |
|||
* @return array An array of libxml error strings |
|||
*/ |
|||
private function getXmlErrors($internalErrors) |
|||
{ |
|||
$errors = array(); |
|||
foreach (libxml_get_errors() as $error) { |
|||
$errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', |
|||
LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', |
|||
$error->code, |
|||
trim($error->message), |
|||
$error->file ? $error->file : 'n/a', |
|||
$error->line, |
|||
$error->column |
|||
); |
|||
} |
|||
|
|||
libxml_clear_errors(); |
|||
libxml_use_internal_errors($internalErrors); |
|||
|
|||
return $errors; |
|||
} |
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
<?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\Loader; |
|||
|
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Routing\Route; |
|||
use Symfony\Component\Config\Resource\FileResource; |
|||
use Symfony\Component\Yaml\Yaml; |
|||
use Symfony\Component\Config\Loader\FileLoader; |
|||
|
|||
/** |
|||
* YamlFileLoader loads Yaml routing files. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class YamlFileLoader extends FileLoader |
|||
{ |
|||
private static $availableKeys = array( |
|||
'type', 'resource', 'prefix', 'pattern', 'options', 'defaults', 'requirements' |
|||
); |
|||
|
|||
/** |
|||
* Loads a Yaml file. |
|||
* |
|||
* @param string $file A Yaml file path |
|||
* @param string $type The resource type |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
* |
|||
* @throws \InvalidArgumentException When route can't be parsed |
|||
* |
|||
* @api |
|||
*/ |
|||
public function load($file, $type = null) |
|||
{ |
|||
$path = $this->locator->locate($file); |
|||
|
|||
$config = Yaml::parse($path); |
|||
|
|||
$collection = new RouteCollection(); |
|||
$collection->addResource(new FileResource($path)); |
|||
|
|||
// empty file
|
|||
if (null === $config) { |
|||
$config = array(); |
|||
} |
|||
|
|||
// not an array
|
|||
if (!is_array($config)) { |
|||
throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $file)); |
|||
} |
|||
|
|||
foreach ($config as $name => $config) { |
|||
$config = $this->normalizeRouteConfig($config); |
|||
|
|||
if (isset($config['resource'])) { |
|||
$type = isset($config['type']) ? $config['type'] : null; |
|||
$prefix = isset($config['prefix']) ? $config['prefix'] : null; |
|||
$this->setCurrentDir(dirname($path)); |
|||
$collection->addCollection($this->import($config['resource'], $type, false, $file), $prefix); |
|||
} else { |
|||
$this->parseRoute($collection, $name, $config, $path); |
|||
} |
|||
} |
|||
|
|||
return $collection; |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this class supports the given resource. |
|||
* |
|||
* @param mixed $resource A resource |
|||
* @param string $type The resource type |
|||
* |
|||
* @return Boolean True if this class supports the given resource, false otherwise |
|||
* |
|||
* @api |
|||
*/ |
|||
public function supports($resource, $type = null) |
|||
{ |
|||
return is_string($resource) && 'yml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'yaml' === $type); |
|||
} |
|||
|
|||
/** |
|||
* Parses a route and adds it to the RouteCollection. |
|||
* |
|||
* @param RouteCollection $collection A RouteCollection instance |
|||
* @param string $name Route name |
|||
* @param array $config Route definition |
|||
* @param string $file A Yaml file path |
|||
* |
|||
* @throws \InvalidArgumentException When config pattern is not defined for the given route |
|||
*/ |
|||
protected function parseRoute(RouteCollection $collection, $name, $config, $file) |
|||
{ |
|||
$defaults = isset($config['defaults']) ? $config['defaults'] : array(); |
|||
$requirements = isset($config['requirements']) ? $config['requirements'] : array(); |
|||
$options = isset($config['options']) ? $config['options'] : array(); |
|||
|
|||
if (!isset($config['pattern'])) { |
|||
throw new \InvalidArgumentException(sprintf('You must define a "pattern" for the "%s" route.', $name)); |
|||
} |
|||
|
|||
$route = new Route($config['pattern'], $defaults, $requirements, $options); |
|||
|
|||
$collection->add($name, $route); |
|||
} |
|||
|
|||
/** |
|||
* Normalize route configuration. |
|||
* |
|||
* @param array $config A resource config |
|||
* |
|||
* @return array |
|||
* |
|||
* @throws InvalidArgumentException if one of the provided config keys is not supported |
|||
*/ |
|||
private function normalizeRouteConfig(array $config) |
|||
{ |
|||
foreach ($config as $key => $value) { |
|||
if (!in_array($key, self::$availableKeys)) { |
|||
throw new \InvalidArgumentException(sprintf( |
|||
'Yaml routing loader does not support given key: "%s". Expected one of the (%s).', |
|||
$key, implode(', ', self::$availableKeys) |
|||
)); |
|||
} |
|||
} |
|||
|
|||
return $config; |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
|
|||
<xsd:schema xmlns="http://symfony.com/schema/routing" |
|||
xmlns:xsd="http://www.w3.org/2001/XMLSchema" |
|||
targetNamespace="http://symfony.com/schema/routing" |
|||
elementFormDefault="qualified"> |
|||
|
|||
<xsd:element name="routes" type="routes" /> |
|||
|
|||
<xsd:complexType name="routes"> |
|||
<xsd:choice maxOccurs="unbounded" minOccurs="0"> |
|||
<xsd:element name="import" type="import" /> |
|||
<xsd:element name="route" type="route" /> |
|||
</xsd:choice> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="route"> |
|||
<xsd:sequence> |
|||
<xsd:element name="default" type="element" minOccurs="0" maxOccurs="unbounded" /> |
|||
<xsd:element name="requirement" type="element" minOccurs="0" maxOccurs="unbounded" /> |
|||
<xsd:element name="option" type="element" minOccurs="0" maxOccurs="unbounded" /> |
|||
</xsd:sequence> |
|||
|
|||
<xsd:attribute name="id" type="xsd:string" /> |
|||
<xsd:attribute name="pattern" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="import"> |
|||
<xsd:attribute name="resource" type="xsd:string" /> |
|||
<xsd:attribute name="type" type="xsd:string" /> |
|||
<xsd:attribute name="prefix" type="xsd:string" /> |
|||
<xsd:attribute name="class" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="element" mixed="true"> |
|||
<xsd:attribute name="key" type="xsd:string" /> |
|||
</xsd:complexType> |
|||
</xsd:schema> |
|||
@ -0,0 +1,76 @@ |
|||
<?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; |
|||
|
|||
use Symfony\Component\Routing\Exception\MethodNotAllowedException; |
|||
|
|||
/** |
|||
* ApacheUrlMatcher matches URL based on Apache mod_rewrite matching (see ApacheMatcherDumper). |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class ApacheUrlMatcher extends UrlMatcher |
|||
{ |
|||
/** |
|||
* Tries to match a URL based on Apache mod_rewrite matching. |
|||
* |
|||
* Returns false if no route matches the URL. |
|||
* |
|||
* @param string $pathinfo The pathinfo to be parsed |
|||
* |
|||
* @return array An array of parameters |
|||
* |
|||
* @throws MethodNotAllowedException If the current method is not allowed |
|||
*/ |
|||
public function match($pathinfo) |
|||
{ |
|||
$parameters = array(); |
|||
$defaults = array(); |
|||
$allow = array(); |
|||
$match = false; |
|||
|
|||
foreach ($_SERVER as $key => $value) { |
|||
$name = $key; |
|||
|
|||
if (0 === strpos($name, 'REDIRECT_')) { |
|||
$name = substr($name, 9); |
|||
} |
|||
|
|||
if (0 === strpos($name, '_ROUTING_DEFAULTS_')) { |
|||
$name = substr($name, 18); |
|||
$defaults[$name] = $value; |
|||
} elseif (0 === strpos($name, '_ROUTING_')) { |
|||
$name = substr($name, 9); |
|||
if ('_route' == $name) { |
|||
$match = true; |
|||
$parameters[$name] = $value; |
|||
} elseif (0 === strpos($name, '_allow_')) { |
|||
$allow[] = substr($name, 7); |
|||
} else { |
|||
$parameters[$name] = $value; |
|||
} |
|||
} else { |
|||
continue; |
|||
} |
|||
|
|||
unset($_SERVER[$key]); |
|||
} |
|||
|
|||
if ($match) { |
|||
return $this->mergeDefaults($parameters, $defaults); |
|||
} elseif (0 < count($allow)) { |
|||
throw new MethodNotAllowedException($allow); |
|||
} else { |
|||
return parent::match($pathinfo); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* 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(); |
|||
|
|||
foreach ($this->getRoutes()->all() as $name => $route) { |
|||
$compiledRoute = $route->compile(); |
|||
|
|||
// prepare the apache regex
|
|||
$regex = $compiledRoute->getRegex(); |
|||
$delimiter = $regex[0]; |
|||
$regexPatternEnd = strrpos($regex, $delimiter); |
|||
if (strlen($regex) < 2 || 0 === $regexPatternEnd) { |
|||
throw new \LogicException('The "%s" route regex "%s" is invalid', $name, $regex); |
|||
} |
|||
$regex = preg_replace('/\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1)); |
|||
$regex = '^'.self::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\'); |
|||
|
|||
$hasTrailingSlash = '/$' == substr($regex, -2) && '^/$' != $regex; |
|||
|
|||
$variables = array('E=_ROUTING__route:'.$name); |
|||
foreach ($compiledRoute->getVariables() as $i => $variable) { |
|||
$variables[] = 'E=_ROUTING_'.$variable.':%'.($i + 1); |
|||
} |
|||
foreach ($route->getDefaults() as $key => $value) { |
|||
$variables[] = 'E=_ROUTING_DEFAULTS_'.$key.':'.strtr($value, array( |
|||
':' => '\\:', |
|||
'=' => '\\=', |
|||
'\\' => '\\\\', |
|||
' ' => '\\ ', |
|||
)); |
|||
} |
|||
$variables = implode(',', $variables); |
|||
|
|||
$rule = array("# $name"); |
|||
|
|||
// method mismatch
|
|||
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'; |
|||
} |
|||
$allow = array(); |
|||
foreach ($methods as $method) { |
|||
$methodVars[] = $method; |
|||
$allow[] = 'E=_ROUTING__allow_'.$method.':1'; |
|||
} |
|||
|
|||
$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) { |
|||
$rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$'; |
|||
$rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]'; |
|||
} |
|||
|
|||
// the main rule
|
|||
$rule[] = "RewriteCond %{REQUEST_URI} $regex"; |
|||
$rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]"; |
|||
|
|||
$rules[] = implode("\n", $rule); |
|||
} |
|||
|
|||
if (0 < count($methodVars)) { |
|||
$rule = array('# 405 Method Not Allowed'); |
|||
$methodVars = array_values(array_unique($methodVars)); |
|||
foreach ($methodVars as $i => $methodVar) { |
|||
$rule[] = sprintf('RewriteCond %%{_ROUTING__allow_%s} !-z%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"; |
|||
} |
|||
|
|||
/** |
|||
* 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; |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
<?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\RouteCollection; |
|||
|
|||
/** |
|||
* MatcherDumper is the abstract class for all built-in matcher dumpers. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
abstract class MatcherDumper implements MatcherDumperInterface |
|||
{ |
|||
private $routes; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param RouteCollection $routes The RouteCollection to dump |
|||
*/ |
|||
public function __construct(RouteCollection $routes) |
|||
{ |
|||
$this->routes = $routes; |
|||
} |
|||
|
|||
/** |
|||
* Gets the routes to dump. |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
*/ |
|||
public function getRoutes() |
|||
{ |
|||
return $this->routes; |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* MatcherDumperInterface is the interface that all matcher dumper classes must implement. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface MatcherDumperInterface |
|||
{ |
|||
/** |
|||
* Dumps a set of routes to a PHP class. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * class: The class name |
|||
* * base_class: The base class name |
|||
* |
|||
* @param array $options An array of options |
|||
* |
|||
* @return string A PHP class representing the matcher class |
|||
*/ |
|||
public function dump(array $options = array()); |
|||
|
|||
/** |
|||
* Gets the routes to match. |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
*/ |
|||
public function getRoutes(); |
|||
} |
|||
@ -0,0 +1,293 @@ |
|||
<?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; |
|||
use Symfony\Component\Routing\RouteCollection; |
|||
|
|||
/** |
|||
* PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class PhpMatcherDumper extends MatcherDumper |
|||
{ |
|||
/** |
|||
* Dumps a set of routes to a PHP class. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * class: The class name |
|||
* * base_class: The base class name |
|||
* |
|||
* @param array $options An array of options |
|||
* |
|||
* @return string A PHP class representing the matcher class |
|||
*/ |
|||
public function dump(array $options = array()) |
|||
{ |
|||
$options = array_merge(array( |
|||
'class' => 'ProjectUrlMatcher', |
|||
'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', |
|||
), $options); |
|||
|
|||
// trailing slash support is only enabled if we know how to redirect the user
|
|||
$interfaces = class_implements($options['base_class']); |
|||
$supportsRedirections = isset($interfaces['Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface']); |
|||
|
|||
return |
|||
$this->startClass($options['class'], $options['base_class']). |
|||
$this->addConstructor(). |
|||
$this->addMatcher($supportsRedirections). |
|||
$this->endClass() |
|||
; |
|||
} |
|||
|
|||
private function addMatcher($supportsRedirections) |
|||
{ |
|||
// we need to deep clone the routes as we will modify the structure to optimize the dump
|
|||
$code = implode("\n", $this->compileRoutes(clone $this->getRoutes(), $supportsRedirections)); |
|||
|
|||
return <<<EOF |
|||
|
|||
public function match(\$pathinfo) |
|||
{ |
|||
\$allow = array(); |
|||
\$pathinfo = urldecode(\$pathinfo); |
|||
|
|||
$code |
|||
throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); |
|||
} |
|||
|
|||
EOF; |
|||
} |
|||
|
|||
private function compileRoutes(RouteCollection $routes, $supportsRedirections, $parentPrefix = null) |
|||
{ |
|||
$code = array(); |
|||
|
|||
$routeIterator = $routes->getIterator(); |
|||
$keys = array_keys($routeIterator->getArrayCopy()); |
|||
$keysCount = count($keys); |
|||
|
|||
$i = 0; |
|||
foreach ($routeIterator as $name => $route) { |
|||
$i++; |
|||
|
|||
if ($route instanceof RouteCollection) { |
|||
$prefix = $route->getPrefix(); |
|||
$optimizable = $prefix && count($route->all()) > 1 && false === strpos($route->getPrefix(), '{'); |
|||
$indent = ''; |
|||
if ($optimizable) { |
|||
for ($j = $i; $j < $keysCount; $j++) { |
|||
if ($keys[$j] === null) { |
|||
continue; |
|||
} |
|||
|
|||
$testRoute = $routeIterator->offsetGet($keys[$j]); |
|||
$isCollection = ($testRoute instanceof RouteCollection); |
|||
|
|||
$testPrefix = $isCollection ? $testRoute->getPrefix() : $testRoute->getPattern(); |
|||
|
|||
if (0 === strpos($testPrefix, $prefix)) { |
|||
$routeIterator->offsetUnset($keys[$j]); |
|||
|
|||
if ($isCollection) { |
|||
$route->addCollection($testRoute); |
|||
} else { |
|||
$route->add($keys[$j], $testRoute); |
|||
} |
|||
|
|||
$i++; |
|||
$keys[$j] = null; |
|||
} |
|||
} |
|||
|
|||
if ($prefix !== $parentPrefix) { |
|||
$code[] = sprintf(" if (0 === strpos(\$pathinfo, %s)) {", var_export($prefix, true)); |
|||
$indent = ' '; |
|||
} |
|||
} |
|||
|
|||
foreach ($this->compileRoutes($route, $supportsRedirections, $prefix) as $line) { |
|||
foreach (explode("\n", $line) as $l) { |
|||
if ($l) { |
|||
$code[] = $indent.$l; |
|||
} else { |
|||
$code[] = $l; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if ($optimizable && $prefix !== $parentPrefix) { |
|||
$code[] = " }\n"; |
|||
} |
|||
} else { |
|||
foreach ($this->compileRoute($route, $name, $supportsRedirections, $parentPrefix) as $line) { |
|||
$code[] = $line; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $code; |
|||
} |
|||
|
|||
private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) |
|||
{ |
|||
$code = array(); |
|||
$compiledRoute = $route->compile(); |
|||
$conditions = array(); |
|||
$hasTrailingSlash = false; |
|||
$matches = false; |
|||
if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) { |
|||
if ($supportsRedirections && substr($m['url'], -1) === '/') { |
|||
$conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); |
|||
$hasTrailingSlash = true; |
|||
} else { |
|||
$conditions[] = sprintf("\$pathinfo === %s", var_export(str_replace('\\', '', $m['url']), true)); |
|||
} |
|||
} else { |
|||
if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() != $parentPrefix) { |
|||
$conditions[] = sprintf("0 === strpos(\$pathinfo, %s)", var_export($compiledRoute->getStaticPrefix(), true)); |
|||
} |
|||
|
|||
$regex = $compiledRoute->getRegex(); |
|||
if ($supportsRedirections && $pos = strpos($regex, '/$')) { |
|||
$regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); |
|||
$hasTrailingSlash = true; |
|||
} |
|||
$conditions[] = sprintf("preg_match(%s, \$pathinfo, \$matches)", var_export($regex, true)); |
|||
|
|||
$matches = true; |
|||
} |
|||
|
|||
$conditions = implode(' && ', $conditions); |
|||
|
|||
$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name); |
|||
|
|||
$code[] = <<<EOF |
|||
// $name
|
|||
if ($conditions) { |
|||
EOF; |
|||
|
|||
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'; |
|||
} |
|||
if (1 === count($methods)) { |
|||
$code[] = <<<EOF |
|||
if (\$this->context->getMethod() != '$methods[0]') { |
|||
\$allow[] = '$methods[0]'; |
|||
goto $gotoname; |
|||
} |
|||
EOF; |
|||
} else { |
|||
$methods = implode('\', \'', $methods); |
|||
$code[] = <<<EOF |
|||
if (!in_array(\$this->context->getMethod(), array('$methods'))) { |
|||
\$allow = array_merge(\$allow, array('$methods')); |
|||
goto $gotoname; |
|||
} |
|||
EOF; |
|||
} |
|||
} |
|||
|
|||
if ($hasTrailingSlash) { |
|||
$code[] = sprintf(<<<EOF |
|||
if (substr(\$pathinfo, -1) !== '/') { |
|||
return \$this->redirect(\$pathinfo.'/', '%s'); |
|||
} |
|||
EOF |
|||
, $name); |
|||
} |
|||
|
|||
if ($scheme = $route->getRequirement('_scheme')) { |
|||
if (!$supportsRedirections) { |
|||
throw new \LogicException('The "_scheme" requirement is only supported for route dumper that implements RedirectableUrlMatcherInterface.'); |
|||
} |
|||
|
|||
$code[] = sprintf(<<<EOF |
|||
if (\$this->context->getScheme() !== '$scheme') { |
|||
return \$this->redirect(\$pathinfo, '%s', '$scheme'); |
|||
} |
|||
EOF |
|||
, $name); |
|||
} |
|||
|
|||
// optimize parameters array
|
|||
if (true === $matches && $compiledRoute->getDefaults()) { |
|||
$code[] = sprintf(" return array_merge(\$this->mergeDefaults(\$matches, %s), array('_route' => '%s'));" |
|||
, str_replace("\n", '', var_export($compiledRoute->getDefaults(), true)), $name); |
|||
} elseif (true === $matches) { |
|||
$code[] = sprintf(" \$matches['_route'] = '%s';", $name); |
|||
$code[] = sprintf(" return \$matches;", $name); |
|||
} elseif ($compiledRoute->getDefaults()) { |
|||
$code[] = sprintf(' return %s;', str_replace("\n", '', var_export(array_merge($compiledRoute->getDefaults(), array('_route' => $name)), true))); |
|||
} else { |
|||
$code[] = sprintf(" return array('_route' => '%s');", $name); |
|||
} |
|||
$code[] = " }"; |
|||
|
|||
if ($req) { |
|||
$code[] = " $gotoname:"; |
|||
} |
|||
|
|||
$code[] = ''; |
|||
|
|||
return $code; |
|||
} |
|||
|
|||
private function startClass($class, $baseClass) |
|||
{ |
|||
return <<<EOF |
|||
<?php |
|||
|
|||
use Symfony\Component\Routing\Exception\MethodNotAllowedException; |
|||
use Symfony\Component\Routing\Exception\ResourceNotFoundException; |
|||
use Symfony\Component\Routing\RequestContext; |
|||
|
|||
/** |
|||
* $class |
|||
* |
|||
* This class has been auto-generated |
|||
* by the Symfony Routing Component. |
|||
*/ |
|||
class $class extends $baseClass |
|||
{ |
|||
|
|||
EOF; |
|||
} |
|||
|
|||
private function addConstructor() |
|||
{ |
|||
return <<<EOF |
|||
/** |
|||
* Constructor. |
|||
*/ |
|||
public function __construct(RequestContext \$context) |
|||
{ |
|||
\$this->context = \$context; |
|||
} |
|||
|
|||
EOF; |
|||
} |
|||
|
|||
private function endClass() |
|||
{ |
|||
return <<<EOF |
|||
} |
|||
|
|||
EOF; |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
<?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; |
|||
|
|||
use Symfony\Component\Routing\Exception\ResourceNotFoundException; |
|||
|
|||
/** |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface |
|||
{ |
|||
private $trailingSlashTest = false; |
|||
|
|||
/** |
|||
* @see UrlMatcher::match() |
|||
* |
|||
* @api |
|||
*/ |
|||
public function match($pathinfo) |
|||
{ |
|||
try { |
|||
$parameters = parent::match($pathinfo); |
|||
} catch (ResourceNotFoundException $e) { |
|||
if ('/' === substr($pathinfo, -1)) { |
|||
throw $e; |
|||
} |
|||
|
|||
// try with a / at the end
|
|||
$this->trailingSlashTest = true; |
|||
|
|||
return $this->match($pathinfo.'/'); |
|||
} |
|||
|
|||
if ($this->trailingSlashTest) { |
|||
$this->trailingSlashTest = false; |
|||
|
|||
return $this->redirect($pathinfo, null); |
|||
} |
|||
|
|||
return $parameters; |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* RedirectableUrlMatcherInterface knows how to redirect the user. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
interface RedirectableUrlMatcherInterface |
|||
{ |
|||
/** |
|||
* Redirects the user to another URL. |
|||
* |
|||
* @param string $path The path info to redirect to. |
|||
* @param string $route The route that matched |
|||
* @param string $scheme The URL scheme (null to keep the current one) |
|||
* |
|||
* @return array An array of parameters |
|||
* |
|||
* @api |
|||
*/ |
|||
public function redirect($path, $route, $scheme = null); |
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
<?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; |
|||
|
|||
use Symfony\Component\Routing\Exception\MethodNotAllowedException; |
|||
use Symfony\Component\Routing\Exception\ResourceNotFoundException; |
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Routing\RequestContext; |
|||
|
|||
/** |
|||
* UrlMatcher matches URL based on a set of routes. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class UrlMatcher implements UrlMatcherInterface |
|||
{ |
|||
protected $context; |
|||
protected $allow; |
|||
|
|||
private $routes; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param RouteCollection $routes A RouteCollection instance |
|||
* @param RequestContext $context The context |
|||
* |
|||
* @api |
|||
*/ |
|||
public function __construct(RouteCollection $routes, RequestContext $context) |
|||
{ |
|||
$this->routes = $routes; |
|||
$this->context = $context; |
|||
} |
|||
|
|||
/** |
|||
* Sets the request context. |
|||
* |
|||
* @param RequestContext $context The context |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setContext(RequestContext $context) |
|||
{ |
|||
$this->context = $context; |
|||
} |
|||
|
|||
/** |
|||
* Gets the request context. |
|||
* |
|||
* @return RequestContext The context |
|||
*/ |
|||
public function getContext() |
|||
{ |
|||
return $this->context; |
|||
} |
|||
|
|||
/** |
|||
* Tries to match a URL with a set of routes. |
|||
* |
|||
* @param string $pathinfo The path info to be parsed |
|||
* |
|||
* @return array An array of parameters |
|||
* |
|||
* @throws ResourceNotFoundException If the resource could not be found |
|||
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed |
|||
* |
|||
* @api |
|||
*/ |
|||
public function match($pathinfo) |
|||
{ |
|||
$this->allow = array(); |
|||
|
|||
if ($ret = $this->matchCollection($pathinfo, $this->routes)) { |
|||
return $ret; |
|||
} |
|||
|
|||
throw 0 < count($this->allow) |
|||
? new MethodNotAllowedException(array_unique(array_map('strtoupper', $this->allow))) |
|||
: new ResourceNotFoundException(); |
|||
} |
|||
|
|||
protected function matchCollection($pathinfo, RouteCollection $routes) |
|||
{ |
|||
$pathinfo = urldecode($pathinfo); |
|||
|
|||
foreach ($routes as $name => $route) { |
|||
if ($route instanceof RouteCollection) { |
|||
if (false === strpos($route->getPrefix(), '{') && $route->getPrefix() !== substr($pathinfo, 0, strlen($route->getPrefix()))) { |
|||
continue; |
|||
} |
|||
|
|||
if (!$ret = $this->matchCollection($pathinfo, $route)) { |
|||
continue; |
|||
} |
|||
|
|||
return $ret; |
|||
} |
|||
|
|||
$compiledRoute = $route->compile(); |
|||
|
|||
// check the static prefix of the URL first. Only use the more expensive preg_match when it matches
|
|||
if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { |
|||
continue; |
|||
} |
|||
|
|||
if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { |
|||
continue; |
|||
} |
|||
|
|||
// check HTTP method requirement
|
|||
if ($req = $route->getRequirement('_method')) { |
|||
// HEAD and GET are equivalent as per RFC
|
|||
if ('HEAD' === $method = $this->context->getMethod()) { |
|||
$method = 'GET'; |
|||
} |
|||
|
|||
if (!in_array($method, $req = explode('|', strtoupper($req)))) { |
|||
$this->allow = array_merge($this->allow, $req); |
|||
|
|||
continue; |
|||
} |
|||
} |
|||
|
|||
return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name)); |
|||
} |
|||
} |
|||
|
|||
protected function mergeDefaults($params, $defaults) |
|||
{ |
|||
$parameters = $defaults; |
|||
foreach ($params as $key => $value) { |
|||
if (!is_int($key)) { |
|||
$parameters[$key] = rawurldecode($value); |
|||
} |
|||
} |
|||
|
|||
return $parameters; |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
<?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; |
|||
|
|||
use Symfony\Component\Routing\RequestContextAwareInterface; |
|||
|
|||
/** |
|||
* UrlMatcherInterface is the interface that all URL matcher classes must implement. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
interface UrlMatcherInterface extends RequestContextAwareInterface |
|||
{ |
|||
/** |
|||
* Tries to match a URL with a set of routes. |
|||
* |
|||
* @param string $pathinfo The path info to be parsed |
|||
* |
|||
* @return array An array of parameters |
|||
* |
|||
* @throws ResourceNotFoundException If the resource could not be found |
|||
* @throws MethodNotAllowedException If the resource was found but the request method is not allowed |
|||
* |
|||
* @api |
|||
*/ |
|||
public function match($pathinfo); |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
Routing Component |
|||
================= |
|||
|
|||
Routing associates a request with the code that will convert it to a response. |
|||
|
|||
The example below demonstrates how you can set up a fully working routing |
|||
system: |
|||
|
|||
use Symfony\Component\HttpFoundation\Request; |
|||
use Symfony\Component\Routing\Matcher\UrlMatcher; |
|||
use Symfony\Component\Routing\RequestContext; |
|||
use Symfony\Component\Routing\RouteCollection; |
|||
use Symfony\Component\Routing\Route; |
|||
|
|||
$routes = new RouteCollection(); |
|||
$routes->add('hello', new Route('/hello', array('controller' => 'foo'))); |
|||
|
|||
$context = new RequestContext(); |
|||
|
|||
// this is optional and can be done without a Request instance |
|||
$context->fromRequest(Request::createFromGlobals()); |
|||
|
|||
$matcher = new UrlMatcher($routes, $context); |
|||
|
|||
$parameters = $matcher->match('/hello'); |
|||
|
|||
Resources |
|||
--------- |
|||
|
|||
Unit tests: |
|||
|
|||
https://github.com/symfony/symfony/tree/master/tests/Symfony/Tests/Component/Routing |
|||
@ -0,0 +1,250 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* Holds information about the current request. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class RequestContext |
|||
{ |
|||
private $baseUrl; |
|||
private $method; |
|||
private $host; |
|||
private $scheme; |
|||
private $httpPort; |
|||
private $httpsPort; |
|||
private $parameters; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param string $baseUrl The base URL |
|||
* @param string $method The HTTP method |
|||
* @param string $host The HTTP host name |
|||
* @param string $scheme The HTTP scheme |
|||
* @param integer $httpPort The HTTP port |
|||
* @param integer $httpsPort The HTTPS port |
|||
* |
|||
* @api |
|||
*/ |
|||
public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443) |
|||
{ |
|||
$this->baseUrl = $baseUrl; |
|||
$this->method = strtoupper($method); |
|||
$this->host = $host; |
|||
$this->scheme = strtolower($scheme); |
|||
$this->httpPort = $httpPort; |
|||
$this->httpsPort = $httpsPort; |
|||
$this->parameters = array(); |
|||
} |
|||
|
|||
/** |
|||
* Gets the base URL. |
|||
* |
|||
* @return string The base URL |
|||
*/ |
|||
public function getBaseUrl() |
|||
{ |
|||
return $this->baseUrl; |
|||
} |
|||
|
|||
/** |
|||
* Sets the base URL. |
|||
* |
|||
* @param string $baseUrl The base URL |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setBaseUrl($baseUrl) |
|||
{ |
|||
$this->baseUrl = $baseUrl; |
|||
} |
|||
|
|||
/** |
|||
* Gets the HTTP method. |
|||
* |
|||
* The method is always an uppercased string. |
|||
* |
|||
* @return string The HTTP method |
|||
*/ |
|||
public function getMethod() |
|||
{ |
|||
return $this->method; |
|||
} |
|||
|
|||
/** |
|||
* Sets the HTTP method. |
|||
* |
|||
* @param string $method The HTTP method |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setMethod($method) |
|||
{ |
|||
$this->method = strtoupper($method); |
|||
} |
|||
|
|||
/** |
|||
* Gets the HTTP host. |
|||
* |
|||
* @return string The HTTP host |
|||
*/ |
|||
public function getHost() |
|||
{ |
|||
return $this->host; |
|||
} |
|||
|
|||
/** |
|||
* Sets the HTTP host. |
|||
* |
|||
* @param string $host The HTTP host |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setHost($host) |
|||
{ |
|||
$this->host = $host; |
|||
} |
|||
|
|||
/** |
|||
* Gets the HTTP scheme. |
|||
* |
|||
* @return string The HTTP scheme |
|||
*/ |
|||
public function getScheme() |
|||
{ |
|||
return $this->scheme; |
|||
} |
|||
|
|||
/** |
|||
* Sets the HTTP scheme. |
|||
* |
|||
* @param string $scheme The HTTP scheme |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setScheme($scheme) |
|||
{ |
|||
$this->scheme = strtolower($scheme); |
|||
} |
|||
|
|||
/** |
|||
* Gets the HTTP port. |
|||
* |
|||
* @return string The HTTP port |
|||
*/ |
|||
public function getHttpPort() |
|||
{ |
|||
return $this->httpPort; |
|||
} |
|||
|
|||
/** |
|||
* Sets the HTTP port. |
|||
* |
|||
* @param string $httpPort The HTTP port |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setHttpPort($httpPort) |
|||
{ |
|||
$this->httpPort = $httpPort; |
|||
} |
|||
|
|||
/** |
|||
* Gets the HTTPS port. |
|||
* |
|||
* @return string The HTTPS port |
|||
*/ |
|||
public function getHttpsPort() |
|||
{ |
|||
return $this->httpsPort; |
|||
} |
|||
|
|||
/** |
|||
* Sets the HTTPS port. |
|||
* |
|||
* @param string $httpsPort The HTTPS port |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setHttpsPort($httpsPort) |
|||
{ |
|||
$this->httpsPort = $httpsPort; |
|||
} |
|||
|
|||
/** |
|||
* Returns the parameters. |
|||
* |
|||
* @return array The parameters |
|||
*/ |
|||
public function getParameters() |
|||
{ |
|||
return $this->parameters; |
|||
} |
|||
|
|||
/** |
|||
* Sets the parameters. |
|||
* |
|||
* This method implements a fluent interface. |
|||
* |
|||
* @param array $parameters The parameters |
|||
* |
|||
* @return Route The current Route instance |
|||
*/ |
|||
public function setParameters(array $parameters) |
|||
{ |
|||
$this->parameters = $parameters; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Gets a parameter value. |
|||
* |
|||
* @param string $name A parameter name |
|||
* |
|||
* @return mixed The parameter value |
|||
*/ |
|||
public function getParameter($name) |
|||
{ |
|||
return isset($this->parameters[$name]) ? $this->parameters[$name] : null; |
|||
} |
|||
|
|||
/** |
|||
* Checks if a parameter value is set for the given parameter. |
|||
* |
|||
* @param string $name A parameter name |
|||
* |
|||
* @return Boolean true if the parameter value is set, false otherwise |
|||
*/ |
|||
public function hasParameter($name) |
|||
{ |
|||
return array_key_exists($name, $this->parameters); |
|||
} |
|||
|
|||
/** |
|||
* Sets a parameter value. |
|||
* |
|||
* @param string $name A parameter name |
|||
* @param mixed $parameter The parameter value |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setParameter($name, $parameter) |
|||
{ |
|||
$this->parameters[$name] = $parameter; |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* @api |
|||
*/ |
|||
interface RequestContextAwareInterface |
|||
{ |
|||
/** |
|||
* Sets the request context. |
|||
* |
|||
* @param RequestContext $context The context |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setContext(RequestContext $context); |
|||
} |
|||
@ -0,0 +1,312 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* A Route describes a route and its parameters. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class Route |
|||
{ |
|||
private $pattern; |
|||
private $defaults; |
|||
private $requirements; |
|||
private $options; |
|||
private $compiled; |
|||
|
|||
private static $compilers = array(); |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * compiler_class: A class name able to compile this route instance (RouteCompiler by default) |
|||
* |
|||
* @param string $pattern The pattern to match |
|||
* @param array $defaults An array of default parameter values |
|||
* @param array $requirements An array of requirements for parameters (regexes) |
|||
* @param array $options An array of options |
|||
* |
|||
* @api |
|||
*/ |
|||
public function __construct($pattern, array $defaults = array(), array $requirements = array(), array $options = array()) |
|||
{ |
|||
$this->setPattern($pattern); |
|||
$this->setDefaults($defaults); |
|||
$this->setRequirements($requirements); |
|||
$this->setOptions($options); |
|||
} |
|||
|
|||
public function __clone() |
|||
{ |
|||
$this->compiled = null; |
|||
} |
|||
|
|||
/** |
|||
* Returns the pattern. |
|||
* |
|||
* @return string The pattern |
|||
*/ |
|||
public function getPattern() |
|||
{ |
|||
return $this->pattern; |
|||
} |
|||
|
|||
/** |
|||
* Sets the pattern. |
|||
* |
|||
* This method implements a fluent interface. |
|||
* |
|||
* @param string $pattern The pattern |
|||
* |
|||
* @return Route The current Route instance |
|||
*/ |
|||
public function setPattern($pattern) |
|||
{ |
|||
$this->pattern = trim($pattern); |
|||
|
|||
// a route must start with a slash
|
|||
if (empty($this->pattern) || '/' !== $this->pattern[0]) { |
|||
$this->pattern = '/'.$this->pattern; |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Returns the options. |
|||
* |
|||
* @return array The options |
|||
*/ |
|||
public function getOptions() |
|||
{ |
|||
return $this->options; |
|||
} |
|||
|
|||
/** |
|||
* Sets the options. |
|||
* |
|||
* This method implements a fluent interface. |
|||
* |
|||
* @param array $options The options |
|||
* |
|||
* @return Route The current Route instance |
|||
*/ |
|||
public function setOptions(array $options) |
|||
{ |
|||
$this->options = array_merge(array( |
|||
'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', |
|||
), $options); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Sets an option value. |
|||
* |
|||
* This method implements a fluent interface. |
|||
* |
|||
* @param string $name An option name |
|||
* @param mixed $value The option value |
|||
* |
|||
* @return Route The current Route instance |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setOption($name, $value) |
|||
{ |
|||
$this->options[$name] = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Get an option value. |
|||
* |
|||
* @param string $name An option name |
|||
* |
|||
* @return mixed The option value |
|||
*/ |
|||
public function getOption($name) |
|||
{ |
|||
return isset($this->options[$name]) ? $this->options[$name] : null; |
|||
} |
|||
|
|||
/** |
|||
* Returns the defaults. |
|||
* |
|||
* @return array The defaults |
|||
*/ |
|||
public function getDefaults() |
|||
{ |
|||
return $this->defaults; |
|||
} |
|||
|
|||
/** |
|||
* Sets the defaults. |
|||
* |
|||
* This method implements a fluent interface. |
|||
* |
|||
* @param array $defaults The defaults |
|||
* |
|||
* @return Route The current Route instance |
|||
*/ |
|||
public function setDefaults(array $defaults) |
|||
{ |
|||
$this->defaults = array(); |
|||
foreach ($defaults as $name => $default) { |
|||
$this->defaults[(string) $name] = $default; |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Gets a default value. |
|||
* |
|||
* @param string $name A variable name |
|||
* |
|||
* @return mixed The default value |
|||
*/ |
|||
public function getDefault($name) |
|||
{ |
|||
return isset($this->defaults[$name]) ? $this->defaults[$name] : null; |
|||
} |
|||
|
|||
/** |
|||
* Checks if a default value is set for the given variable. |
|||
* |
|||
* @param string $name A variable name |
|||
* |
|||
* @return Boolean true if the default value is set, false otherwise |
|||
*/ |
|||
public function hasDefault($name) |
|||
{ |
|||
return array_key_exists($name, $this->defaults); |
|||
} |
|||
|
|||
/** |
|||
* Sets a default value. |
|||
* |
|||
* @param string $name A variable name |
|||
* @param mixed $default The default value |
|||
* |
|||
* @return Route The current Route instance |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setDefault($name, $default) |
|||
{ |
|||
$this->defaults[(string) $name] = $default; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Returns the requirements. |
|||
* |
|||
* @return array The requirements |
|||
*/ |
|||
public function getRequirements() |
|||
{ |
|||
return $this->requirements; |
|||
} |
|||
|
|||
/** |
|||
* Sets the requirements. |
|||
* |
|||
* This method implements a fluent interface. |
|||
* |
|||
* @param array $requirements The requirements |
|||
* |
|||
* @return Route The current Route instance |
|||
*/ |
|||
public function setRequirements(array $requirements) |
|||
{ |
|||
$this->requirements = array(); |
|||
foreach ($requirements as $key => $regex) { |
|||
$this->requirements[$key] = $this->sanitizeRequirement($key, $regex); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Returns the requirement for the given key. |
|||
* |
|||
* @param string $key The key |
|||
* |
|||
* @return string The regex |
|||
*/ |
|||
public function getRequirement($key) |
|||
{ |
|||
return isset($this->requirements[$key]) ? $this->requirements[$key] : null; |
|||
} |
|||
|
|||
/** |
|||
* Sets a requirement for the given key. |
|||
* |
|||
* @param string $key The key |
|||
* @param string $regex The regex |
|||
* |
|||
* @return Route The current Route instance |
|||
* |
|||
* @api |
|||
*/ |
|||
public function setRequirement($key, $regex) |
|||
{ |
|||
$this->requirements[$key] = $this->sanitizeRequirement($key, $regex); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Compiles the route. |
|||
* |
|||
* @return CompiledRoute A CompiledRoute instance |
|||
*/ |
|||
public function compile() |
|||
{ |
|||
if (null !== $this->compiled) { |
|||
return $this->compiled; |
|||
} |
|||
|
|||
$class = $this->getOption('compiler_class'); |
|||
|
|||
if (!isset(self::$compilers[$class])) { |
|||
self::$compilers[$class] = new $class; |
|||
} |
|||
|
|||
return $this->compiled = self::$compilers[$class]->compile($this); |
|||
} |
|||
|
|||
private function sanitizeRequirement($key, $regex) |
|||
{ |
|||
if (is_array($regex)) { |
|||
throw new \InvalidArgumentException(sprintf('Routing requirements must be a string, array given for "%s"', $key)); |
|||
} |
|||
|
|||
if ('^' == $regex[0]) { |
|||
$regex = substr($regex, 1); |
|||
} |
|||
|
|||
if ('$' == substr($regex, -1)) { |
|||
$regex = substr($regex, 0, -1); |
|||
} |
|||
|
|||
return $regex; |
|||
} |
|||
} |
|||
@ -0,0 +1,259 @@ |
|||
<?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; |
|||
|
|||
use Symfony\Component\Config\Resource\ResourceInterface; |
|||
|
|||
/** |
|||
* A RouteCollection represents a set of Route instances. |
|||
* |
|||
* When adding a route, it overrides existing routes with the |
|||
* same name defined in the instance or its children and parents. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
* |
|||
* @api |
|||
*/ |
|||
class RouteCollection implements \IteratorAggregate |
|||
{ |
|||
private $routes; |
|||
private $resources; |
|||
private $prefix; |
|||
private $parent; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @api |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
$this->routes = array(); |
|||
$this->resources = array(); |
|||
$this->prefix = ''; |
|||
} |
|||
|
|||
public function __clone() |
|||
{ |
|||
foreach ($this->routes as $name => $route) { |
|||
$this->routes[$name] = clone $route; |
|||
if ($route instanceof RouteCollection) { |
|||
$this->routes[$name]->setParent($this); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the parent RouteCollection. |
|||
* |
|||
* @return RouteCollection The parent RouteCollection |
|||
*/ |
|||
public function getParent() |
|||
{ |
|||
return $this->parent; |
|||
} |
|||
|
|||
/** |
|||
* Sets the parent RouteCollection. |
|||
* |
|||
* @param RouteCollection $parent The parent RouteCollection |
|||
*/ |
|||
public function setParent(RouteCollection $parent) |
|||
{ |
|||
$this->parent = $parent; |
|||
} |
|||
|
|||
/** |
|||
* Gets the current RouteCollection as an Iterator. |
|||
* |
|||
* @return \ArrayIterator An \ArrayIterator interface |
|||
*/ |
|||
public function getIterator() |
|||
{ |
|||
return new \ArrayIterator($this->routes); |
|||
} |
|||
|
|||
/** |
|||
* Adds a route. |
|||
* |
|||
* @param string $name The route name |
|||
* @param Route $route A Route instance |
|||
* |
|||
* @throws \InvalidArgumentException When route name contains non valid characters |
|||
* |
|||
* @api |
|||
*/ |
|||
public function add($name, Route $route) |
|||
{ |
|||
if (!preg_match('/^[a-z0-9A-Z_.]+$/', $name)) { |
|||
throw new \InvalidArgumentException(sprintf('The provided route name "%s" contains non valid characters. A route name must only contain digits (0-9), letters (a-z and A-Z), underscores (_) and dots (.).', $name)); |
|||
} |
|||
|
|||
$parent = $this; |
|||
while ($parent->getParent()) { |
|||
$parent = $parent->getParent(); |
|||
} |
|||
|
|||
if ($parent) { |
|||
$parent->remove($name); |
|||
} |
|||
|
|||
$this->routes[$name] = $route; |
|||
} |
|||
|
|||
/** |
|||
* Returns the array of routes. |
|||
* |
|||
* @return array An array of routes |
|||
*/ |
|||
public function all() |
|||
{ |
|||
$routes = array(); |
|||
foreach ($this->routes as $name => $route) { |
|||
if ($route instanceof RouteCollection) { |
|||
$routes = array_merge($routes, $route->all()); |
|||
} else { |
|||
$routes[$name] = $route; |
|||
} |
|||
} |
|||
|
|||
return $routes; |
|||
} |
|||
|
|||
/** |
|||
* Gets a route by name. |
|||
* |
|||
* @param string $name The route name |
|||
* |
|||
* @return Route $route A Route instance |
|||
*/ |
|||
public function get($name) |
|||
{ |
|||
// get the latest defined route
|
|||
foreach (array_reverse($this->routes) as $routes) { |
|||
if (!$routes instanceof RouteCollection) { |
|||
continue; |
|||
} |
|||
|
|||
if (null !== $route = $routes->get($name)) { |
|||
return $route; |
|||
} |
|||
} |
|||
|
|||
if (isset($this->routes[$name])) { |
|||
return $this->routes[$name]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Removes a route by name. |
|||
* |
|||
* @param string $name The route name |
|||
*/ |
|||
public function remove($name) |
|||
{ |
|||
if (isset($this->routes[$name])) { |
|||
unset($this->routes[$name]); |
|||
} |
|||
|
|||
foreach ($this->routes as $routes) { |
|||
if ($routes instanceof RouteCollection) { |
|||
$routes->remove($name); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Adds a route collection to the current set of routes (at the end of the current set). |
|||
* |
|||
* @param RouteCollection $collection A RouteCollection instance |
|||
* @param string $prefix An optional prefix to add before each pattern of the route collection |
|||
* |
|||
* @api |
|||
*/ |
|||
public function addCollection(RouteCollection $collection, $prefix = '') |
|||
{ |
|||
$collection->setParent($this); |
|||
$collection->addPrefix($prefix); |
|||
|
|||
// remove all routes with the same name in all existing collections
|
|||
foreach (array_keys($collection->all()) as $name) { |
|||
$this->remove($name); |
|||
} |
|||
|
|||
$this->routes[] = $collection; |
|||
} |
|||
|
|||
/** |
|||
* Adds a prefix to all routes in the current set. |
|||
* |
|||
* @param string $prefix An optional prefix to add before each pattern of the route collection |
|||
* |
|||
* @api |
|||
*/ |
|||
public function addPrefix($prefix) |
|||
{ |
|||
// a prefix must not end with a slash
|
|||
$prefix = rtrim($prefix, '/'); |
|||
|
|||
if (!$prefix) { |
|||
return; |
|||
} |
|||
|
|||
// a prefix must start with a slash
|
|||
if ('/' !== $prefix[0]) { |
|||
$prefix = '/'.$prefix; |
|||
} |
|||
|
|||
$this->prefix = $prefix.$this->prefix; |
|||
|
|||
foreach ($this->routes as $name => $route) { |
|||
if ($route instanceof RouteCollection) { |
|||
$route->addPrefix($prefix); |
|||
} else { |
|||
$route->setPattern($prefix.$route->getPattern()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public function getPrefix() |
|||
{ |
|||
return $this->prefix; |
|||
} |
|||
|
|||
/** |
|||
* Returns an array of resources loaded to build this collection. |
|||
* |
|||
* @return ResourceInterface[] An array of resources |
|||
*/ |
|||
public function getResources() |
|||
{ |
|||
$resources = $this->resources; |
|||
foreach ($this as $routes) { |
|||
if ($routes instanceof RouteCollection) { |
|||
$resources = array_merge($resources, $routes->getResources()); |
|||
} |
|||
} |
|||
|
|||
return array_unique($resources); |
|||
} |
|||
|
|||
/** |
|||
* Adds a resource for this collection. |
|||
* |
|||
* @param ResourceInterface $resource A resource instance |
|||
*/ |
|||
public function addResource(ResourceInterface $resource) |
|||
{ |
|||
$this->resources[] = $resource; |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* RouteCompiler compiles Route instances to CompiledRoute instances. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class RouteCompiler implements RouteCompilerInterface |
|||
{ |
|||
/** |
|||
* Compiles the current route instance. |
|||
* |
|||
* @param Route $route A Route instance |
|||
* |
|||
* @return CompiledRoute A CompiledRoute instance |
|||
*/ |
|||
public function compile(Route $route) |
|||
{ |
|||
$pattern = $route->getPattern(); |
|||
$len = strlen($pattern); |
|||
$tokens = array(); |
|||
$variables = array(); |
|||
$pos = 0; |
|||
preg_match_all('#.\{([\w\d_]+)\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); |
|||
foreach ($matches as $match) { |
|||
if ($text = substr($pattern, $pos, $match[0][1] - $pos)) { |
|||
$tokens[] = array('text', $text); |
|||
} |
|||
$seps = array($pattern[$pos]); |
|||
$pos = $match[0][1] + strlen($match[0][0]); |
|||
$var = $match[1][0]; |
|||
|
|||
if ($req = $route->getRequirement($var)) { |
|||
$regexp = $req; |
|||
} else { |
|||
if ($pos !== $len) { |
|||
$seps[] = $pattern[$pos]; |
|||
} |
|||
$regexp = sprintf('[^%s]+?', preg_quote(implode('', array_unique($seps)), '#')); |
|||
} |
|||
|
|||
$tokens[] = array('variable', $match[0][0][0], $regexp, $var); |
|||
|
|||
if (in_array($var, $variables)) { |
|||
throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $route->getPattern(), $var)); |
|||
} |
|||
|
|||
$variables[] = $var; |
|||
} |
|||
|
|||
if ($pos < $len) { |
|||
$tokens[] = array('text', substr($pattern, $pos)); |
|||
} |
|||
|
|||
// find the first optional token
|
|||
$firstOptional = INF; |
|||
for ($i = count($tokens) - 1; $i >= 0; $i--) { |
|||
$token = $tokens[$i]; |
|||
if ('variable' === $token[0] && $route->hasDefault($token[3])) { |
|||
$firstOptional = $i; |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// compute the matching regexp
|
|||
$regexp = ''; |
|||
for ($i = 0, $nbToken = count($tokens); $i < $nbToken; $i++) { |
|||
$regexp .= $this->computeRegexp($tokens, $i, $firstOptional); |
|||
} |
|||
|
|||
return new CompiledRoute( |
|||
$route, |
|||
'text' === $tokens[0][0] ? $tokens[0][1] : '', |
|||
sprintf("#^%s$#s", $regexp), |
|||
array_reverse($tokens), |
|||
$variables |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Computes the regexp used to match the token. |
|||
* |
|||
* @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 |
|||
* |
|||
* @return string The regexp |
|||
*/ |
|||
private function computeRegexp(array $tokens, $index, $firstOptional) |
|||
{ |
|||
$token = $tokens[$index]; |
|||
if ('text' === $token[0]) { |
|||
// Text tokens
|
|||
return preg_quote($token[1], '#'); |
|||
} else { |
|||
// Variable tokens
|
|||
if (0 === $index && 0 === $firstOptional && 1 == count($tokens)) { |
|||
// When the only token is an optional variable token, the separator is required
|
|||
return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], '#'), $token[3], $token[2]); |
|||
} else { |
|||
$nbTokens = count($tokens); |
|||
$regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], '#'), $token[3], $token[2]); |
|||
if ($index >= $firstOptional) { |
|||
// Enclose each optional tokens in a subpattern to make it optional
|
|||
$regexp = "(?:$regexp"; |
|||
if ($nbTokens - 1 == $index) { |
|||
// Close the optional subpatterns
|
|||
$regexp .= str_repeat(")?", $nbTokens - $firstOptional); |
|||
} |
|||
} |
|||
|
|||
return $regexp; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?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; |
|||
|
|||
/** |
|||
* RouteCompilerInterface is the interface that all RouteCompiler classes must implements. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface RouteCompilerInterface |
|||
{ |
|||
/** |
|||
* Compiles the current route instance. |
|||
* |
|||
* @param Route $route A Route instance |
|||
* |
|||
* @return CompiledRoute A CompiledRoute instance |
|||
*/ |
|||
public function compile(Route $route); |
|||
} |
|||
@ -0,0 +1,263 @@ |
|||
<?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; |
|||
|
|||
use Symfony\Component\Config\Loader\LoaderInterface; |
|||
use Symfony\Component\Config\ConfigCache; |
|||
|
|||
/** |
|||
* The Router class is an example of the integration of all pieces of the |
|||
* routing system for easier use. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Router implements RouterInterface |
|||
{ |
|||
protected $matcher; |
|||
protected $generator; |
|||
protected $defaults; |
|||
protected $context; |
|||
protected $loader; |
|||
protected $collection; |
|||
protected $resource; |
|||
protected $options; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param LoaderInterface $loader A LoaderInterface instance |
|||
* @param mixed $resource The main resource to load |
|||
* @param array $options An array of options |
|||
* @param RequestContext $context The context |
|||
* @param array $defaults The default values |
|||
*/ |
|||
public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, array $defaults = array()) |
|||
{ |
|||
$this->loader = $loader; |
|||
$this->resource = $resource; |
|||
$this->context = null === $context ? new RequestContext() : $context; |
|||
$this->defaults = $defaults; |
|||
$this->setOptions($options); |
|||
} |
|||
|
|||
/** |
|||
* Sets options. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * cache_dir: The cache directory (or null to disable caching) |
|||
* * debug: Whether to enable debugging or not (false by default) |
|||
* * resource_type: Type hint for the main resource (optional) |
|||
* |
|||
* @param array $options An array of options |
|||
* |
|||
* @throws \InvalidArgumentException When unsupported option is provided |
|||
*/ |
|||
public function setOptions(array $options) |
|||
{ |
|||
$this->options = array( |
|||
'cache_dir' => null, |
|||
'debug' => false, |
|||
'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', |
|||
'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', |
|||
'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', |
|||
'generator_cache_class' => 'ProjectUrlGenerator', |
|||
'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', |
|||
'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', |
|||
'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', |
|||
'matcher_cache_class' => 'ProjectUrlMatcher', |
|||
'resource_type' => null, |
|||
); |
|||
|
|||
// check option names and live merge, if errors are encountered Exception will be thrown
|
|||
$invalid = array(); |
|||
$isInvalid = false; |
|||
foreach ($options as $key => $value) { |
|||
if (array_key_exists($key, $this->options)) { |
|||
$this->options[$key] = $value; |
|||
} else { |
|||
$isInvalid = true; |
|||
$invalid[] = $key; |
|||
} |
|||
} |
|||
|
|||
if ($isInvalid) { |
|||
throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('\', \'', $invalid))); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Sets an option. |
|||
* |
|||
* @param string $key The key |
|||
* @param mixed $value The value |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function setOption($key, $value) |
|||
{ |
|||
if (!array_key_exists($key, $this->options)) { |
|||
throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); |
|||
} |
|||
|
|||
$this->options[$key] = $value; |
|||
} |
|||
|
|||
/** |
|||
* Gets an option value. |
|||
* |
|||
* @param string $key The key |
|||
* |
|||
* @return mixed The value |
|||
* |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function getOption($key) |
|||
{ |
|||
if (!array_key_exists($key, $this->options)) { |
|||
throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); |
|||
} |
|||
|
|||
return $this->options[$key]; |
|||
} |
|||
|
|||
/** |
|||
* Gets the RouteCollection instance associated with this Router. |
|||
* |
|||
* @return RouteCollection A RouteCollection instance |
|||
*/ |
|||
public function getRouteCollection() |
|||
{ |
|||
if (null === $this->collection) { |
|||
$this->collection = $this->loader->load($this->resource, $this->options['resource_type']); |
|||
} |
|||
|
|||
return $this->collection; |
|||
} |
|||
|
|||
/** |
|||
* Sets the request context. |
|||
* |
|||
* @param RequestContext $context The context |
|||
*/ |
|||
public function setContext(RequestContext $context) |
|||
{ |
|||
$this->context = $context; |
|||
|
|||
$this->getMatcher()->setContext($context); |
|||
$this->getGenerator()->setContext($context); |
|||
} |
|||
|
|||
/** |
|||
* Gets the request context. |
|||
* |
|||
* @return RequestContext The context |
|||
*/ |
|||
public function getContext() |
|||
{ |
|||
return $this->context; |
|||
} |
|||
|
|||
/** |
|||
* Generates a URL from the given parameters. |
|||
* |
|||
* @param string $name The name of the route |
|||
* @param mixed $parameters An array of parameters |
|||
* @param Boolean $absolute Whether to generate an absolute URL |
|||
* |
|||
* @return string The generated URL |
|||
*/ |
|||
public function generate($name, $parameters = array(), $absolute = false) |
|||
{ |
|||
return $this->getGenerator()->generate($name, $parameters, $absolute); |
|||
} |
|||
|
|||
/** |
|||
* Tries to match a URL with a set of routes. |
|||
* |
|||
* Returns false if no route matches the URL. |
|||
* |
|||
* @param string $url URL to be parsed |
|||
* |
|||
* @return array|false An array of parameters or false if no route matches |
|||
*/ |
|||
public function match($url) |
|||
{ |
|||
return $this->getMatcher()->match($url); |
|||
} |
|||
|
|||
/** |
|||
* Gets the UrlMatcher instance associated with this Router. |
|||
* |
|||
* @return UrlMatcherInterface A UrlMatcherInterface instance |
|||
*/ |
|||
public function getMatcher() |
|||
{ |
|||
if (null !== $this->matcher) { |
|||
return $this->matcher; |
|||
} |
|||
|
|||
if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { |
|||
return $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context, $this->defaults); |
|||
} |
|||
|
|||
$class = $this->options['matcher_cache_class']; |
|||
$cache = new ConfigCache($this->options['cache_dir'].'/'.$class.'.php', $this->options['debug']); |
|||
if (!$cache->isFresh($class)) { |
|||
$dumper = new $this->options['matcher_dumper_class']($this->getRouteCollection()); |
|||
|
|||
$options = array( |
|||
'class' => $class, |
|||
'base_class' => $this->options['matcher_base_class'], |
|||
); |
|||
|
|||
$cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); |
|||
} |
|||
|
|||
require_once $cache; |
|||
|
|||
return $this->matcher = new $class($this->context, $this->defaults); |
|||
} |
|||
|
|||
/** |
|||
* Gets the UrlGenerator instance associated with this Router. |
|||
* |
|||
* @return UrlGeneratorInterface A UrlGeneratorInterface instance |
|||
*/ |
|||
public function getGenerator() |
|||
{ |
|||
if (null !== $this->generator) { |
|||
return $this->generator; |
|||
} |
|||
|
|||
if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { |
|||
return $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->defaults); |
|||
} |
|||
|
|||
$class = $this->options['generator_cache_class']; |
|||
$cache = new ConfigCache($this->options['cache_dir'].'/'.$class.'.php', $this->options['debug']); |
|||
if (!$cache->isFresh($class)) { |
|||
$dumper = new $this->options['generator_dumper_class']($this->getRouteCollection()); |
|||
|
|||
$options = array( |
|||
'class' => $class, |
|||
'base_class' => $this->options['generator_base_class'], |
|||
); |
|||
|
|||
$cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); |
|||
} |
|||
|
|||
require_once $cache; |
|||
|
|||
return $this->generator = new $class($this->context, $this->defaults); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
<?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; |
|||
|
|||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
|||
use Symfony\Component\Routing\Matcher\UrlMatcherInterface; |
|||
|
|||
/** |
|||
* RouterInterface is the interface that all Router classes must implements. |
|||
* |
|||
* This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. |
|||
* |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface |
|||
{ |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
{ |
|||
"name": "symfony/routing", |
|||
"type": "library", |
|||
"description": "Symfony Routing Component", |
|||
"keywords": [], |
|||
"homepage": "http://symfony.com", |
|||
"license": "MIT", |
|||
"authors": [ |
|||
{ |
|||
"name": "Fabien Potencier", |
|||
"email": "fabien@symfony.com" |
|||
}, |
|||
{ |
|||
"name": "Symfony Community", |
|||
"homepage": "http://symfony.com/contributors" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": ">=5.3.2" |
|||
}, |
|||
"suggest": { |
|||
"symfony/config": "self.version", |
|||
"symfony/yaml": "self.version" |
|||
}, |
|||
"autoload": { |
|||
"psr-0": { "Symfony\\Component\\Routing": "" } |
|||
}, |
|||
"target-dir": "Symfony/Component/Routing" |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
{ |
|||
"description": "ownCloud gives you universal access to your files/contacts/calendar through a web interface or WebDAV.", |
|||
"homepage": "http://owncloud.org", |
|||
"license": "AGPL-3.0+", |
|||
"support": { |
|||
"email": "owncloud@kde.org", |
|||
"irc": "irc://irc.freenode.org/owncloud", |
|||
"forum": "http://forum.owncloud.org/", |
|||
"issues": "https://github.com/owncloud/core/issues" |
|||
}, |
|||
"require": { |
|||
"php": ">=5.3.2", |
|||
"symfony/routing": "2.0.*" |
|||
}, |
|||
"config": { |
|||
"vendor-dir": "3rdparty" |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue