Commit a6c393ef authored by Dries's avatar Dries

Issue #1874530 by Crell: Add Symfony CMF Routing component.

parent 99b26ea8
......@@ -14,7 +14,8 @@
"twig/twig": "1.*@stable",
"doctrine/common": "2.3.*@stable",
"guzzle/http": "*",
"kriswallsmith/assetic": "1.1.*@alpha"
"kriswallsmith/assetic": "1.1.*@alpha",
"symfony-cmf/routing": "1.0.*@dev"
},
"minimum-stability": "dev"
}
{
"hash": "5d17aee0bd24c24563c2c864600fc5bd",
"hash": "27b5fb7194e0d492c69d372d8ba17b2b",
"packages": [
{
"name": "doctrine/common",
......@@ -309,6 +309,56 @@
"minification"
]
},
{
"name": "symfony-cmf/routing",
"version": "dev-master",
"target-dir": "Symfony/Cmf/Component/Routing",
"source": {
"type": "git",
"url": "https://github.com/symfony-cmf/Routing",
"reference": "1.0.0-alpha3"
},
"dist": {
"type": "zip",
"url": "https://github.com/symfony-cmf/Routing/archive/1.0.0-alpha3.zip",
"reference": "1.0.0-alpha3",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"symfony/routing": ">=2.1,<2.3-dev",
"symfony/http-kernel": ">=2.1,<2.3-dev"
},
"time": "2012-12-16 17:52:57",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"installation-source": "source",
"autoload": {
"psr-0": {
"Symfony\\Cmf\\Component\\Routing": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony CMF Community",
"homepage": "https://github.com/symfony-cmf/Routing/contributors"
}
],
"description": "Extends the Symfony2 routing component for dynamic routes and chaining several routers",
"homepage": "http://cmf.symfony.com",
"keywords": [
"database",
"routing"
]
},
{
"name": "symfony/class-loader",
"version": "dev-master",
......@@ -848,6 +898,7 @@
"stability-flags": {
"twig/twig": 0,
"doctrine/common": 0,
"kriswallsmith/assetic": 15
"kriswallsmith/assetic": 15,
"symfony-cmf/routing": 20
}
}
......@@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInit295209ab8f7c3b45c210d28fa6db3592::getLoader();
return ComposerAutoloaderInit23a41d5f637bb8e297fc063ef4ab931a::getLoader();
......@@ -16,6 +16,7 @@
'Symfony\\Component\\EventDispatcher\\' => $vendorDir . '/symfony/event-dispatcher/',
'Symfony\\Component\\DependencyInjection\\' => $vendorDir . '/symfony/dependency-injection/',
'Symfony\\Component\\ClassLoader\\' => $vendorDir . '/symfony/class-loader/',
'Symfony\\Cmf\\Component\\Routing' => $vendorDir . '/symfony-cmf/routing/',
'Guzzle\\Stream' => $vendorDir . '/guzzle/stream/',
'Guzzle\\Parser' => $vendorDir . '/guzzle/parser/',
'Guzzle\\Http' => $vendorDir . '/guzzle/http/',
......
......@@ -2,7 +2,7 @@
// autoload_real.php generated by Composer
class ComposerAutoloaderInit295209ab8f7c3b45c210d28fa6db3592
class ComposerAutoloaderInit23a41d5f637bb8e297fc063ef4ab931a
{
private static $loader;
......@@ -19,9 +19,9 @@ public static function getLoader()
return static::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit295209ab8f7c3b45c210d28fa6db3592', 'loadClassLoader'));
spl_autoload_register(array('ComposerAutoloaderInit23a41d5f637bb8e297fc063ef4ab931a', 'loadClassLoader'));
static::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit295209ab8f7c3b45c210d28fa6db3592', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit23a41d5f637bb8e297fc063ef4ab931a', 'loadClassLoader'));
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
......
......@@ -852,5 +852,56 @@
"client",
"Guzzle"
]
},
{
"name": "symfony-cmf/routing",
"version": "dev-master",
"version_normalized": "9999999-dev",
"target-dir": "Symfony/Cmf/Component/Routing",
"source": {
"type": "git",
"url": "https://github.com/symfony-cmf/Routing",
"reference": "1.0.0-alpha3"
},
"dist": {
"type": "zip",
"url": "https://github.com/symfony-cmf/Routing/archive/1.0.0-alpha3.zip",
"reference": "1.0.0-alpha3",
"shasum": ""
},
"require": {
"php": ">=5.3.2",
"symfony/routing": ">=2.1,<2.3-dev",
"symfony/http-kernel": ">=2.1,<2.3-dev"
},
"time": "2012-12-16 17:52:57",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"installation-source": "source",
"autoload": {
"psr-0": {
"Symfony\\Cmf\\Component\\Routing": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony CMF Community",
"homepage": "https://github.com/symfony-cmf/Routing/contributors"
}
],
"description": "Extends the Symfony2 routing component for dynamic routes and chaining several routers",
"homepage": "http://cmf.symfony.com",
"keywords": [
"database",
"routing"
]
}
]
language: php
env:
- SYMFONY_VERSION=2.1.*
- SYMFONY_VERSION=dev-master
before_script:
- composer require symfony/routing:${SYMFONY_VERSION}
- composer install --dev
script: phpunit --coverage-text
notifications:
irc: "irc.freenode.org#symfony-cmf"
email: "symfony-cmf-devs@googlegroups.com"
<?php
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
/**
* ChainRouter
*
* Allows access to a lot of different routers.
*
* @author Henrik Bjornskov <henrik@bjrnskov.dk>
* @author Magnus Nordlander <magnus@e-butik.se>
*/
class ChainRouter implements RouterInterface, RequestMatcherInterface, WarmableInterface
{
/**
* @var \Symfony\Component\Routing\RequestContext
*/
private $context;
/**
* @var Symfony\Component\Routing\RouterInterface[]
*/
private $routers = array();
/**
* @var \Symfony\Component\Routing\RouterInterface[] Array of routers, sorted by priority
*/
private $sortedRouters;
/**
* @var \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;
/**
* @var null|\Symfony\Component\HttpKernel\Log\LoggerInterface
*/
protected $logger;
/**
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* @return RequestContext
*/
public function getContext()
{
return $this->context;
}
/**
* Add a Router to the index
*
* @param RouterInterface $router The router instance
* @param integer $priority The priority
*/
public function add(RouterInterface $router, $priority = 0)
{
if (empty($this->routers[$priority])) {
$this->routers[$priority] = array();
}
$this->routers[$priority][] = $router;
$this->sortedRouters = array();
}
/**
* Sorts the routers and flattens them.
*
* @return array
*/
public function all()
{
if (empty($this->sortedRouters)) {
$this->sortedRouters = $this->sortRouters();
// setContext() is done here instead of in add() to avoid fatal errors when clearing and warming up caches
// See https://github.com/symfony-cmf/Routing/pull/18
$context = $this->getContext();
if (null !== $context) {
foreach ($this->sortedRouters as $router) {
if ($router instanceof RequestContextAwareInterface) {
$router->setContext($context);
}
}
}
}
return $this->sortedRouters;
}
/**
* Sort routers by priority.
* The highest priority number is the highest priority (reverse sorting)
*
* @return RouterInterface[]
*/
protected function sortRouters()
{
$sortedRouters = array();
krsort($this->routers);
foreach ($this->routers as $routers) {
$sortedRouters = array_merge($sortedRouters, $routers);
}
return $sortedRouters;
}
/**
* {@inheritdoc}
*
* Loops through all routes and tries to match the passed url.
*
* Note: You should use matchRequest if you can.
*/
public function match($url)
{
$methodNotAllowed = null;
/** @var $router ChainedRouterInterface */
foreach ($this->all() as $router) {
try {
return $router->match($url);
} catch (ResourceNotFoundException $e) {
if ($this->logger) {
$this->logger->info('Router '.get_class($router).' was not able to match, message "'.$e->getMessage().'"');
}
// Needs special care
} catch (MethodNotAllowedException $e) {
if ($this->logger) {
$this->logger->info('Router '.get_class($router).' throws MethodNotAllowedException with message "'.$e->getMessage().'"');
}
$methodNotAllowed = $e;
}
}
throw $methodNotAllowed ?: new ResourceNotFoundException("None of the routers in the chain matched '$url'");
}
/**
* {@inheritdoc}
*
* Loops through all routes and tries to match the passed request.
*/
public function matchRequest(Request $request)
{
$methodNotAllowed = null;
foreach ($this->all() as $router) {
try {
// the request/url match logic is the same as in Symfony/Component/HttpKernel/EventListener/RouterListener.php
// matching requests is more powerful than matching URLs only, so try that first
if ($router instanceof RequestMatcherInterface) {
return $router->matchRequest($request);
}
return $router->match($request->getPathInfo());
} catch (ResourceNotFoundException $e) {
if ($this->logger) {
$this->logger->info('Router '.get_class($router).' was not able to match, message "'.$e->getMessage().'"');
}
// Needs special care
} catch (MethodNotAllowedException $e) {
if ($this->logger) {
$this->logger->info('Router '.get_class($router).' throws MethodNotAllowedException with message "'.$e->getMessage().'"');
}
$methodNotAllowed = $e;
}
}
throw $methodNotAllowed ?: new ResourceNotFoundException("None of the routers in the chain matched this request");
}
/**
* {@inheritdoc}
*
* Loops through all registered routers and returns a router if one is found.
* It will always return the first route generated.
*/
public function generate($name, $parameters = array(), $absolute = false)
{
/** @var $router ChainedRouterInterface */
foreach ($this->all() as $router) {
// if $name and $router does not implement ChainedRouterInterface and $name is not a string, continue
// if $name and $router does not implement ChainedRouterInterface and $name is string but does not match a default Symfony2 route name, continue
if ($name && !$router instanceof ChainedRouterInterface) {
if (!is_string($name) || !preg_match('/^[a-z0-9A-Z_.]+$/', $name)) {
continue;
}
}
// If $router implements ChainedRouterInterface but doesn't support this route name, continue
if ($router instanceof ChainedRouterInterface && !$router->supports($name)) {
continue;
}
try {
return $router->generate($name, $parameters, $absolute);
} catch (RouteNotFoundException $e) {
if ($this->logger) {
$this->logger->info($e->getMessage());
}
}
}
throw new RouteNotFoundException(sprintf('None of the chained routers were able to generate route "%s".', $name));
}
/**
* {@inheritdoc}
*/
public function setContext(RequestContext $context)
{
foreach ($this->all() as $router) {
if ($router instanceof RequestContextAwareInterface) {
$router->setContext($context);
}
}
$this->context = $context;
}
/**
* {@inheritdoc}
*
* check for each contained router if it can warmup
*/
public function warmUp($cacheDir)
{
foreach ($this->all() as $router) {
if ($router instanceof WarmableInterface) {
$router->warmUp($cacheDir);
}
}
}
/**
* {@inheritdoc}
*/
public function getRouteCollection()
{
if (!$this->routeCollection instanceof RouteCollection) {
$this->routeCollection = new RouteCollection();
foreach ($this->all() as $router) {
$this->routeCollection->addCollection($router->getRouteCollection());
}
}
return $this->routeCollection;
}
}
<?php
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\RouterInterface;
/**
* Use this interface on custom routers that can handle non-string route
* "names".
*/
interface ChainedRouterInterface extends RouterInterface
{
/**
* Whether the router supports the thing in $name to generate a route.
*
* This check does not need to look if the specific instance can be
* resolved to a route, only whether the router can generate routes from
* objects of this class.
* @param mixed $name The route name or route object
*
* @return bool
*/
public function supports($name);
}
\ No newline at end of file
<?php
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\Route as SymfonyRoute;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Cmf\Component\Routing\RouteProviderInterface;
/**
* A generator that tries to generate routes from object, route names or
* content objects or names.
*
* @author Philippo de Santis
* @author David Buchmann
* @author Uwe Jäger
*/
class ContentAwareGenerator extends ProviderBasedGenerator
{
/**
* The content repository used to find content by it's id
* This can be used to specify a parameter content_id when generating urls
*
* This is optional and might not be initialized.
*
* @var ContentRepositoryInterface
*/
protected $contentRepository;
/**
* Set an optional content repository to find content by ids
*
* @param ContentRepositoryInterface $contentRepository
*/
public function setContentRepository(ContentRepositoryInterface $contentRepository)
{
$this->contentRepository = $contentRepository;
}
/**
* {@inheritDoc}
*
* @param string $name ignored
* @param array $parameters must either contain the field 'route' with a
* RouteObjectInterface or the field 'content' with the document
* instance to get the route for (implementing RouteAwareInterface)
*
* @throws RouteNotFoundException If there is no such route in the database
*/
public function generate($name, $parameters = array(), $absolute = false)
{
if ($name instanceof SymfonyRoute) {
$route = $this->getBestLocaleRoute($name, $parameters);
} elseif (is_string($name) && $name) {
$route = $this->getRouteByName($name, $parameters);
} else {
$route = $this->getRouteByContent($name, $parameters);
}
if (! $route instanceof SymfonyRoute) {
$hint = is_object($route) ? get_class($route) : gettype($route);
throw new RouteNotFoundException('Route of this document is not an instance of Symfony\Component\Routing\Route but: '.$hint);
}
return parent::generate($route, $parameters, $absolute);
}
/**
* Get the route by a string name
*
* @param string $route
* @param array $parameters
*
* @return SymfonyRoute
*
* @throws RouteNotFoundException if there is no route found for the provided name
*/
protected function getRouteByName($name, array $parameters)
{
$route = $this->provider->getRouteByName($name, $parameters);
if (empty($route)) {
throw new RouteNotFoundException('No route found for name: ' . $name);
}
return $this->getBestLocaleRoute($route, $parameters);
}
/**
* Determine if there is a route with matching locale associated with the
* given route via associated content.
*
* @param SymfonyRoute $route
* @param array $parameters
*
* @return SymfonyRoute either the passed route or an alternative with better locale
*/
protected function getBestLocaleRoute(SymfonyRoute $route, $parameters)
{
if (! $route instanceof RouteObjectInterface) {
// this route has no content, we can't get the alternatives
return $route;
}
$locale = $this->getLocale($parameters);
if (! $this->checkLocaleRequirement($route, $locale)) {
$content = $route->getRouteContent();
if ($content instanceof RouteAwareInterface) {
$routes = $content->getRoutes();
$contentRoute = $this->getRouteByLocale($routes, $locale);
if ($contentRoute) {
return $contentRoute;
}
}
}
return $route;
}
/**
* Get the route based on the content field in parameters
*
* Called in generate when there is no route given in the parameters.
*
* If there is more than one route for the content, tries to find the
* first one that matches the _locale (provided in $parameters or otherwise
* defaulting to the request locale).
*
* If none is found, falls back to just return the first route.
*
* @param mixed $name
* @param array $parameters which should contain a content field containing a RouteAwareInterface object
*
* @return SymfonyRoute the route instance
*
* @throws RouteNotFoundException if there is no content field in the
* parameters or its not possible to build a route from that object
*/
protected function getRouteByContent($name, &$parameters)
{
if ($name instanceof RouteAwareInterface) {
$content = $name;
} elseif (isset($parameters['content_id']) && null !== $this->contentRepository) {
$content = $this->contentRepository->findById($parameters['content_id']);
} elseif (isset($parameters['content'])) {
$content = $parameters['content'];
}
unset($parameters['content'], $parameters['content_id']);
if (empty($content)) {
throw new RouteNotFoundException('Neither the route name, nor a parameter "content" or "content_id" could be resolved to an content instance');
}
if (!$content instanceof RouteAwareInterface) {
$hint = is_object($content) ? get_class($content) : gettype($content);
throw new RouteNotFoundException('The content does not implement RouteAwareInterface: ' . $hint);
}
$routes = $content->getRoutes();
if (empty($routes)) {
$hint = method_exists($content, 'getPath') ? $content->getPath() : get_class($content);
throw new RouteNotFoundException('Document has no route: ' . $hint);
}
$route = $this->getRouteByLocale($routes, $this->getLocale($parameters));
if ($route) {
return $route;
}
// if none matched, continue and randomly return the first one
return reset($routes);
}
/**
* @param RouteCollection $routes
* @param string $locale
*
* @return bool|SymfonyRoute false if no route requirement matches the provided locale
*/
protected function getRouteByLocale($routes, $locale)
{
foreach ($routes as $route) {
if (! $route instanceof SymfonyRoute) {
continue;
}
if ($this->checkLocaleRequirement($route, $locale)) {
return $route;
}
}
return false;
}
/**