Include copy of Symfony routing component, and don't use composer

This commit is contained in:
Bart Visscher 2012-10-27 16:24:24 +02:00
parent 8a3eda16f2
commit c1c76539cc
44 changed files with 4037 additions and 22 deletions

4
.gitignore vendored
View File

@ -54,7 +54,3 @@ nbproject
# WebFinger
.well-known
/.buildpath
3rdparty/autoload.php
3rdparty/composer/
3rdparty/symfony/
composer.lock

BIN
3rdparty/bin/composer vendored

Binary file not shown.

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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
{
}

View File

@ -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
{
}

View File

@ -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;
}
}

View File

@ -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
{
}

View File

@ -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
{
}

View File

@ -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
{
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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.

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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
{
}

View File

@ -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"
}

View File

@ -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"
}
}