[Routing] added hostname matching support to PhpMatcherDumper
This commit is contained in:
parent
402359ba9d
commit
85d11af880
@ -98,4 +98,52 @@ class DumperCollection implements \IteratorAggregate
|
||||
{
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the attribute is defined
|
||||
*
|
||||
* @param string $name The attribute name
|
||||
* @return Boolean true if the attribute is defined, false otherwise
|
||||
*/
|
||||
public function hasAttribute($name)
|
||||
{
|
||||
return array_key_exists($name, $this->attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an attribute by name
|
||||
*
|
||||
* @param string $name The attribute name
|
||||
* @param mixed $default Default value is the attribute doesn't exist
|
||||
* @return mixed The attribute value
|
||||
*/
|
||||
public function getAttribute($name, $default = null)
|
||||
{
|
||||
if ($this->hasAttribute($name)) {
|
||||
return $this->attributes[$name];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an attribute by name
|
||||
*
|
||||
* @param string $name The attribute name
|
||||
* @param mixed $value The attribute value
|
||||
*/
|
||||
public function setAttribute($name, $value)
|
||||
{
|
||||
$this->attributes[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets multiple attributes
|
||||
*
|
||||
* @param array $attributes The attributes
|
||||
*/
|
||||
public function setAttributes($attributes)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
}
|
||||
|
@ -109,10 +109,37 @@ EOF;
|
||||
*/
|
||||
private function compileRoutes(RouteCollection $routes, $supportsRedirections)
|
||||
{
|
||||
$collection = $this->flattenRouteCollection($routes);
|
||||
$tree = $this->buildPrefixTree($collection);
|
||||
$fetchedHostname = false;
|
||||
|
||||
return $this->compilePrefixRoutes($tree, $supportsRedirections);
|
||||
$routes = $this->flattenRouteCollection($routes);
|
||||
$groups = $this->groupRoutesByHostnameRegex($routes);
|
||||
$code = '';
|
||||
|
||||
foreach ($groups as $collection) {
|
||||
if (null !== $regex = $collection->getAttribute('hostname_regex')) {
|
||||
|
||||
if (!$fetchedHostname) {
|
||||
$code .= " \$hostname = \$this->context->getHost();\n\n";
|
||||
$fetchedHostname = true;
|
||||
}
|
||||
|
||||
$code .= sprintf(" if (preg_match(%s, \$hostname, \$hostnameMatches)) {\n", var_export($regex, true));
|
||||
}
|
||||
|
||||
$tree = $this->buildPrefixTree($collection);
|
||||
$groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections);
|
||||
|
||||
if (null !== $regex) {
|
||||
// apply extra indention at each line (except empty ones)
|
||||
$groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode);
|
||||
$code .= $groupCode;
|
||||
$code .= " }\n\n";
|
||||
} else {
|
||||
$code .= $groupCode;
|
||||
}
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,6 +198,7 @@ EOF;
|
||||
$conditions = array();
|
||||
$hasTrailingSlash = false;
|
||||
$matches = false;
|
||||
$hostnameMatches = false;
|
||||
$methods = array();
|
||||
|
||||
if ($req = $route->getRequirement('_method')) {
|
||||
@ -183,7 +211,7 @@ EOF;
|
||||
|
||||
$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods));
|
||||
|
||||
if (!count($compiledRoute->getVariables()) && false !== preg_match('#^(.)\^(?<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
|
||||
if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?<url>.*?)\$\1#', $compiledRoute->getRegex(), $m)) {
|
||||
if ($supportsTrailingSlash && substr($m['url'], -1) === '/') {
|
||||
$conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
|
||||
$hasTrailingSlash = true;
|
||||
@ -205,6 +233,10 @@ EOF;
|
||||
$matches = true;
|
||||
}
|
||||
|
||||
if ($compiledRoute->getHostnameVariables()) {
|
||||
$hostnameMatches = true;
|
||||
}
|
||||
|
||||
$conditions = implode(' && ', $conditions);
|
||||
|
||||
$code .= <<<EOF
|
||||
@ -263,10 +295,30 @@ EOF;
|
||||
}
|
||||
|
||||
// optimize parameters array
|
||||
if (true === $matches && $route->getDefaults()) {
|
||||
$code .= sprintf(" return array_merge(\$this->mergeDefaults(\$matches, %s), array('_route' => '%s'));\n"
|
||||
, str_replace("\n", '', var_export($route->getDefaults(), true)), $name);
|
||||
} elseif (true === $matches) {
|
||||
if (($matches || $hostnameMatches) && $route->getDefaults()) {
|
||||
|
||||
$vars = array();
|
||||
if ($matches) {
|
||||
$vars[] = '$matches';
|
||||
}
|
||||
if ($hostnameMatches) {
|
||||
$vars[] = '$hostnameMatches';
|
||||
}
|
||||
$matchesExpr = implode(' + ', $vars);
|
||||
|
||||
$code .= sprintf(" return array_merge(\$this->mergeDefaults(%s, %s), array('_route' => '%s'));\n"
|
||||
, $matchesExpr, str_replace("\n", '', var_export($route->getDefaults(), true)), $name);
|
||||
|
||||
} elseif ($matches || $hostnameMatches) {
|
||||
|
||||
if (!$matches) {
|
||||
$code .= " \$matches = \$hostnameMatches;\n";
|
||||
} else {
|
||||
if ($hostnameMatches) {
|
||||
$code .= " \$matches = \$matches + \$hostnameMatches;\n";
|
||||
}
|
||||
}
|
||||
|
||||
$code .= sprintf(" \$matches['_route'] = '%s';\n\n", $name);
|
||||
$code .= " return \$matches;\n";
|
||||
} elseif ($route->getDefaults()) {
|
||||
@ -308,6 +360,37 @@ EOF;
|
||||
return $to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups consecutive routes having the same hostname regex
|
||||
*
|
||||
* The results is a collection of collections of routes having the same
|
||||
* hostnameRegex
|
||||
*
|
||||
* @param DumperCollection $routes Flat collection of DumperRoutes
|
||||
*
|
||||
* @return DumperCollection A collection with routes grouped by hostname regex in sub-collections
|
||||
*/
|
||||
private function groupRoutesByHostnameRegex(DumperCollection $routes)
|
||||
{
|
||||
$groups = new DumperCollection();
|
||||
|
||||
$currentGroup = new DumperCollection();
|
||||
$currentGroup->setAttribute('hostname_regex', null);
|
||||
$groups->add($currentGroup);
|
||||
|
||||
foreach ($routes as $route) {
|
||||
$hostnameRegex = $route->getRoute()->compile()->getHostnameRegex();
|
||||
if ($currentGroup->getAttribute('hostname_regex') !== $hostnameRegex) {
|
||||
$currentGroup = new DumperCollection();
|
||||
$currentGroup->setAttribute('hostname_regex', $hostnameRegex);
|
||||
$groups->add($currentGroup);
|
||||
}
|
||||
$currentGroup->add($route);
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Organizes the routes into a prefix tree.
|
||||
*
|
||||
@ -331,5 +414,4 @@ EOF;
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -206,22 +206,121 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
||||
return $matches;
|
||||
}
|
||||
|
||||
if (0 === strpos($pathinfo, '/a')) {
|
||||
if (0 === strpos($pathinfo, '/aba')) {
|
||||
// ababa
|
||||
if ($pathinfo === '/ababa') {
|
||||
return array('_route' => 'ababa');
|
||||
}
|
||||
if (0 === strpos($pathinfo, '/aba')) {
|
||||
// ababa
|
||||
if ($pathinfo === '/ababa') {
|
||||
return array('_route' => 'ababa');
|
||||
}
|
||||
|
||||
// foo4
|
||||
if (preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches['_route'] = 'foo4';
|
||||
// foo4
|
||||
if (preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches['_route'] = 'foo4';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$hostname = $this->context->getHost();
|
||||
|
||||
if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route1
|
||||
if ($pathinfo === '/route1') {
|
||||
return array('_route' => 'route1');
|
||||
}
|
||||
|
||||
// route2
|
||||
if ($pathinfo === '/c2/route2') {
|
||||
return array('_route' => 'route2');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^b\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route3
|
||||
if ($pathinfo === '/c2/route3') {
|
||||
return array('_route' => 'route3');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route4
|
||||
if ($pathinfo === '/route4') {
|
||||
return array('_route' => 'route4');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route5
|
||||
if ($pathinfo === '/route5') {
|
||||
return array('_route' => 'route5');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// route6
|
||||
if ($pathinfo === '/route6') {
|
||||
return array('_route' => 'route6');
|
||||
}
|
||||
|
||||
if (preg_match('#^(?<var1>[^\\.]++)\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
if (0 === strpos($pathinfo, '/route1')) {
|
||||
// route11
|
||||
if ($pathinfo === '/route11') {
|
||||
$matches = $hostnameMatches;
|
||||
$matches['_route'] = 'route11';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
// route12
|
||||
if ($pathinfo === '/route12') {
|
||||
return array_merge($this->mergeDefaults($hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route12'));
|
||||
}
|
||||
|
||||
// route13
|
||||
if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches = $matches + $hostnameMatches;
|
||||
$matches['_route'] = 'route13';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
// route14
|
||||
if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
return array_merge($this->mergeDefaults($matches + $hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route14'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route15
|
||||
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches['_route'] = 'route15';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (0 === strpos($pathinfo, '/route1')) {
|
||||
// route16
|
||||
if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
return array_merge($this->mergeDefaults($matches, array ( 'var1' => 'val',)), array('_route' => 'route16'));
|
||||
}
|
||||
|
||||
// route17
|
||||
if ($pathinfo === '/route17') {
|
||||
return array('_route' => 'route17');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (0 === strpos($pathinfo, '/a')) {
|
||||
// a
|
||||
if ($pathinfo === '/a/a...') {
|
||||
return array('_route' => 'a');
|
||||
|
@ -218,22 +218,121 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
||||
return $matches;
|
||||
}
|
||||
|
||||
if (0 === strpos($pathinfo, '/a')) {
|
||||
if (0 === strpos($pathinfo, '/aba')) {
|
||||
// ababa
|
||||
if ($pathinfo === '/ababa') {
|
||||
return array('_route' => 'ababa');
|
||||
}
|
||||
if (0 === strpos($pathinfo, '/aba')) {
|
||||
// ababa
|
||||
if ($pathinfo === '/ababa') {
|
||||
return array('_route' => 'ababa');
|
||||
}
|
||||
|
||||
// foo4
|
||||
if (preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches['_route'] = 'foo4';
|
||||
// foo4
|
||||
if (preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches['_route'] = 'foo4';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$hostname = $this->context->getHost();
|
||||
|
||||
if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route1
|
||||
if ($pathinfo === '/route1') {
|
||||
return array('_route' => 'route1');
|
||||
}
|
||||
|
||||
// route2
|
||||
if ($pathinfo === '/c2/route2') {
|
||||
return array('_route' => 'route2');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^b\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route3
|
||||
if ($pathinfo === '/c2/route3') {
|
||||
return array('_route' => 'route3');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^a\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route4
|
||||
if ($pathinfo === '/route4') {
|
||||
return array('_route' => 'route4');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route5
|
||||
if ($pathinfo === '/route5') {
|
||||
return array('_route' => 'route5');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// route6
|
||||
if ($pathinfo === '/route6') {
|
||||
return array('_route' => 'route6');
|
||||
}
|
||||
|
||||
if (preg_match('#^(?<var1>[^\\.]++)\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
if (0 === strpos($pathinfo, '/route1')) {
|
||||
// route11
|
||||
if ($pathinfo === '/route11') {
|
||||
$matches = $hostnameMatches;
|
||||
$matches['_route'] = 'route11';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
// route12
|
||||
if ($pathinfo === '/route12') {
|
||||
return array_merge($this->mergeDefaults($hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route12'));
|
||||
}
|
||||
|
||||
// route13
|
||||
if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches = $matches + $hostnameMatches;
|
||||
$matches['_route'] = 'route13';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
// route14
|
||||
if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
return array_merge($this->mergeDefaults($matches + $hostnameMatches, array ( 'var1' => 'val',)), array('_route' => 'route14'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (preg_match('#^c\\.example\\.com$#s', $hostname, $hostnameMatches)) {
|
||||
// route15
|
||||
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
$matches['_route'] = 'route15';
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (0 === strpos($pathinfo, '/route1')) {
|
||||
// route16
|
||||
if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||
return array_merge($this->mergeDefaults($matches, array ( 'var1' => 'val',)), array('_route' => 'route16'));
|
||||
}
|
||||
|
||||
// route17
|
||||
if ($pathinfo === '/route17') {
|
||||
return array('_route' => 'route17');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (0 === strpos($pathinfo, '/a')) {
|
||||
// a
|
||||
if ($pathinfo === '/a/a...') {
|
||||
return array('_route' => 'a');
|
||||
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Routing\Test\Matcher\Dumper;
|
||||
|
||||
use Symfony\Component\Routing\Matcher\Dumper\DumperCollection;
|
||||
|
||||
class DumperCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetRoot()
|
||||
{
|
||||
$a = new DumperCollection();
|
||||
|
||||
$b = new DumperCollection();
|
||||
$a->add($b);
|
||||
|
||||
$c = new DumperCollection();
|
||||
$b->add($c);
|
||||
|
||||
$d = new DumperCollection();
|
||||
$c->add($d);
|
||||
|
||||
$this->assertSame($a, $c->getRoot());
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\Routing\Tests\Matcher\Dumper;
|
||||
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
use Symfony\Component\Routing\Matcher\Dumper\DumperCollection;
|
||||
|
||||
class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -41,6 +42,7 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$dumper = new PhpMatcherDumper($collection);
|
||||
|
||||
file_put_contents('/tmp/' . $fixture, $dumper->dump($options));
|
||||
$this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.');
|
||||
}
|
||||
|
||||
@ -156,6 +158,61 @@ class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase
|
||||
$collection1->add('foo4', new Route('/{foo}'));
|
||||
$collection->addCollection($collection1, '/aba');
|
||||
|
||||
// prefix and hostname
|
||||
|
||||
$collection1 = new RouteCollection();
|
||||
|
||||
$route1 = new Route('/route1', array(), array(), array(), 'a.example.com');
|
||||
$collection1->add('route1', $route1);
|
||||
|
||||
$collection2 = new RouteCollection();
|
||||
|
||||
$route2 = new Route('/route2', array(), array(), array(), 'a.example.com');
|
||||
$collection2->add('route2', $route2);
|
||||
|
||||
$route3 = new Route('/route3', array(), array(), array(), 'b.example.com');
|
||||
$collection2->add('route3', $route3);
|
||||
|
||||
$collection1->addCollection($collection2, '/c2');
|
||||
|
||||
$route4 = new Route('/route4', array(), array(), array(), 'a.example.com');
|
||||
$collection1->add('route4', $route4);
|
||||
|
||||
$route5 = new Route('/route5', array(), array(), array(), 'c.example.com');
|
||||
$collection1->add('route5', $route5);
|
||||
|
||||
$route6 = new Route('/route6', array(), array(), array(), null);
|
||||
$collection1->add('route6', $route6);
|
||||
|
||||
$collection->addCollection($collection1);
|
||||
|
||||
// hostname and variables
|
||||
|
||||
$collection1 = new RouteCollection();
|
||||
|
||||
$route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com');
|
||||
$collection1->add('route11', $route11);
|
||||
|
||||
$route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com');
|
||||
$collection1->add('route12', $route12);
|
||||
|
||||
$route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com');
|
||||
$collection1->add('route13', $route13);
|
||||
|
||||
$route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com');
|
||||
$collection1->add('route14', $route14);
|
||||
|
||||
$route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com');
|
||||
$collection1->add('route15', $route15);
|
||||
|
||||
$route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null);
|
||||
$collection1->add('route16', $route16);
|
||||
|
||||
$route17 = new Route('/route17', array(), array(), array(), null);
|
||||
$collection1->add('route17', $route17);
|
||||
|
||||
$collection->addCollection($collection1);
|
||||
|
||||
// multiple sub-collections with a single route and a prefix each
|
||||
$collection1 = new RouteCollection();
|
||||
$collection1->add('a', new Route('/a...'));
|
||||
|
Reference in New Issue
Block a user