Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,3 @@
phpunit.xml
composer.lock
/vendor/

View file

@ -0,0 +1 @@
preset: symfony

View file

@ -0,0 +1,46 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
env: SYMFONY_VERSION=2.8.*
matrix:
include:
- php: 5.6
env: DEPS=dev
- php: 5.3
env: COMPOSER_FLAGS="--prefer-lowest"
- php: 5.6
env: SYMFONY_VERSION=2.3.*
- php: 5.6
env: SYMFONY_VERSION=2.7.*
- php: 5.6
env: SYMFONY_VERSION=3.0.*
finish_fast: true
before_install:
- if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "memory_limit = -1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi
- phpenv config-rm xdebug.ini || true
- composer self-update
- if [ "$DEPS" = "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi
- if [ "$SYMFONY_VERSION" != "" ]; then composer require symfony/symfony:${SYMFONY_VERSION} --no-update; fi
install: composer update --prefer-dist $COMPOSER_FLAGS
script: phpunit
notifications:
irc: "irc.freenode.org#symfony-cmf"
email: "symfony-cmf-devs@googlegroups.com"

View file

@ -0,0 +1,66 @@
Changelog
=========
1.4.0
-----
* **2016-02-27**: Added ContentRepositoryEnhancer that can look up a content by
ID from a content repository.
1.4.0-RC1
---------
* **2016-01-09**: When ChainRouter::match is used with a RequestMatcher, the
Request is now properly rebuilt from the RequestContext if that was set on
the ChainRouter, and http://localhost is used otherwise to avoid issues with
paths starting with a double forward slash.
* **2014-09-29**: ChainRouter does not require a RouterInterface, as a
RequestMatcher and UrlGenerator is fine too. Fixed chain router interface to
not force a RouterInterface.
* **2014-09-29**: Deprecated DynamicRouter::match in favor of matchRequest.
1.3.0
-----
Release 1.3.0
1.3.0-RC1
---------
* **2014-08-20**: Added an interface for the ChainRouter
* **2014-06-06**: Updated to PSR-4 autoloading
1.2.0
-----
Release 1.2.0
1.2.0-RC1
---------
* **2013-12-23**: add support for ChainRouter::getRouteCollection()
* **2013-01-07**: Removed the deprecated $parameters argument in
RouteProviderInterface::getRouteByName and getRoutesByNames.
1.1.0
-----
Release 1.1.0
1.1.0-RC1
---------
* **2013-07-31**: DynamicRouter now accepts an EventDispatcher to trigger a
RouteMatchEvent right before the matching starts
* **2013-07-29**: Renamed RouteAwareInterface to RouteReferrersReadInterface
for naming consistency and added RouteReferrersInterface for write access.
* **2013-07-13**: NestedMatcher now expects a FinalMatcherInterface as second
argument of the constructor
1.1.0-alpha1
------------
* **2013-04-30**: Dropped Symfony 2.1 support and got rid of
ConfigurableUrlMatcher class
* **2013-04-05**: [ContentAwareGenerator] Fix locale handling to always respect
locale but never have unnecessary ?locale=

View file

@ -0,0 +1,12 @@
Contributing
------------
Symfony2 CMF is an open source, community-driven project. We follow the same
guidelines as core Symfony2. If you'd like to contribute, please read the
[Contributing Code][1] part of the documentation. If you're submitting a pull
request, please follow the guidelines in the [Submitting a Patch][2] section
and use the [Pull Request Template][3].
[1]: http://symfony.com/doc/current/contributing/code/index.html
[2]: http://symfony.com/doc/current/contributing/code/patches.html#check-list
[3]: http://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request

View file

@ -0,0 +1,154 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Candidates;
use Symfony\Component\HttpFoundation\Request;
/**
* A straightforward strategy that splits the URL on "/".
*
* If locales is set, additionally generates candidates removing the locale if
* it is one of the configured locales, for non-locale specific URLs.
*
* @author David Buchmann <mail@davidbu.ch>
*/
class Candidates implements CandidatesInterface
{
/**
* @var array
*/
protected $locales;
/**
* A limit to apply to the number of candidates generated.
*
* This is to prevent abusive requests with a lot of "/". The limit is per
* batch, that is if a locale matches you could get as many as 2 * $limit
* candidates if the URL has that many slashes.
*
* @var int
*/
protected $limit;
/**
* @param array $locales The locales to support.
* @param int $limit A limit to apply to the candidates generated.
*/
public function __construct(array $locales = array(), $limit = 20)
{
$this->setLocales($locales);
$this->limit = $limit;
}
/**
* Set the locales to support by this strategy.
*
* @param array $locales The locales to support.
*/
public function setLocales(array $locales)
{
$this->locales = $locales;
}
/**
* {@inheritdoc}
*
* Always returns true.
*/
public function isCandidate($name)
{
return true;
}
/**
* {@inheritdoc}
*
* Does nothing.
*/
public function restrictQuery($queryBuilder)
{
}
/**
* {@inheritdoc}
*/
public function getCandidates(Request $request)
{
$url = $request->getPathInfo();
$candidates = $this->getCandidatesFor($url);
$locale = $this->determineLocale($url);
if ($locale) {
$candidates = array_unique(array_merge($candidates, $this->getCandidatesFor(substr($url, strlen($locale) + 1))));
}
return $candidates;
}
/**
* Determine the locale of this URL.
*
* @param string $url The url to determine the locale from.
*
* @return string|bool The locale if $url starts with one of the allowed locales.
*/
protected function determineLocale($url)
{
if (!count($this->locales)) {
return false;
}
$matches = array();
if (preg_match('#('.implode('|', $this->locales).')(/|$)#', $url, $matches)) {
return $matches[1];
}
return false;
}
/**
* Handle a possible format extension and split the $url on "/".
*
* $prefix is prepended to every candidate generated.
*
* @param string $url The URL to split.
* @param string $prefix A prefix to prepend to every pattern.
*
* @return array Paths that could represent routes that match $url and are
* child of $prefix.
*/
protected function getCandidatesFor($url, $prefix = '')
{
$candidates = array();
if ('/' !== $url) {
// handle format extension, like .html or .json
if (preg_match('/(.+)\.[a-z]+$/i', $url, $matches)) {
$candidates[] = $prefix.$url;
$url = $matches[1];
}
$part = $url;
$count = 0;
while (false !== ($pos = strrpos($part, '/'))) {
if (++$count > $this->limit) {
return $candidates;
}
$candidates[] = $prefix.$part;
$part = substr($url, 0, $pos);
}
}
$candidates[] = $prefix ?: '/';
return $candidates;
}
}

View file

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Candidates;
use Symfony\Component\HttpFoundation\Request;
/**
* Candidates is a subsystem useful for the route provider. It separates the
* logic for determining possible static prefixes from the route provider.
*
* @author David Buchmann <mail@davidbu.ch>
*/
interface CandidatesInterface
{
/**
* @param Request $request
*
* @return array a list of paths
*/
public function getCandidates(Request $request);
/**
* Determine if $name is a valid candidate, e.g. in getRouteByName.
*
* @param string $name
*
* @return bool
*/
public function isCandidate($name);
/**
* Provide a best effort query restriction to limit a query to only find
* routes that are supported.
*
* @param object $queryBuilder A query builder suited for the storage backend.
*/
public function restrictQuery($queryBuilder);
}

View file

@ -0,0 +1,272 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class ChainRouteCollection extends RouteCollection
{
/**
* @var RouteCollection[]
*/
private $routeCollections = array();
/**
* @var RouteCollection
*/
private $routeCollection;
public function __clone()
{
foreach ($this->routeCollections as $routeCollection) {
$this->routeCollections[] = clone $routeCollection;
}
}
/**
* Gets the current RouteCollection as an Iterator that includes all routes.
*
* It implements \IteratorAggregate.
*
* @see all()
*
* @return \ArrayIterator An \ArrayIterator object for iterating over routes
*/
public function getIterator()
{
return new \ArrayIterator($this->all());
}
/**
* Gets the number of Routes in this collection.
*
* @return int The number of routes
*/
public function count()
{
$count = 0;
foreach ($this->routeCollections as $routeCollection) {
$count += $routeCollection->count();
}
return $count;
}
/**
* Adds a route.
*
* @param string $name The route name
* @param Route $route A Route instance
*/
public function add($name, Route $route)
{
$this->createInternalCollection();
$this->routeCollection->add($name, $route);
}
/**
* Returns all routes in this collection.
*
* @return Route[] An array of routes
*/
public function all()
{
$routeCollectionAll = new RouteCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollectionAll->addCollection($routeCollection);
}
return $routeCollectionAll->all();
}
/**
* Gets a route by name.
*
* @param string $name The route name
*
* @return Route|null A Route instance or null when not found
*/
public function get($name)
{
foreach ($this->routeCollections as $routeCollection) {
$route = $routeCollection->get($name);
if (null !== $route) {
return $route;
}
}
return;
}
/**
* Removes a route or an array of routes by name from the collection.
*
* @param string|array $name The route name or an array of route names
*/
public function remove($name)
{
foreach ($this->routeCollections as $routeCollection) {
$route = $routeCollection->get($name);
if (null !== $route) {
$routeCollection->remove($name);
}
}
}
/**
* Adds a route collection at the end of the current set by appending all
* routes of the added collection.
*
* @param RouteCollection $collection A RouteCollection instance
*/
public function addCollection(RouteCollection $collection)
{
$this->routeCollections[] = $collection;
}
/**
* Adds a prefix to the path of all child routes.
*
* @param string $prefix An optional prefix to add before each pattern of the route collection
* @param array $defaults An array of default values
* @param array $requirements An array of requirements
*/
public function addPrefix($prefix, array $defaults = array(), array $requirements = array())
{
$this->createInternalCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollection->addPrefix($prefix, $defaults, $requirements);
}
}
/**
* Sets the host pattern on all routes.
*
* @param string $pattern The pattern
* @param array $defaults An array of default values
* @param array $requirements An array of requirements
*/
public function setHost($pattern, array $defaults = array(), array $requirements = array())
{
$this->createInternalCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollection->setHost($pattern, $defaults, $requirements);
}
}
/**
* Adds defaults to all routes.
*
* An existing default value under the same name in a route will be overridden.
*
* @param array $defaults An array of default values
*/
public function addDefaults(array $defaults)
{
$this->createInternalCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollection->addDefaults($defaults);
}
}
/**
* Adds requirements to all routes.
*
* An existing requirement under the same name in a route will be overridden.
*
* @param array $requirements An array of requirements
*/
public function addRequirements(array $requirements)
{
$this->createInternalCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollection->addRequirements($requirements);
}
}
/**
* Adds options to all routes.
*
* An existing option value under the same name in a route will be overridden.
*
* @param array $options An array of options
*/
public function addOptions(array $options)
{
$this->createInternalCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollection->addOptions($options);
}
}
/**
* Sets the schemes (e.g. 'https') all child routes are restricted to.
*
* @param string|array $schemes The scheme or an array of schemes
*/
public function setSchemes($schemes)
{
$this->createInternalCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollection->setSchemes($schemes);
}
}
/**
* Sets the HTTP methods (e.g. 'POST') all child routes are restricted to.
*
* @param string|array $methods The method or an array of methods
*/
public function setMethods($methods)
{
$this->createInternalCollection();
foreach ($this->routeCollections as $routeCollection) {
$routeCollection->setMethods($methods);
}
}
/**
* Returns an array of resources loaded to build this collection.
*
* @return ResourceInterface[] An array of resources
*/
public function getResources()
{
$resources = array();
foreach ($this->routeCollections as $routeCollection) {
$resources = array_merge($resources, $routeCollection->getResources());
}
return array_unique($resources);
}
/**
* Adds a resource for this collection.
*
* @param ResourceInterface $resource A resource instance
*/
public function addResource(ResourceInterface $resource)
{
$this->createInternalCollection();
$this->routeCollection->addResource($resource);
}
private function createInternalCollection()
{
if (!$this->routeCollection instanceof RouteCollection) {
$this->routeCollection = new RouteCollection();
$this->routeCollections[] = $this->routeCollection;
}
}
}

