From fd88de79ffe5673e8c1a8eb2c280466371c096ab Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Mon, 27 Oct 2014 14:00:02 +0100 Subject: [PATCH] [Routing] serialize the compiled route to speed things up This also makes the CompiledRoute implement Serializable in order to: 1. make the serialization format shorter 2. have no null bytes in there, which the native serializer add for private properties, and thus would complicate saving in databases etc. 3. We should add to our symfony BC promise, that only classes that implement Serializable are ensured to be deserializable correctly with serialized representations of the class in previous symfony versions. --- .../Component/Routing/CompiledRoute.php | 35 +++++++++++++++++- src/Symfony/Component/Routing/Route.php | 14 ++++++- .../Component/Routing/Tests/RouteTest.php | 37 ++++++++++++++++++- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Routing/CompiledRoute.php b/src/Symfony/Component/Routing/CompiledRoute.php index 5e4c96073e..7b21fe9eab 100644 --- a/src/Symfony/Component/Routing/CompiledRoute.php +++ b/src/Symfony/Component/Routing/CompiledRoute.php @@ -16,7 +16,7 @@ namespace Symfony\Component\Routing; * * @author Fabien Potencier */ -class CompiledRoute +class CompiledRoute implements \Serializable { private $variables; private $tokens; @@ -51,6 +51,39 @@ class CompiledRoute $this->variables = $variables; } + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'vars' => $this->variables, + 'path_prefix' => $this->staticPrefix, + 'path_regex' => $this->regex, + 'path_tokens' => $this->tokens, + 'path_vars' => $this->pathVariables, + 'host_regex' => $this->hostRegex, + 'host_tokens' => $this->hostTokens, + 'host_vars' => $this->hostVariables, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + $this->variables = $data['vars']; + $this->staticPrefix = $data['path_prefix']; + $this->regex = $data['path_regex']; + $this->tokens = $data['path_tokens']; + $this->pathVariables = $data['path_vars']; + $this->hostRegex = $data['host_regex']; + $this->hostTokens = $data['host_tokens']; + $this->hostVariables = $data['host_vars']; + } + /** * Returns the static prefix. * diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php index 02fc6de24a..31266fd820 100644 --- a/src/Symfony/Component/Routing/Route.php +++ b/src/Symfony/Component/Routing/Route.php @@ -95,6 +95,9 @@ class Route implements \Serializable } } + /** + * {@inheritdoc} + */ public function serialize() { return serialize(array( @@ -105,12 +108,16 @@ class Route implements \Serializable 'options' => $this->options, 'schemes' => $this->schemes, 'methods' => $this->methods, + 'compiled' => $this->compiled, )); } - public function unserialize($data) + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { - $data = unserialize($data); + $data = unserialize($serialized); $this->path = $data['path']; $this->host = $data['host']; $this->defaults = $data['defaults']; @@ -118,6 +125,9 @@ class Route implements \Serializable $this->options = $data['options']; $this->schemes = $data['schemes']; $this->methods = $data['methods']; + if (isset($data['compiled'])) { + $this->compiled = $data['compiled']; + } } /** diff --git a/src/Symfony/Component/Routing/Tests/RouteTest.php b/src/Symfony/Component/Routing/Tests/RouteTest.php index 494ea58b87..eef51d17a6 100644 --- a/src/Symfony/Component/Routing/Tests/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteTest.php @@ -212,7 +212,7 @@ class RouteTest extends \PHPUnit_Framework_TestCase public function testSerialize() { - $route = new Route('/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); $serialized = serialize($route); $unserialized = unserialize($serialized); @@ -220,4 +220,39 @@ class RouteTest extends \PHPUnit_Framework_TestCase $this->assertEquals($route, $unserialized); $this->assertNotSame($route, $unserialized); } + + /** + * Tests that the compiled version is also serialized to prevent the overhead + * of compiling it again after unserialize. + */ + public function testSerializeWhenCompiled() + { + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $serialized = serialize($route); + $unserialized = unserialize($serialized); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } + + /** + * Tests that the serialized representation of a route in one symfony version + * also works in later symfony versions, i.e. the unserialized route is in the + * same state as another, semantically equivalent, route. + */ + public function testSerializedRepresentationKeepsWorking() + { + $serialized = 'C:31:"Symfony\Component\Routing\Route":933:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":568:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:30:"#^/prefix(?:/(?P\d+))?$#s";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:38:"#^(?P[^\.]++)\.example\.net$#s";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}'; + $unserialized = unserialize($serialized); + + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } }