[Routing] added hostname matching support to PhpMatcherDumper

This commit is contained in:
Arnaud Le Blanc 2012-04-14 19:18:29 +02:00
parent 402359ba9d
commit 85d11af880
6 changed files with 437 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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...'));