Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Routing] Optimised dumped router matcher, prevent unneeded function calls. #21755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
62 changes: 48 additions & 14 deletions 62 src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,16 @@ public function match(\$pathinfo)
{
\$allow = array();
\$pathinfo = rawurldecode(\$pathinfo);
\$trimmedPathinfo = rtrim(\$pathinfo, '/');
\$context = \$this->context;
\$request = \$this->request;
\$requestMethod = \$isLikeGetMethod = \$context->getMethod();
\$schema = \$context->getScheme();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be $scheme here, not $schema, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabpot correct, I've fixed it.


if ('HEAD' === \$requestMethod) {
\$isLikeGetMethod = 'GET';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the $isLikeGetMethod variable name makes me think it is a boolean

Copy link
Member

@javiereguiluz javiereguiluz Feb 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is prefix in this variable name looks wrong. Maybe it's too late, but what if we rename $isLikeGetMethod = 'GET' to $isSafeMethod = true

We had some discussions about what a "safe method" is in HTTP, but the section 9.1.1 of RFC 2616 mentions GET and HEAD as safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html


Or at least, rename $isLikeGetMethod = 'GET' to $isGetOrHead = true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we don't want a boolean, because then we can't perform a meaningful optimisation. We just want to have a interpreted method, which we can check against. Otherwise we'd need to do more check than less.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about renaming it to $methodAlias or $canonicalMethod or something like that? We "can't" store a string in a $is* variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like conicalMethod, a lot. I had previously opted to use methodWhereHeadMatchesGet instead. Which is a little verbose, but it does make it clear what's going on.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for $canonicalMethod

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed the variable to $canonicalMethod.

}


$code

Expand All @@ -133,7 +141,7 @@ private function compileRoutes(RouteCollection $routes, $supportsRedirections)
foreach ($groups as $collection) {
if (null !== $regex = $collection->getAttribute('host_regex')) {
if (!$fetchedHost) {
$code .= " \$host = \$this->context->getHost();\n\n";
$code .= " \$host = \$context->getHost();\n\n";
$fetchedHost = true;
}

Expand Down Expand Up @@ -217,20 +225,15 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
$hostMatches = false;
$methods = $route->getMethods();

// GET and HEAD are equivalent
if (in_array('GET', $methods) && !in_array('HEAD', $methods)) {
$methods[] = 'HEAD';
}

$supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods));
$regex = $compiledRoute->getRegex();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removed N trim calls.

if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P<url>.*?)\$\1#'.(substr($regex, -1) === 'u' ? 'u' : ''), $regex, $m)) {
if ($supportsTrailingSlash && substr($m['url'], -1) === '/') {
$conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
$conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true));
$hasTrailingSlash = true;
} else {
$conditions[] = sprintf('$pathinfo === %s', var_export(str_replace('\\', '', $m['url']), true));
$conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true));
}
} else {
if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) {
Expand Down Expand Up @@ -263,26 +266,57 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
EOF;

$gotoname = 'not_'.preg_replace('/[^A-Za-z0-9_]/', '', $name);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes N function calls.

if ($methods) {
if (1 === count($methods)) {
$code .= <<<EOF
if (\$this->context->getMethod() != '$methods[0]') {
if ($methods[0] === 'HEAD') {
$code .= <<<EOF
if ('HEAD' !== \$requestMethod) {
\$allow[] = 'HEAD';
goto $gotoname;
}


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes N function calls.

EOF;
} else {
$code .= <<<EOF
if ('$methods[0]' !== \$isLikeGetMethod) {
\$allow[] = '$methods[0]';
goto $gotoname;
}


EOF;
}
} else {
$methods = implode("', '", $methods);
$code .= <<<EOF
if (!in_array(\$this->context->getMethod(), array('$methods'))) {
$methodVariable = 'requestMethod';

if (in_array('GET', $methods)) {
// Since we treat HEAD requests like GET requests we don't need to match it.
$methodVariable = 'isLikeGetMethod';
$methods = array_filter($methods, function ($method) { return 'HEAD' !== $method; });
}

if (1 === count($methods)) {
$code .= <<<EOF
if ('$methods[0]' !== \$$methodVariable) {
\$allow[] = '$methods[0]';
goto $gotoname;
Copy link
Contributor

@GuilhemN GuilhemN Feb 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has the use of the goto ever been discussed?
It seems like here we only need to reverse the conditions to get ride of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm checking this out now, if proven to be effective I'll create another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GuilhemN I suspected it to have some effect, but it did literally nothing.

}


EOF;
} else {
$methods = implode("', '", $methods);
$code .= <<<EOF
if (!in_array(\$$methodVariable, array('$methods'))) {
\$allow = array_merge(\$allow, array('$methods'));
goto $gotoname;
}


EOF;
}
}
}

Expand All @@ -303,7 +337,7 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
$schemes = str_replace("\n", '', var_export(array_flip($schemes), true));
$code .= <<<EOF
\$requiredSchemes = $schemes;
if (!isset(\$requiredSchemes[\$this->context->getScheme()])) {
if (!isset(\$requiredSchemes[\$schema])) {
return \$this->redirect(\$pathinfo, '$name', key(\$requiredSchemes));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ public function match($pathinfo)
{
$allow = array();
$pathinfo = rawurldecode($pathinfo);
$trimmedPathinfo = rtrim($pathinfo, '/');
$context = $this->context;
$request = $this->request;
$requestMethod = $isLikeGetMethod = $context->getMethod();
$schema = $context->getScheme();

if ('HEAD' === $requestMethod) {
$isLikeGetMethod = 'GET';
}


// foo
if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?P<bar>baz|symfony)$#s', $pathinfo, $matches)) {
Expand All @@ -35,8 +43,8 @@ public function match($pathinfo)
if (0 === strpos($pathinfo, '/bar')) {
// bar
if (preg_match('#^/bar/(?P<foo>[^/]++)$#s', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD'));
if ('GET' !== $isLikeGetMethod) {
$allow[] = 'GET';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't you adding HEAD anymore here ? It is an allowed method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stof this was an optimisation proposed by @fabpot. We stream HEAD in a special way (this is current behaviour). When a HEAD request is made, we also match GET. Inverted, when we declare GET actions, we also match in HEAD. So in this normalised case we know that if something is "like get" we don't need HEAD anymore because we're we already match GET.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make sure that this is properly tested? If someone else breaks this, it will be a huge bug!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jean85 there's an additional test case added in this PR to do just that.

goto not_bar;
}

Expand All @@ -46,8 +54,8 @@ public function match($pathinfo)

// barhead
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P<foo>[^/]++)$#s', $pathinfo, $matches)) {
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
$allow = array_merge($allow, array('GET', 'HEAD'));
if ('GET' !== $isLikeGetMethod) {
$allow[] = 'GET';
goto not_barhead;
}

Expand All @@ -60,17 +68,17 @@ public function match($pathinfo)
if (0 === strpos($pathinfo, '/test')) {
if (0 === strpos($pathinfo, '/test/baz')) {
// baz
if ($pathinfo === '/test/baz') {
if ('/test/baz' === $pathinfo) {
return array('_route' => 'baz');
}

// baz2
if ($pathinfo === '/test/baz.html') {
if ('/test/baz.html' === $pathinfo) {
return array('_route' => 'baz2');
}

// baz3
if ($pathinfo === '/test/baz3/') {
if ('/test/baz3/' === $pathinfo) {
return array('_route' => 'baz3');
}

Expand All @@ -83,7 +91,7 @@ public function match($pathinfo)

// baz5
if (preg_match('#^/test/(?P<foo>[^/]++)/$#s', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'POST') {
if ('POST' !== $isLikeGetMethod) {
$allow[] = 'POST';
goto not_baz5;
}
Expand All @@ -94,7 +102,7 @@ public function match($pathinfo)

// baz.baz6
if (preg_match('#^/test/(?P<foo>[^/]++)/$#s', $pathinfo, $matches)) {
if ($this->context->getMethod() != 'PUT') {
if ('PUT' !== $isLikeGetMethod) {
$allow[] = 'PUT';
goto not_bazbaz6;
}
Expand All @@ -106,7 +114,7 @@ public function match($pathinfo)
}

// foofoo
if ($pathinfo === '/foofoo') {
if ('/foofoo' === $pathinfo) {
return array ( 'def' => 'test', '_route' => 'foofoo',);
}

Expand All @@ -116,7 +124,7 @@ public function match($pathinfo)
}

// space
if ($pathinfo === '/spa ce') {
if ('/spa ce' === $pathinfo) {
return array('_route' => 'space');
}

Expand Down Expand Up @@ -161,12 +169,12 @@ public function match($pathinfo)
}

// overridden2
if ($pathinfo === '/multi/new') {
if ('/multi/new' === $pathinfo) {
return array('_route' => 'overridden2');
}

// hey
if ($pathinfo === '/multi/hey/') {
if ('/multi/hey/' === $pathinfo) {
return array('_route' => 'hey');
}

Expand All @@ -184,7 +192,7 @@ public function match($pathinfo)

if (0 === strpos($pathinfo, '/aba')) {
// ababa
if ($pathinfo === '/ababa') {
if ('/ababa' === $pathinfo) {
return array('_route' => 'ababa');
}

Expand All @@ -195,59 +203,59 @@ public function match($pathinfo)

}

$host = $this->context->getHost();
$host = $context->getHost();

if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) {
// route1
if ($pathinfo === '/route1') {
if ('/route1' === $pathinfo) {
return array('_route' => 'route1');
}

// route2
if ($pathinfo === '/c2/route2') {
if ('/c2/route2' === $pathinfo) {
return array('_route' => 'route2');
}

}

if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) {
// route3
if ($pathinfo === '/c2/route3') {
if ('/c2/route3' === $pathinfo) {
return array('_route' => 'route3');
}

}

if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) {
// route4
if ($pathinfo === '/route4') {
if ('/route4' === $pathinfo) {
return array('_route' => 'route4');
}

}

if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) {
// route5
if ($pathinfo === '/route5') {
if ('/route5' === $pathinfo) {
return array('_route' => 'route5');
}

}

// route6
if ($pathinfo === '/route6') {
if ('/route6' === $pathinfo) {
return array('_route' => 'route6');
}

if (preg_match('#^(?P<var1>[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) {
if (0 === strpos($pathinfo, '/route1')) {
// route11
if ($pathinfo === '/route11') {
if ('/route11' === $pathinfo) {
return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ());
}

// route12
if ($pathinfo === '/route12') {
if ('/route12' === $pathinfo) {
return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',));
}

Expand Down Expand Up @@ -280,15 +288,15 @@ public function match($pathinfo)
}

// route17
if ($pathinfo === '/route17') {
if ('/route17' === $pathinfo) {
return array('_route' => 'route17');
}

}

if (0 === strpos($pathinfo, '/a')) {
// a
if ($pathinfo === '/a/a...') {
if ('/a/a...' === $pathinfo) {
return array('_route' => 'a');
}

Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.