Navigation Menu

Skip to content

Commit

Permalink
Fluid interface for building routes in PHP
Browse files Browse the repository at this point in the history
  • Loading branch information
weaverryan authored and fabpot committed Oct 1, 2015
1 parent 00dffe7 commit 15ba2e8
Show file tree
Hide file tree
Showing 2 changed files with 665 additions and 0 deletions.
348 changes: 348 additions & 0 deletions src/Symfony/Component/Routing/RouteCollectionBuilder.php
@@ -0,0 +1,348 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Routing;

use Symfony\Component\Config\Exception\FileLoaderLoadException;
use Symfony\Component\Config\Loader\LoaderInterface;

/**
* Helps add and import routes into a RouteCollection.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class RouteCollectionBuilder
{
/**
* @var Route[]|RouteCollectionBuilder[]
*/
private $routes = array();

private $loader;
private $defaults = array();
private $prefix;
private $host;
private $condition;
private $requirements = array();
private $options = array();
private $schemes;
private $methods;

/**
* @param LoaderInterface $loader
*/
public function __construct(LoaderInterface $loader = null)
{
$this->loader = $loader;
}

/**
* Import an external routing resource and returns the RouteCollectionBuilder.
*
* $routes->mount('/blog', $routes->import('blog.yml'));
*
* @param mixed $resource
* @param string $type
*
* @return RouteCollectionBuilder
*
* @throws FileLoaderLoadException
*/
public function import($resource, $type = null)
{
/** @var RouteCollection $collection */
$collection = $this->load($resource, $type);

// create a builder from the RouteCollection
$builder = $this->createBuilder();
foreach ($collection->all() as $name => $route) {
$builder->addRoute($route, $name);
}

foreach ($collection->getResources() as $resource) {
$builder->addResource($resource);
}

return $builder;
}

/**
* Adds a route and returns it for future modification.
*
* @param string $path The route path
* @param string $controller The route's controller
* @param string|null $name The name to give this route
*
* @return Route
*/
public function add($path, $controller, $name = null)
{
$route = new Route($path);
$route->setDefault('_controller', $controller);
$this->addRoute($route, $name);

return $route;
}

/**
* Returns a RouteCollectionBuilder that can be configured and then added with mount().
*
* @return RouteCollectionBuilder
*/
public function createBuilder()
{
return new self($this->loader);
}

/**
* Add a RouteCollectionBuilder.
*
* @param RouteCollectionBuilder $builder
*/
public function mount($prefix, RouteCollectionBuilder $builder)
{
$builder->prefix = trim(trim($prefix), '/');
$this->routes[] = $builder;
}

/**
* Adds a Route object to the builder.
*
* @param Route $route
* @param string|null $name
*
* @return $this
*/
public function addRoute(Route $route, $name = null)
{
if (null === $name) {
// used as a flag to know which routes will need a name later
$name = '_unnamed_route_'.spl_object_hash($route);
}

$this->routes[$name] = $route;

return $this;
}

/**
* Sets the host on all embedded routes (unless already set).
*
* @param string $pattern
*
* @return $this
*/
public function setHost($pattern)
{
$this->host = $pattern;

return $this;
}

/**
* Sets a condition on all embedded routes (unless already set).
*
* @param string $condition
*
* @return $this
*/
public function setCondition($condition)
{
$this->condition = $condition;

return $this;
}

/**
* Sets a default value that will be added to all embedded routes (unless that
* default value is already set.
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function setDefault($key, $value)
{
$this->defaults[$key] = $value;

return $this;
}

/**
* Sets a requirement that will be added to all embedded routes (unless that
* requirement is already set.
*
* @param string $key
* @param mixed $regex
*
* @return $this
*/
public function setRequirement($key, $regex)
{
$this->requirements[$key] = $regex;

return $this;
}

/**
* Sets an opiton that will be added to all embedded routes (unless that
* option is already set.
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function setOption($key, $value)
{
$this->options[$key] = $value;

return $this;
}

/**
* Sets the schemes on all embedded routes (unless already set).
*
* @param array|string $schemes
*
* @return $this
*/
public function setSchemes($schemes)
{
$this->schemes = $schemes;

return $this;
}

/**
* Sets the methods on all embedded routes (unless already set).
*
* @param array|string $methods
*
* @return $this
*/
public function setMethods($methods)
{
$this->methods = $methods;

return $this;
}

/**
* Creates the final ArrayCollection, returns it, and clears everything.
*
* @return RouteCollection
*/
public function build()
{
$routeCollection = new RouteCollection();

foreach ($this->routes as $name => $route) {
if ($route instanceof Route) {
$route->setDefaults(array_merge($this->defaults, $route->getDefaults()));
$route->setOptions(array_merge($this->options, $route->getOptions()));

// we're extra careful here to avoid re-setting deprecated _method and _scheme
foreach ($this->requirements as $key => $val) {
if (!$route->hasRequirement($key)) {
$route->setRequirement($key, $val);
}
}

if (null !== $this->prefix) {
$route->setPath('/'.$this->prefix.$route->getPath());
}

if (!$route->getHost()) {
$route->setHost($this->host);
}

if (!$route->getCondition()) {
$route->setCondition($this->condition);
}

if (!$route->getSchemes()) {
$route->setSchemes($this->schemes);
}

if (!$route->getMethods()) {
$route->setMethods($this->methods);
}

// auto-generate the route name if it's been marked
if ('_unnamed_route_' === substr($name, 0, 15)) {
$name = $this->generateRouteName($route);
}

$routeCollection->add($name, $route);
} else {
/* @var self $route */
$subCollection = $route->build();
$subCollection->addPrefix($this->prefix);

$routeCollection->addCollection($subCollection);
}
}

return $routeCollection;
}

/**
* Generates a route name based on details of this route.
*
* @return string
*/
private function generateRouteName(Route $route)
{
$methods = implode('_', $route->getMethods()).'_';

$routeName = $methods.$route->getPath();
$routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName);
$routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);

// Collapse consecutive underscores down into a single underscore.
$routeName = preg_replace('/_+/', '_', $routeName);

return $routeName;
}

/**
* Finds a loader able to load an imported resource and loads it.
*
* @param mixed $resource A resource
* @param string|null $type The resource type or null if unknown
*
* @return RouteCollection
*
* @throws FileLoaderLoadException If no loader is found
*/
private function load($resource, $type = null)
{
if (null === $this->loader) {
throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.');
}

if ($this->loader->supports($resource, $type)) {
return $this->loader->load($resource, $type);
}

if (null === $resolver = $this->loader->getResolver()) {
throw new FileLoaderLoadException($resource);
}

if (false === $loader = $resolver->resolve($resource, $type)) {
throw new FileLoaderLoadException($resource);
}

return $loader->load($resource, $type);
}
}

0 comments on commit 15ba2e8

Please sign in to comment.