View file

@ -0,0 +1,356 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
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 Psr\Log\LoggerInterface;
/**
* The ChainRouter allows to combine several routers to try in a defined order.
*
* @author Henrik Bjornskov <henrik@bjrnskov.dk>
* @author Magnus Nordlander <magnus@e-butik.se>
*/
class ChainRouter implements ChainRouterInterface, WarmableInterface
{
/**
* @var RequestContext
*/
private $context;
/**
* Array of arrays of routers grouped by priority.
*
* @var array
*/
private $routers = array();
/**
* @var RouterInterface[] Array of routers, sorted by priority
*/
private $sortedRouters;
/**
* @var RouteCollection
*/
private $routeCollection;
/**
* @var null|LoggerInterface
*/
protected $logger;
/**
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* @return RequestContext
*/
public function getContext()
{
return $this->context;
}
/**
* {@inheritdoc}
*/
public function add($router, $priority = 0)
{
if (!$router instanceof RouterInterface
&& !($router instanceof RequestMatcherInterface && $router instanceof UrlGeneratorInterface)
) {
throw new \InvalidArgumentException(sprintf('%s is not a valid router.', get_class($router)));
}
if (empty($this->routers[$priority])) {
$this->routers[$priority] = array();
}
$this->routers[$priority][] = $router;
$this->sortedRouters = array();
}
/**
* {@inheritdoc}
*/
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($pathinfo)
{
return $this->doMatch($pathinfo);
}
/**
* {@inheritdoc}
*
* Loops through all routes and tries to match the passed request.
*/
public function matchRequest(Request $request)
{
return $this->doMatch($request->getPathInfo(), $request);
}
/**
* Loops through all routers and tries to match the passed request or url.
*
* At least the url must be provided, if a request is additionally provided
* the request takes precedence.
*
* @param string $pathinfo
* @param Request $request
*
* @return array An array of parameters
*
* @throws ResourceNotFoundException If no router matched.
*/
private function doMatch($pathinfo, Request $request = null)
{
$methodNotAllowed = null;
$requestForMatching = $request;
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) {
if (empty($requestForMatching)) {
$requestForMatching = $this->rebuildRequest($pathinfo);
}
return $router->matchRequest($requestForMatching);
}
// every router implements the match method
return $router->match($pathinfo);
} catch (ResourceNotFoundException $e) {
if ($this->logger) {
$this->logger->debug('Router '.get_class($router).' was not able to match, message "'.$e->getMessage().'"');
}
// Needs special care
} catch (MethodNotAllowedException $e) {
if ($this->logger) {
$this->logger->debug('Router '.get_class($router).' throws MethodNotAllowedException with message "'.$e->getMessage().'"');
}
$methodNotAllowed = $e;
}
}
$info = $request
? "this request\n$request"
: "url '$pathinfo'";
throw $methodNotAllowed ?: new ResourceNotFoundException("None of the routers in the chain matched $info");
}
/**
* {@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 = UrlGeneratorInterface::ABSOLUTE_PATH)
{
$debug = array();
foreach ($this->all() as $router) {
// if $router does not announce it is capable of handling
// non-string routes and $name is not a string, continue
if ($name && !is_string($name) && !$router instanceof VersatileGeneratorInterface) {
continue;
}
// If $router is versatile and doesn't support this route name, continue
if ($router instanceof VersatileGeneratorInterface && !$router->supports($name)) {
continue;
}
try {
return $router->generate($name, $parameters, $absolute);
} catch (RouteNotFoundException $e) {
$hint = $this->getErrorMessage($name, $router, $parameters);
$debug[] = $hint;
if ($this->logger) {
$this->logger->debug('Router '.get_class($router)." was unable to generate route. Reason: '$hint': ".$e->getMessage());
}
}
}
if ($debug) {
$debug = array_unique($debug);
$info = implode(', ', $debug);
} else {
$info = $this->getErrorMessage($name);
}
throw new RouteNotFoundException(sprintf('None of the chained routers were able to generate route: %s', $info));
}
/**
* Rebuild the request object from a URL with the help of the RequestContext.
*
* If the request context is not set, this simply returns the request object built from $uri.
*
* @param string $pathinfo
*
* @return Request
*/
private function rebuildRequest($pathinfo)
{
if (!$this->context) {
return Request::create('http://localhost'.$pathinfo);
}
$uri = $pathinfo;
$server = array();
if ($this->context->getBaseUrl()) {
$uri = $this->context->getBaseUrl().$pathinfo;
$server['SCRIPT_FILENAME'] = $this->context->getBaseUrl();
$server['PHP_SELF'] = $this->context->getBaseUrl();
}
$host = $this->context->getHost() ?: 'localhost';
if ('https' === $this->context->getScheme() && 443 !== $this->context->getHttpsPort()) {
$host .= ':'.$this->context->getHttpsPort();
}
if ('http' === $this->context->getScheme() && 80 !== $this->context->getHttpPort()) {
$host .= ':'.$this->context->getHttpPort();
}
$uri = $this->context->getScheme().'://'.$host.$uri.'?'.$this->context->getQueryString();
return Request::create($uri, $this->context->getMethod(), $this->context->getParameters(), array(), array(), $server);
}
private function getErrorMessage($name, $router = null, $parameters = null)
{
if ($router instanceof VersatileGeneratorInterface) {
$displayName = $router->getRouteDebugMessage($name, $parameters);
} elseif (is_object($name)) {
$displayName = method_exists($name, '__toString')
? (string) $name
: get_class($name)
;
} else {
$displayName = (string) $name;
}
return "Route '$displayName' not found";
}
/**
* {@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 ChainRouteCollection();
foreach ($this->all() as $router) {
$this->routeCollection->addCollection($router->getRouteCollection());
}
}
return $this->routeCollection;
}
/**
* Identify if any routers have been added into the chain yet.
*
* @return bool
*/
public function hasRouters()
{
return !empty($this->routers);
}
}

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
/**
* Interface for a router that proxies routing to other routers.
*
* @author Daniel Wehner <dawehner@googlemail.com>
*/
interface ChainRouterInterface extends RouterInterface, RequestMatcherInterface
{
/**
* Add a Router to the index.
*
* @param RouterInterface $router The router instance. Instead of RouterInterface, may also
* be RequestMatcherInterface and UrlGeneratorInterface.
* @param int $priority The priority
*/
public function add($router, $priority = 0);
/**
* Sorts the routers and flattens them.
*
* @return RouterInterface[] or RequestMatcherInterface and UrlGeneratorInterface.
*/
public function all();
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\RouterInterface;
/**
* Interface to combine the VersatileGeneratorInterface with the RouterInterface.
*/
interface ChainedRouterInterface extends RouterInterface, VersatileGeneratorInterface
{
}

View file

@ -0,0 +1,321 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Route as SymfonyRoute;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\RouteCollection;
/**
* 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 locale to use when neither the parameters nor the request context
* indicate the locale to use.
*
* @var string
*/
protected $defaultLocale = null;
/**
* 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_id'
* with the id of a document implementing
* RouteReferrersReadInterface.
*
* @throws RouteNotFoundException If there is no such route in the database
*/
public function generate($name, $parameters = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
{
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);
}
$this->unsetLocaleIfNotNeeded($route, $parameters);
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);
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->getContent();
if ($content instanceof RouteReferrersReadInterface) {
$routes = $content->getRoutes();
$contentRoute = $this->getRouteByLocale($routes, $locale);
if ($contentRoute) {
return $contentRoute;
}
}
}
return $route;
}
/**
* Get the route based on the $name that is an object implementing
* RouteReferrersReadInterface or a content found in the content repository
* with the content_id specified in parameters that is an instance of
* RouteReferrersReadInterface.
*
* 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 no route with matching locale is found, falls back to just return the
* first route.
*
* @param mixed $name
* @param array $parameters which should contain a content field containing
* a RouteReferrersReadInterface object
*
* @return SymfonyRoute the route instance
*
* @throws RouteNotFoundException if no route can be determined
*/
protected function getRouteByContent($name, &$parameters)
{
if ($name instanceof RouteReferrersReadInterface) {
$content = $name;
} elseif (isset($parameters['content_id'])
&& null !== $this->contentRepository
) {
$content = $this->contentRepository->findById($parameters['content_id']);
if (empty($content)) {
throw new RouteNotFoundException('The content repository found nothing at id '.$parameters['content_id']);
}
if (!$content instanceof RouteReferrersReadInterface) {
throw new RouteNotFoundException('Content repository did not return a RouteReferrersReadInterface instance for id '.$parameters['content_id']);
}
} else {
$hint = is_object($name) ? get_class($name) : gettype($name);
throw new RouteNotFoundException("The route name argument '$hint' is not RouteReferrersReadInterface instance and there is no 'content_id' parameter");
}
$routes = $content->getRoutes();
if (empty($routes)) {
$hint = ($this->contentRepository && $this->contentRepository->getContentId($content))
? $this->contentRepository->getContentId($content)
: get_class($content);
throw new RouteNotFoundException('Content document has no route: '.$hint);
}
unset($parameters['content_id']);
$route = $this->getRouteByLocale($routes, $this->getLocale($parameters));
if ($route) {
return $route;
}
// if none matched, randomly return the first one
if ($routes instanceof Collection) {
return $routes->first();
}
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;
}
/**
* @param SymfonyRoute $route
* @param string $locale
*
* @return bool true if there is either no $locale, no _locale requirement
* on the route or if the requirement and the passed $locale
* match.
*/
private function checkLocaleRequirement(SymfonyRoute $route, $locale)
{
return empty($locale)
|| !$route->getRequirement('_locale')
|| preg_match('/'.$route->getRequirement('_locale').'/', $locale)
;
}
/**
* Determine the locale to be used with this request.
*
* @param array $parameters the parameters determined by the route
*
* @return string the locale following of the parameters or any other
* information the router has available. defaultLocale if no
* other locale can be determined.
*/
protected function getLocale($parameters)
{
if (isset($parameters['_locale'])) {
return $parameters['_locale'];
}
if ($this->getContext()->hasParameter('_locale')) {
return $this->getContext()->getParameter('_locale');
}
return $this->defaultLocale;
}
/**
* Overwrite the locale to be used by default if there is neither one in
* the parameters when building the route nor a request available (i.e. CLI).
*
* @param string $locale
*/
public function setDefaultLocale($locale)
{
$this->defaultLocale = $locale;
}
/**
* We additionally support empty name and data in parameters and RouteAware content.
*/
public function supports($name)
{
return !$name || parent::supports($name) || $name instanceof RouteReferrersReadInterface;
}
/**
* {@inheritdoc}
*/
public function getRouteDebugMessage($name, array $parameters = array())
{
if (empty($name) && isset($parameters['content_id'])) {
return 'Content id '.$parameters['content_id'];
}
if ($name instanceof RouteReferrersReadInterface) {
return 'Route aware content '.parent::getRouteDebugMessage($name, $parameters);
}
return parent::getRouteDebugMessage($name, $parameters);
}
/**
* If the _locale parameter is allowed by the requirements of the route
* and it is the default locale, remove it from the parameters so that we
* do not get an unneeded ?_locale= query string.
*
* @param SymfonyRoute $route The route being generated.
* @param array $parameters The parameters used, will be modified to
* remove the _locale field if needed.
*/
protected function unsetLocaleIfNotNeeded(SymfonyRoute $route, array &$parameters)
{
$locale = $this->getLocale($parameters);
if (null !== $locale) {
if (preg_match('/'.$route->getRequirement('_locale').'/', $locale)
&& $locale == $route->getDefault('_locale')
) {
$compiledRoute = $route->compile();
if (!in_array('_locale', $compiledRoute->getVariables())) {
unset($parameters['_locale']);
}
}
}
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
/**
* Interface used by the DynamicRouter to retrieve content by it's id when
* generating routes from content-id.
*
* This can be easily implemented using i.e. the Doctrine PHPCR-ODM
* DocumentManager.
*
* @author Uwe Jäger
*/
interface ContentRepositoryInterface
{
/**
* Return a content object by it's id or null if there is none.
*
* If the returned content implements RouteReferrersReadInterface, it will
* be used to get the route from it to generate an URL.
*
* @param string $id id of the content object
*
* @return object A content that matches this id.
*/
public function findById($id);
/**
* Return the content identifier for the provided content object for
* debugging purposes.
*
* @param object $content A content instance
*
* @return string|null $id id of the content object or null if unable to determine an id
*/
public function getContentId($content);
}

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* This compiler pass adds additional route enhancers
* to the dynamic router.
*
* @author Daniel Leech <dan.t.leech@gmail.com>
* @author Nathaniel Catchpole (catch)
*/
class RegisterRouteEnhancersPass implements CompilerPassInterface
{
/**
* @var string
*/
protected $dynamicRouterService;
protected $enhancerTag;
public function __construct($dynamicRouterService = 'cmf_routing.dynamic_router', $enhancerTag = 'dynamic_router_route_enhancer')
{
$this->dynamicRouterService = $dynamicRouterService;
$this->enhancerTag = $enhancerTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dynamicRouterService)) {
return;
}
$router = $container->getDefinition($this->dynamicRouterService);
foreach ($container->findTaggedServiceIds($this->enhancerTag) as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$router->addMethodCall('addRouteEnhancer', array(new Reference($id), $priority));
}
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Compiler pass to register routers to the ChainRouter.
*
* @author Wouter J <waldio.webdesign@gmail.com>
* @author Henrik Bjornskov <henrik@bjrnskov.dk>
* @author Magnus Nordlander <magnus@e-butik.se>
*/
class RegisterRoutersPass implements CompilerPassInterface
{
/**
* @var string
*/
protected $chainRouterService;
protected $routerTag;
public function __construct($chainRouterService = 'cmf_routing.router', $routerTag = 'router')
{
$this->chainRouterService = $chainRouterService;
$this->routerTag = $routerTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->chainRouterService)) {
return;
}
$definition = $container->getDefinition($this->chainRouterService);
foreach ($container->findTaggedServiceIds($this->routerTag) as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$definition->addMethodCall('add', array(new Reference($id), $priority));
}
}
}

View file

@ -0,0 +1,387 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Cmf\Component\Routing\Event\Events;
use Symfony\Cmf\Component\Routing\Event\RouterMatchEvent;
use Symfony\Cmf\Component\Routing\Event\RouterGenerateEvent;
/**
* A flexible router accepting matcher and generator through injection and
* using the RouteEnhancer concept to generate additional data on the routes.
*
* @author Larry Garfield
* @author David Buchmann
*/
class DynamicRouter implements RouterInterface, RequestMatcherInterface, ChainedRouterInterface
{
/**
* @var RequestMatcherInterface|UrlMatcherInterface
*/
protected $matcher;
/**
* @var UrlGeneratorInterface
*/
protected $generator;
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* @var RouteEnhancerInterface[]
*/
protected $enhancers = array();
/**
* Cached sorted list of enhancers.
*
* @var RouteEnhancerInterface[]
*/
protected $sortedEnhancers = array();
/**
* The regexp pattern that needs to be matched before a dynamic lookup is
* made.
*
* @var string
*/
protected $uriFilterRegexp;
/**
* @var RequestContext
*/
protected $context;
/**
* @var RouteCollection
*/
private $routeCollection;
/**
* @param RequestContext $context
* @param RequestMatcherInterface|UrlMatcherInterface $matcher
* @param UrlGeneratorInterface $generator
* @param string $uriFilterRegexp
* @param EventDispatcherInterface|null $eventDispatcher
* @param RouteProviderInterface $provider
*/
public function __construct(RequestContext $context,
$matcher,
UrlGeneratorInterface $generator,
$uriFilterRegexp = '',
EventDispatcherInterface $eventDispatcher = null,
RouteProviderInterface $provider = null
) {
$this->context = $context;
if (!$matcher instanceof RequestMatcherInterface && !$matcher instanceof UrlMatcherInterface) {
throw new \InvalidArgumentException('Matcher must implement either Symfony\Component\Routing\Matcher\RequestMatcherInterface or Symfony\Component\Routing\Matcher\UrlMatcherInterface');
}
$this->matcher = $matcher;
$this->generator = $generator;
$this->eventDispatcher = $eventDispatcher;
$this->uriFilterRegexp = $uriFilterRegexp;
$this->provider = $provider;
$this->generator->setContext($context);
}
/**
* {@inheritdoc}
*/
public function getRouteCollection()
{
if (!$this->routeCollection instanceof RouteCollection) {
$this->routeCollection = $this->provider
? new LazyRouteCollection($this->provider) : new RouteCollection();
}
return $this->routeCollection;
}
/**
* @return RequestMatcherInterface|UrlMatcherInterface
*/
public function getMatcher()
{
/* we may not set the context in DynamicRouter::setContext as this
* would lead to symfony cache warmup problems.
* a request matcher does not need the request context separately as it
* can get it from the request.
*/
if ($this->matcher instanceof RequestContextAwareInterface) {
$this->matcher->setContext($this->getContext());
}
return $this->matcher;
}
/**
* @return UrlGeneratorInterface
*/
public function getGenerator()
{
$this->generator->setContext($this->getContext());
return $this->generator;
}
/**
* Generates a URL from the given parameters.
*
* If the generator is not able to generate the url, it must throw the
* RouteNotFoundException as documented below.
*
* @param string|Route $name The name of the route or the Route instance
* @param mixed $parameters An array of parameters
* @param bool|string $referenceType The type of reference to be generated (one of the constants in UrlGeneratorInterface)
*
* @return string The generated URL
*
* @throws RouteNotFoundException if route doesn't exist
*
* @api
*/
public function generate($name, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
if ($this->eventDispatcher) {
$event = new RouterGenerateEvent($name, $parameters, $referenceType);
$this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_GENERATE, $event);
$name = $event->getRoute();
$parameters = $event->getParameters();
$referenceType = $event->getReferenceType();
}
return $this->getGenerator()->generate($name, $parameters, $referenceType);
}
/**
* Delegate to our generator.
*
* {@inheritdoc}
*/
public function supports($name)
{
if ($this->generator instanceof VersatileGeneratorInterface) {
return $this->generator->supports($name);
}
return is_string($name);
}
/**
* Tries to match a URL path with a set of routes.
*
* If the matcher can not find information, it must throw one of the
* exceptions documented below.
*
* @param string $pathinfo The path info to be parsed (raw format, i.e. not
* urldecoded)
*
* @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
*
* @deprecated Use matchRequest exclusively to avoid problems. This method will be removed in version 2.0
*
* @api
*/
public function match($pathinfo)
{
@trigger_error(__METHOD__.'() is deprecated since version 1.3 and will be removed in 2.0. Use matchRequest() instead.', E_USER_DEPRECATED);
$request = Request::create($pathinfo);
if ($this->eventDispatcher) {
$event = new RouterMatchEvent();
$this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH, $event);
}
if (!empty($this->uriFilterRegexp) && !preg_match($this->uriFilterRegexp, $pathinfo)) {
throw new ResourceNotFoundException("$pathinfo does not match the '{$this->uriFilterRegexp}' pattern");
}
$matcher = $this->getMatcher();
if (!$matcher instanceof UrlMatcherInterface) {
throw new \InvalidArgumentException('Wrong matcher type, you need to call matchRequest');
}
$defaults = $matcher->match($pathinfo);
return $this->applyRouteEnhancers($defaults, $request);
}
/**
* Tries to match a request with a set of routes and returns the array of
* information for that route.
*
* If the matcher can not find information, it must throw one of the
* exceptions documented below.
*
* @param Request $request The request to match
*
* @return array An array of parameters
*
* @throws ResourceNotFoundException If no matching resource could be found
* @throws MethodNotAllowedException If a matching resource was found but
* the request method is not allowed
*/
public function matchRequest(Request $request)
{
if ($this->eventDispatcher) {
$event = new RouterMatchEvent($request);
$this->eventDispatcher->dispatch(Events::PRE_DYNAMIC_MATCH_REQUEST, $event);
}
if (!empty($this->uriFilterRegexp)
&& !preg_match($this->uriFilterRegexp, $request->getPathInfo())
) {
throw new ResourceNotFoundException("{$request->getPathInfo()} does not match the '{$this->uriFilterRegexp}' pattern");
}
$matcher = $this->getMatcher();
if ($matcher instanceof UrlMatcherInterface) {
$defaults = $matcher->match($request->getPathInfo());
} else {
$defaults = $matcher->matchRequest($request);
}
return $this->applyRouteEnhancers($defaults, $request);
}
/**
* Apply the route enhancers to the defaults, according to priorities.
*
* @param array $defaults
* @param Request $request
*
* @return array
*/
protected function applyRouteEnhancers($defaults, Request $request)
{
foreach ($this->getRouteEnhancers() as $enhancer) {
$defaults = $enhancer->enhance($defaults, $request);
}
return $defaults;
}
/**
* Add route enhancers to the router to let them generate information on
* matched routes.
*
* The order of the enhancers is determined by the priority, the higher the
* value, the earlier the enhancer is run.
*
* @param RouteEnhancerInterface $enhancer
* @param int $priority
*/
public function addRouteEnhancer(RouteEnhancerInterface $enhancer, $priority = 0)
{
if (empty($this->enhancers[$priority])) {
$this->enhancers[$priority] = array();
}
$this->enhancers[$priority][] = $enhancer;
$this->sortedEnhancers = array();
return $this;
}
/**
* Sorts the enhancers and flattens them.
*
* @return RouteEnhancerInterface[] the enhancers ordered by priority
*/
public function getRouteEnhancers()
{
if (empty($this->sortedEnhancers)) {
$this->sortedEnhancers = $this->sortRouteEnhancers();
}
return $this->sortedEnhancers;
}
/**
* Sort enhancers by priority.
*
* The highest priority number is the highest priority (reverse sorting).
*
* @return RouteEnhancerInterface[] the sorted enhancers
*/
protected function sortRouteEnhancers()
{
$sortedEnhancers = array();
krsort($this->enhancers);
foreach ($this->enhancers as $enhancers) {
$sortedEnhancers = array_merge($sortedEnhancers, $enhancers);
}
return $sortedEnhancers;
}
/**
* 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
*
* @api
*/
public function getContext()
{
return $this->context;
}
/**
* {@inheritdoc}
*
* Forwards to the generator.
*/
public function getRouteDebugMessage($name, array $parameters = array())
{
if ($this->generator instanceof VersatileGeneratorInterface) {
return $this->generator->getRouteDebugMessage($name, $parameters);
}
return "Route '$name' not found";
}
}

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Enhancer;
use Symfony\Cmf\Component\Routing\ContentRepositoryInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* This enhancer uses a ContentRepositoryInterface to load a content if $target
* is empty.
*
* $source specifies the field that contains the ID to load, $target specifies
* the field where to put the content returned by the repository.
*
* @author Samusev Andrey
*/
class ContentRepositoryEnhancer implements RouteEnhancerInterface
{
/**
* @var ContentRepositoryInterface
*/
private $contentRepository;
/**
* @var string
*/
private $target;
/**
* @var string
*/
private $source;
/**
* @param ContentRepositoryInterface $contentRepository repository to search for the content
* @param string $target the field name to set content
* @param string $source the field name of the request parameter that contains the id
*/
public function __construct(
ContentRepositoryInterface $contentRepository,
$target = RouteObjectInterface::CONTENT_OBJECT,
$source = RouteObjectInterface::CONTENT_ID
) {
$this->contentRepository = $contentRepository;
$this->target = $target;
$this->source = $source;
}
/**
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request)
{
if (!isset($defaults[$this->target]) && isset($defaults[$this->source])) {
$defaults[$this->target] = $this->contentRepository->findById($defaults[$this->source]);
}
return $defaults;
}
}

View file

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Enhancer;
use Symfony\Component\HttpFoundation\Request;
/**
* This enhancer sets a field if not yet existing from the class of an object
* in another field.
*
* The comparison is done with instanceof to support proxy classes and such.
*
* Only works with RouteObjectInterface routes that can return a referenced
* content.
*
* @author David Buchmann
*/
class FieldByClassEnhancer implements RouteEnhancerInterface
{
/**
* @var string field for the source class
*/
protected $source;
/**
* @var string field to write hashmap lookup result into
*/
protected $target;
/**
* @var array containing the mapping between a class name and the target value
*/
protected $map;
/**
* @param string $source the field name of the class
* @param string $target the field name to set from the map
* @param array $map the map of class names to field values
*/
public function __construct($source, $target, $map)
{
$this->source = $source;
$this->target = $target;
$this->map = $map;
}
/**
* If the source field is instance of one of the entries in the map,
* target is set to the value of that map entry.
*
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request)
{
if (isset($defaults[$this->target])) {
// no need to do anything
return $defaults;
}
if (!isset($defaults[$this->source])) {
return $defaults;
}
// we need to loop over the array and do instanceof in case the content
// class extends the specified class
// i.e. phpcr-odm generates proxy class for the content.
foreach ($this->map as $class => $value) {
if ($defaults[$this->source] instanceof $class) {
// found a matching entry in the map
$defaults[$this->target] = $value;
return $defaults;
}
}
return $defaults;
}
}

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Enhancer;
use Symfony\Component\HttpFoundation\Request;
/**
* This enhancer can fill one field with the result of a hashmap lookup of
* another field. If the target field is already set, it does nothing.
*
* @author David Buchmann
*/
class FieldMapEnhancer implements RouteEnhancerInterface
{
/**
* @var string field for key in hashmap lookup
*/
protected $source;
/**
* @var string field to write hashmap lookup result into
*/
protected $target;
/**
* @var array containing the mapping between the source field value and target field value
*/
protected $hashmap;
/**
* @param string $source the field to read
* @param string $target the field to write the result of the lookup into
* @param array $hashmap for looking up value from source and get value for target
*/
public function __construct($source, $target, array $hashmap)
{
$this->source = $source;
$this->target = $target;
$this->hashmap = $hashmap;
}
/**
* If the target field is not set but the source field is, map the field.
*
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request)
{
if (isset($defaults[$this->target])) {
return $defaults;
}
if (!isset($defaults[$this->source])) {
return $defaults;
}
if (!isset($this->hashmap[$defaults[$this->source]])) {
return $defaults;
}
$defaults[$this->target] = $this->hashmap[$defaults[$this->source]];
return $defaults;
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Enhancer;
use Symfony\Component\HttpFoundation\Request;
/**
* This enhancer sets a field to a fixed value. You can specify a source field
* name to only set the target field if the source is present.
*
* @author David Buchmann
*/
class FieldPresenceEnhancer implements RouteEnhancerInterface
{
/**
* Field name for the source field that must exist. If null, the target
* field is always set if not already present.
*
* @var string|null
*/
protected $source;
/**
* Field name to write the value into.
*
* @var string
*/
protected $target;
/**
* Value to set the target field to.
*
* @var string
*/
private $value;
/**
* @param null|string $source the field name of the class, null to disable the check
* @param string $target the field name to set from the map
* @param string $value value to set target field to if source field exists
*/
public function __construct($source, $target, $value)
{
$this->source = $source;
$this->target = $target;
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request)
{
if (isset($defaults[$this->target])) {
// no need to do anything
return $defaults;
}
if (null !== $this->source && !isset($defaults[$this->source])) {
return $defaults;
}
$defaults[$this->target] = $this->value;
return $defaults;
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Enhancer;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
/**
* This enhancer sets the content to target field if the route provides content.
*
* Only works with RouteObjectInterface routes that can return a referenced
* content.
*
* @author David Buchmann
*/
class RouteContentEnhancer implements RouteEnhancerInterface
{
/**
* @var string field for the route class
*/
protected $routefield;
/**
* @var string field to write hashmap lookup result into
*/
protected $target;
/**
* @param string $routefield the field name of the route class
* @param string $target the field name to set from the map
* @param array $hashmap the map of class names to field values
*/
public function __construct($routefield, $target)
{
$this->routefield = $routefield;
$this->target = $target;
}
/**
* If the route has a non-null content and if that content class is in the
* injected map, returns that controller.
*
* {@inheritdoc}
*/
public function enhance(array $defaults, Request $request)
{
if (isset($defaults[$this->target])) {
// no need to do anything
return $defaults;
}
if (!isset($defaults[$this->routefield])
|| !$defaults[$this->routefield] instanceof RouteObjectInterface
) {
// we can't determine the content
return $defaults;
}
/** @var $route RouteObjectInterface */
$route = $defaults[$this->routefield];
$content = $route->getContent();
if (!$content) {
// we have no content
return $defaults;
}
$defaults[$this->target] = $content;
return $defaults;
}
}

View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Enhancer;
use Symfony\Component\HttpFoundation\Request;
/**
* A route enhancer can change the values in the route data arrays.
*
* This is useful to provide information to the rest of the routing system
* that can be inferred from other parameters rather than hardcode that
* information in every route.
*
* @author David Buchmann
*/
interface RouteEnhancerInterface
{
/**
* Update the defaults based on its own data and the request.
*
* @param array $defaults the getRouteDefaults array.
* @param Request $request the Request instance.
*
* @return array the modified defaults. Each enhancer MUST return the
* $defaults but may add or remove values.
*/
public function enhance(array $defaults, Request $request);
}

View file

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Event;
final class Events
{
/**
* Fired before a path is matched in \Symfony\Cmf\Component\Routing\DynamicRouter#match.
*
* The event object is RouterMatchEvent.
*/
const PRE_DYNAMIC_MATCH = 'cmf_routing.pre_dynamic_match';
/**
* Fired before a Request is matched in \Symfony\Cmf\Component\Routing\DynamicRouter#match.
*
* The event object is RouterMatchEvent.
*/
const PRE_DYNAMIC_MATCH_REQUEST = 'cmf_routing.pre_dynamic_match_request';
/**
* Fired before a route is generated in \Symfony\Cmf\Component\Routing\DynamicRouter#generate.
*
* The event object is RouterGenerateEvent.
*/
const PRE_DYNAMIC_GENERATE = 'cmf_routing.pre_dynamic_generate';
}

View file

@ -0,0 +1,142 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Event;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Routing\Route;
/**
* Event fired before the dynamic router generates a url for a route.
*
* The name, parameters and absolute properties have the semantics of
* UrlGeneratorInterface::generate()
*
* @author Ben Glassman
*
* @see \Symfony\Component\Routing\Generator\UrlGeneratorInterface::generate()
*/
class RouterGenerateEvent extends Event
{
/**
* The name of the route or the Route instance to generate.
*
* @var string|Route
*/
private $route;
/**
* The parameters to use when generating the url.
*
* @var array
*/
private $parameters;
/**
* The type of reference to be generated (one of the constants in UrlGeneratorInterface).
*
* @var bool|string
*/
private $referenceType;
/**
* @param string|Route $route The route name or object
* @param array $parameters The parameters to use
* @param bool|string $referenceType The type of reference to be generated
*/
public function __construct($route, $parameters, $referenceType)
{
$this->route = $route;
$this->parameters = $parameters;
$this->referenceType = $referenceType;
}
/**
* Get route name or object.
*
* @return string|Route
*/
public function getRoute()
{
return $this->route;
}
/**
* Set route name or object.
*
* @param string|Route $route
*/
public function setRoute($route)
{
$this->route = $route;
}
/**
* Get route parameters.
*
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Set the route parameters.
*
* @param array $parameters
*/
public function setParameters(array $parameters)
{
$this->parameters = $parameters;
}
/**
* Set a route parameter.
*
* @param string $key
* @param mixed $value
*/
public function setParameter($key, $value)
{
$this->parameters[$key] = $value;
}
/**
* Remove a route parameter by key.
*
* @param string $key
*/
public function removeParameter($key)
{
unset($this->parameters[$key]);
}
/**
* The type of reference to be generated (one of the constants in UrlGeneratorInterface).
*
* @return bool|string
*/
public function getReferenceType()
{
return $this->referenceType;
}
/**
* The type of reference to be generated (one of the constants in UrlGeneratorInterface).
*
* @param bool|string $referenceType
*/
public function setReferenceType($referenceType)
{
$this->referenceType = $referenceType;
}
}

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\Event;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpFoundation\Request;
class RouterMatchEvent extends Event
{
/**
* @var Request
*/
protected $request;
/**
* @param Request $request
*/
public function __construct(Request $request = null)
{
$this->request = $request;
}
/**
* @return Request | null
*/
public function getRequest()
{
return $this->request;
}
}

23
web/vendor/symfony-cmf/routing/LICENSE vendored Normal file
View file

@ -0,0 +1,23 @@
Routing
The MIT License
Copyright (c) 2011-2015 Symfony CMF
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,75 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
class LazyRouteCollection extends RouteCollection
{
/**
* The route provider for this generator.
*
* @var RouteProviderInterface
*/
protected $provider;
public function __construct(RouteProviderInterface $provider)
{
$this->provider = $provider;
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new \ArrayIterator($this->all());
}
/**
* Gets the number of Routes in this collection.
*
* @return int The number of routes
*/
public function count()
{
return count($this->all());
}
/**
* Returns all routes in this collection.
*
* @return Route[] An array of routes
*/
public function all()
{
return $this->provider->getRoutesByNames(null);
}
/**
* Gets a route by name.
*
* @param string $name The route name
*
* @return Route|null A Route instance or null when not found
*/
public function get($name)
{
try {
return $this->provider->getRouteByName($name);
} catch (RouteNotFoundException $e) {
return;
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\NestedMatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
* A FinalMatcher returns only one route from a collection of candidate routes.
*
* @author Larry Garfield
* @author David Buchmann
*/
interface FinalMatcherInterface
{
/**
* Matches a request against a route collection and returns exactly one result.
*
* @param RouteCollection $collection The collection against which to match.
* @param Request $request The request to match.
*
* @return array An array of parameters
*
* @throws ResourceNotFoundException if none of the routes in $collection
* matches $request
*/
public function finalMatch(RouteCollection $collection, Request $request);
}

View file

@ -0,0 +1,189 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\NestedMatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Cmf\Component\Routing\RouteProviderInterface;
/**
* A more flexible approach to matching. The route collection to match against
* can be dynamically determined based on the request and users can inject
* their own filters or use a custom final matching strategy.
*
* The nested matcher splits matching into three configurable steps:
*
* 1) Get potential matches from a RouteProviderInterface
* 2) Apply any RouteFilterInterface to reduce the route collection
* 3) Have FinalMatcherInterface select the best match of the remaining routes
*
* @author Larry Garfield
* @author David Buchmann
*/
class NestedMatcher implements RequestMatcherInterface
{
/**
* The route provider responsible for the first-pass match.
*
* @var RouteProviderInterface
*/
protected $routeProvider;
/**
* The final matcher.
*
* @var FinalMatcherInterface
*/
protected $finalMatcher;
/**
* An array of RouteFilterInterface objects.
*
* @var RouteFilterInterface[]
*/
protected $filters = array();
/**
* Array of RouteFilterInterface objects, sorted.
*
* @var RouteFilterInterface[]
*/
protected $sortedFilters = array();
/**
* Constructs a new NestedMatcher.
*
* @param RouteProviderInterface $provider The route provider this matcher
* should use
* @param FinalMatcherInterface $final The Final Matcher to match the
* routes
*/
public function __construct(
RouteProviderInterface $provider = null,
FinalMatcherInterface $final = null
) {
if (null !== $provider) {
$this->setRouteProvider($provider);
}
if (null !== $final) {
$this->setFinalMatcher($final);
}
}
/**
* Sets the route provider for the matching plan.
*
* @param RouteProviderInterface $provider A source of routes.
*
* @return NestedMatcher this object to have a fluent interface
*/
public function setRouteProvider(RouteProviderInterface $provider)
{
$this->routeProvider = $provider;
return $this;
}
/**
* Adds a partial matcher to the matching plan.
*
* Partial matchers will be run in the order in which they are added.
*
* @param RouteFilterInterface $filter
* @param int $priority (optional) The priority of the
* filter. Higher number filters will
* be used first. Defaults to 0.
*
* @return NestedMatcher this object to have a fluent interface
*/
public function addRouteFilter(RouteFilterInterface $filter, $priority = 0)
{
if (empty($this->filters[$priority])) {
$this->filters[$priority] = array();
}
$this->filters[$priority][] = $filter;
$this->sortedFilters = array();
return $this;
}
/**
* Sets the final matcher for the matching plan.
*
* @param FinalMatcherInterface $final The final matcher that will have to
* pick the route that will be used.
*
* @return NestedMatcher this object to have a fluent interface
*/
public function setFinalMatcher(FinalMatcherInterface $final)
{
$this->finalMatcher = $final;
return $this;
}
/**
* {@inheritdoc}
*/
public function matchRequest(Request $request)
{
$collection = $this->routeProvider->getRouteCollectionForRequest($request);
if (!count($collection)) {
throw new ResourceNotFoundException();
}
// Route filters are expected to throw an exception themselves if they
// end up filtering the list down to 0.
foreach ($this->getRouteFilters() as $filter) {
$collection = $filter->filter($collection, $request);
}
$attributes = $this->finalMatcher->finalMatch($collection, $request);
return $attributes;
}
/**
* Sorts the filters and flattens them.
*
* @return RouteFilterInterface[] the filters ordered by priority
*/
public function getRouteFilters()
{
if (empty($this->sortedFilters)) {
$this->sortedFilters = $this->sortFilters();
}
return $this->sortedFilters;
}
/**
* Sort filters by priority.
*
* The highest priority number is the highest priority (reverse sorting).
*
* @return RouteFilterInterface[] the sorted filters
*/
protected function sortFilters()
{
$sortedFilters = array();
krsort($this->filters);
foreach ($this->filters as $filters) {
$sortedFilters = array_merge($sortedFilters, $filters);
}
return $sortedFilters;
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\NestedMatcher;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\HttpFoundation\Request;
/**
* A RouteFilter takes a RouteCollection and returns a filtered subset.
*
* It is not implemented as a filter iterator because we want to allow
* router filters to handle their own empty-case handling, usually by throwing
* an appropriate exception if no routes match the object's rules.
*
* @author Larry Garfield
* @author David Buchmann
*/
interface RouteFilterInterface
{
/**
* Filters the route collection against a request and returns all matching
* routes.
*
* @param RouteCollection $collection The collection against which to match.
* @param Request $request A Request object against which to match.
*
* @return RouteCollection A non-empty RouteCollection of matched routes.
*
* @throws ResourceNotFoundException if none of the routes in $collection
* matches $request. This is a performance
* optimization to not continue the match
* process when a match will no longer be
* possible.
*/
public function filter(RouteCollection $collection, Request $request);
}

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing\NestedMatcher;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Matcher\UrlMatcher as SymfonyUrlMatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
/**
* Extended UrlMatcher to provide an additional interface and enhanced features.
*
* This class requires Symfony 2.2 for a refactoring done to the symfony UrlMatcher
*
* @author Larry Garfield
*/
class UrlMatcher extends SymfonyUrlMatcher implements FinalMatcherInterface
{
/**
* {@inheritdoc}
*/
public function finalMatch(RouteCollection $collection, Request $request)
{
$this->routes = $collection;
$context = new RequestContext();
$context->fromRequest($request);
$this->setContext($context);
return $this->match($request->getPathInfo());
}
/**
* {@inheritdoc}
*/
protected function getAttributes(Route $route, $name, array $attributes)
{
if ($route instanceof RouteObjectInterface && is_string($route->getRouteKey())) {
$name = $route->getRouteKey();
}
$attributes[RouteObjectInterface::ROUTE_NAME] = $name;
$attributes[RouteObjectInterface::ROUTE_OBJECT] = $route;
return $this->mergeDefaults($attributes, $route->getDefaults());
}
}

View file

@ -0,0 +1,126 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
/**
* Provides a route collection which avoids having all routes in memory.
*
* Internally, this does load multiple routes over time using a
* PagedRouteProviderInterface $route_provider.
*/
class PagedRouteCollection implements \Iterator, \Countable
{
/**
* @var PagedRouteProviderInterface
*/
protected $provider;
/**
* Stores the amount of routes which are loaded in parallel and kept in
* memory.
*
* @var int
*/
protected $routesBatchSize;
/**
* Contains the current item the iterator points to.
*
* @var int
*/
protected $current = -1;
/**
* Stores the current loaded routes.
*
* @var \Symfony\Component\Routing\Route[]
*/
protected $currentRoutes;
public function __construct(PagedRouteProviderInterface $pagedRouteProvider, $routesBatchSize = 50)
{
$this->provider = $pagedRouteProvider;
$this->routesBatchSize = $routesBatchSize;
}
/**
* Loads the next routes into the elements array.
*
* @param int $offset The offset used in the db query.
*/
protected function loadNextElements($offset)
{
// If the last batch was smaller than the batch size, this means there
// are no more routes available.
if (isset($this->currentRoutes) && count($this->currentRoutes) < $this->routesBatchSize) {
$this->currentRoutes = array();
} else {
$this->currentRoutes = $this->provider->getRoutesPaged($offset, $this->routesBatchSize);
}
}
/**
* {@inheritdoc}
*/
public function current()
{
return current($this->currentRoutes);
}
/**
* {@inheritdoc}
*/
public function next()
{
$result = next($this->currentRoutes);
if (false === $result) {
$this->loadNextElements($this->current + 1);
}
++$this->current;
}
/**
* {@inheritdoc}
*/
public function key()
{
return key($this->currentRoutes);
}
/**
* {@inheritdoc}
*/
public function valid()
{
return key($this->currentRoutes);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->current = 0;
$this->currentRoutes = null;
$this->loadNextElements($this->current);
}
/**
* Gets the number of Routes in this collection.
*
* @return int The number of routes
*/
public function count()
{
return $this->provider->getRoutesCount();
}
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* This file is part of the Symfony CMF package.
*
* (c) 2011-2014 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
/**
* Interface for a provider which allows to retrieve a limited amount of routes.
*/
interface PagedRouteProviderInterface extends RouteProviderInterface
{
/**
* Find an amount of routes with an offset and possible a limit.
*
* In case you want to iterate over all routes, you want to avoid to load
* all routes at once.
*
* @param int $offset
* The sequence will start with that offset in the list of all routes.
* @param int $length [optional]
* The sequence will have that many routes in it. If no length is
* specified all routes are returned.
*
* @return \Symfony\Component\Routing\Route[]
* Routes keyed by the route name.
*/
public function getRoutesPaged($offset, $length = null);
/**
* Determines the total amount of routes.
*
* @return int
*/
public function getRoutesCount();
}

View file

@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Route as SymfonyRoute;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Psr\Log\LoggerInterface;
/**
* A Generator that uses a RouteProvider rather than a RouteCollection.
*
* @author Larry Garfield
*/
class ProviderBasedGenerator extends UrlGenerator implements VersatileGeneratorInterface
{
/**
* The route provider for this generator.
*
* @var RouteProviderInterface
*/
protected $provider;
/**
* @param RouteProviderInterface $provider
* @param LoggerInterface $logger
*/
public function __construct(RouteProviderInterface $provider, LoggerInterface $logger = null)
{
$this->provider = $provider;
$this->logger = $logger;
$this->context = new RequestContext();
}
/**
* {@inheritdoc}
*/
public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)
{
if ($name instanceof SymfonyRoute) {
$route = $name;
} elseif (null === $route = $this->provider->getRouteByName($name)) {
throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name));
}
// the Route has a cache of its own and is not recompiled as long as it does not get modified
$compiledRoute = $route->compile();
$hostTokens = $compiledRoute->getHostTokens();
$debug_message = $this->getRouteDebugMessage($name);
return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $debug_message, $referenceType, $hostTokens);
}
/**
* Support a route object and any string as route name.
*
* {@inheritdoc}
*/
public function supports($name)
{
return is_string($name) || $name instanceof SymfonyRoute;
}
/**
* {@inheritdoc}
*/
public function getRouteDebugMessage($name, array $parameters = array())
{
if (is_scalar($name)) {
return $name;
}
if (is_array($name)) {
return serialize($name);
}
if ($name instanceof RouteObjectInterface) {
return 'Route with key '.$name->getRouteKey();
}
if ($name instanceof SymfonyRoute) {
return 'Route with path '.$name->getPath();
}
return get_class($name);
}
}

View file

@ -0,0 +1,54 @@
# Symfony CMF Routing Component
[![Build Status](https://secure.travis-ci.org/symfony-cmf/Routing.png?branch=master)](http://travis-ci.org/symfony-cmf/Routing)
[![Latest Stable Version](https://poser.pugx.org/symfony-cmf/routing/version.png)](https://packagist.org/packages/symfony-cmf/routing)
[![Total Downloads](https://poser.pugx.org/symfony-cmf/routing/d/total.png)](https://packagist.org/packages/symfony-cmf/routing)
The Symfony CMF Routing component extends the Symfony2 core routing component.
It provides:
* A ChainRouter to run several routers in parallel
* A DynamicRouter that can load routes from any database and can generate
additional information in the route match.
Even though it has Symfony in its name, the Routing component does not need the
full Symfony2 Framework and can be used in standalone projects.
For Symfony 2 projects, an optional
[RoutingBundle](https://github.com/symfony-cmf/RoutingBundle)
is also available.
This library is provided by the [Symfony Content Management Framework (CMF) project](http://cmf.symfony.com/)
and licensed under the [MIT License](LICENSE).
## Requirements
* The Symfony Routing component (>= 2.2.0)
* See also the `require` section of [composer.json](composer.json)
## Documentation
For the install guide and reference, see:
* [Routing component documentation](http://symfony.com/doc/master/cmf/components/routing/index.html)
See also:
* [All Symfony CMF documentation](http://symfony.com/doc/master/cmf/index.html) - complete Symfony CMF reference
* [Symfony CMF Website](http://cmf.symfony.com/) - introduction, live demo, support and community links
## Contributing
Pull requests are welcome. Please see our
[CONTRIBUTING](https://github.com/symfony-cmf/symfony-cmf/blob/master/CONTRIBUTING.md)
guide.
Unit and/or functional tests exist for this component. See the
[Testing documentation](http://symfony.com/doc/master/cmf/components/testing.html)
for a guide to running the tests.
Thanks to
[everyone who has contributed](https://github.com/symfony-cmf/Routing/contributors) already.

View file

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
/**
* Document for redirection entries with the RedirectController.
*
* Defines additional methods needed by the RedirectController to redirect
* based on the route.
*
* This document may define (in order of precedence - the others can be empty):
*
* - uri: an absolute uri
* - routeName and routeParameters: to be used with the standard symfony router
* or a route entry in the routeParameters for the DynamicRouter. Precedency
* between these is determined by the order of the routers in the chain
* router.
*
* With standard Symfony routing, you can just use uri / routeName and a
* hashmap of parameters.
*
* For the dynamic router, you can return a RouteInterface instance in the
* field 'route' of the parameters.
*
* Note: getRedirectContent must return the redirect route itself for the
* integration with DynamicRouter to work.
*
* @author David Buchmann <david@liip.ch>
*/
interface RedirectRouteInterface extends RouteObjectInterface
{
/**
* Get the absolute uri to redirect to external domains.
*
* If this is non-empty, the other methods won't be used.
*
* @return string target absolute uri
*/
public function getUri();
/**
* Get the target route document this route redirects to.
*
* If non-null, it is added as route into the parameters, which will lead
* to have the generate call issued by the RedirectController to have
* the target route in the parameters.
*
* @return RouteObjectInterface the route this redirection points to
*/
public function getRouteTarget();
/**
* Get the name of the target route for working with the symfony standard
* router.
*
* @return string target route name
*/
public function getRouteName();
/**
* Whether this should be a permanent or temporary redirect.
*
* @return bool
*/
public function isPermanent();
/**
* Get the parameters for the target route router::generate().
*
* Note that for the DynamicRouter, you return the target route
* document as field 'route' of the hashmap.
*
* @return array Information to build the route
*/
public function getParameters();
}

View file

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
/**
* Classes for entries in the routing table may implement this interface in
* addition to extending Symfony\Component\Routing\Route.
*
* If they do, the DynamicRouter will request the route content and put it into
* the RouteObjectInterface::CONTENT_OBJECT field. The DynamicRouter will also
* request getRouteKey and this will be used instead of the symfony core compatible
* route name and can contain any characters.
*
* Some fields in defaults have a special meaning in the getDefaults(). In addition
* to the constants defined in this class, _locale and _controller are also used.
*/
interface RouteObjectInterface
{
/**
* Field name that will hold the route name that was matched.
*/
const ROUTE_NAME = '_route';
/**
* Field name of the route object that was matched.
*/
const ROUTE_OBJECT = '_route_object';
/**
* Field name for an explicit controller name to be used with this route.
*/
const CONTROLLER_NAME = '_controller';
/**
* Field name for an explicit template to be used with this route.
* i.e. CmfContentBundle:StaticContent:index.html.twig.
*/
const TEMPLATE_NAME = '_template';
/**
* Field name for the content of the current route, if any.
*/
const CONTENT_OBJECT = '_content';
/**
* Field name for the content id of the current route, if any.
*/
const CONTENT_ID = '_content_id';
/**
* Get the content document this route entry stands for. If non-null,
* the ControllerClassMapper uses it to identify a controller and
* the content is passed to the controller.
*
* If there is no specific content for this url (i.e. its an "application"
* page), may return null.
*
* @return object the document or entity this route entry points to
*/
public function getContent();
/**
* Get the route name.
*
* Normal symfony routes do not know their name, the name is only known
* from the route collection. In the CMF, it is possible to use route
* documents outside of collections, and thus useful to have routes provide
* their name.
*
* There are no limitations to allowed characters in the name.
*
* @return string|null the route name or null to use the default name
* (e.g. from route collection if known)
*/
public function getRouteKey();
}

View file

@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Interface for the route provider the DynamicRouter is using.
*
* Typically this could be a doctrine orm or odm repository, but you can
* implement something else if you need to.
*/
interface RouteProviderInterface
{
/**
* Finds routes that may potentially match the request.
*
* This may return a mixed list of class instances, but all routes returned
* must extend the core symfony route. The classes may also implement
* RouteObjectInterface to link to a content document.
*
* This method may not throw an exception based on implementation specific
* restrictions on the url. That case is considered a not found - returning
* an empty array. Exceptions are only used to abort the whole request in
* case something is seriously broken, like the storage backend being down.
*
* Note that implementations may not implement an optimal matching
* algorithm, simply a reasonable first pass. That allows for potentially
* very large route sets to be filtered down to likely candidates, which
* may then be filtered in memory more completely.
*
* @param Request $request A request against which to match.
*
* @return RouteCollection with all Routes that could potentially match
* $request. Empty collection if nothing can match.
*/
public function getRouteCollectionForRequest(Request $request);
/**
* Find the route using the provided route name.
*
* @param string $name The route name to fetch.
*
* @return Route
*
* @throws RouteNotFoundException If there is no route with that name in
* this repository
*/
public function getRouteByName($name);
/**
* Find many routes by their names using the provided list of names.
*
* Note that this method may not throw an exception if some of the routes
* are not found or are not actually Route instances. It will just return the
* list of those Route instances it found.
*
* This method exists in order to allow performance optimizations. The
* simple implementation could be to just repeatedly call
* $this->getRouteByName() while catching and ignoring eventual exceptions.
*
* If $names is null, this method SHOULD return a collection of all routes
* known to this provider. If there are many routes to be expected, usage of
* a lazy loading collection is recommended. A provider MAY only return a
* subset of routes to e.g. support paging or other concepts, but be aware
* that the DynamicRouter will only call this method once per
* DynamicRouter::getRouteCollection() call.
*
* @param array|null $names The list of names to retrieve, In case of null,
* the provider will determine what routes to return.
*
* @return Route[] Iterable list with the keys being the names from the
* $names array.
*/
public function getRoutesByNames($names);
}

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\Route;
/**
* Interface to be implemented by content that exposes editable route
* referrers.
*/
interface RouteReferrersInterface extends RouteReferrersReadInterface
{
/**
* Add a route to the collection.
*
* @param Route $route
*/
public function addRoute($route);
/**
* Remove a route from the collection.
*
* @param Route $route
*/
public function removeRoute($route);
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\Route;
/**
* Interface to be implemented by content that wants to be support route generation
* from content with the DynamicRouter by providing the routes that point to it.
*/
interface RouteReferrersReadInterface
{
/**
* Get the routes that point to this content.
*
* @return Route[] Route instances that point to this content
*/
public function getRoutes();
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony CMF package.
*
* (c) 2011-2015 Symfony CMF
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Cmf\Component\Routing;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* This generator is able to handle more than string route names as symfony
* core supports them.
*/
interface VersatileGeneratorInterface extends UrlGeneratorInterface
{
/**
* Whether this generator supports the supplied $name.
*
* 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" which may also be an object or anything
*
* @return bool
*/
public function supports($name);
/**
* Convert a route identifier (name, content object etc) into a string
* usable for logging and other debug/error messages.
*
* @param mixed $name
* @param array $parameters which should contain a content field containing
* a RouteReferrersReadInterface object
*
* @return string
*/
public function getRouteDebugMessage($name, array $parameters = array());
}

View file

@ -0,0 +1,40 @@
{
"name": "symfony-cmf/routing",
"type": "library",
"description": "Extends the Symfony2 routing component for dynamic routes and chaining several routers",
"keywords": ["routing", "database"],
"homepage": "http://cmf.symfony.com",
"license": "MIT",
"authors": [
{
"name": "Symfony CMF Community",
"homepage": "https://github.com/symfony-cmf/Routing/contributors"
}
],
"require": {
"php": "^5.3.9|^7.0",
"symfony/routing": "^2.2|3.*",
"symfony/http-kernel": "^2.2|3.*",
"psr/log": "1.*"
},
"require-dev": {
"symfony-cmf/testing": "^1.3",
"symfony/dependency-injection": "^2.0.5|3.*",
"symfony/config": "^2.2|3.*",
"symfony/event-dispatcher": "^2.1|3.*",
"friendsofsymfony/jsrouting-bundle": "^1.1"
},
"suggest": {
"symfony/event-dispatcher": "DynamicRouter can optionally trigger an event at the start of matching. Minimal version (~2.1)"
},
"autoload": {
"psr-4": {
"Symfony\\Cmf\\Component\\Routing\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
}
}

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit
colors="true"
bootstrap="vendor/symfony-cmf/testing/bootstrap/bootstrap.php"
>
<testsuites>
<testsuite name="Symfony Cmf Routing Test Suite">
<directory>./Tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory>.</directory>
<exclude>
<directory>Test/</directory>
<directory>Tests/</directory>
<directory>vendor/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>