diff --git a/core/core.services.yml b/core/core.services.yml index 6ab7957..32495fc 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -115,6 +115,13 @@ services: factory_method: get factory_service: cache_factory arguments: [discovery] + cache.url_generator: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin } + factory_method: get + factory_service: cache_factory + arguments: [url_generator] page_cache_request_policy: class: Drupal\Core\PageCache\DefaultRequestPolicy arguments: ['@session_configuration'] @@ -544,11 +551,17 @@ services: arguments: ['@route_filter.lazy_collector'] tags: - { name: event_subscriber } - url_generator: + url_generator.uncached: class: Drupal\Core\Routing\UrlGenerator + public: false arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@logger.channel.default', '@request_stack'] calls: - [setContext, ['@?router.request_context']] + url_generator: + class: Drupal\Core\Routing\CachedUrlGenerator + arguments: ['@url_generator.uncached', '@cache.url_generator', '@router.route_provider', '@request_stack'] + tags: + - { name: needs_destruction } unrouted_url_assembler: class: Drupal\Core\Utility\UnroutedUrlAssembler arguments: ['@request_stack', '@config.factory', '@path_processor_manager'] diff --git a/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php b/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php new file mode 100644 index 0000000..805c32b --- /dev/null +++ b/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php @@ -0,0 +1,246 @@ +urlGenerator = $url_generator; + $this->cache = $cache; + $this->routeProvider = $route_provider; + $this->requestStack = $request_stack; + + // Select cache based on the current request URI. We store one entry for + // each URI. + $cache_key = $request_stack->getCurrentRequest()->getUri(); + $this->cachedUrls[$cache_key] = []; + + // Retrieve stored URLs. + $cached = $this->cache->get($cache_key); + if ($cached) { + $this->cachedUrls[$cache_key] = $cached->data; + } + } + + /** + * {@inheritdoc} + */ + public function clearCache() { + foreach (array_keys($this->cachedUrls) as $cache_key) { + $this->cache->delete($cache_key); + } + $this->cachedUrls = array(); + } + + /** + * Writes the cache of generated URLs. + */ + protected function writeCache() { + if ($this->cacheNeedsWriting && !empty($this->cachedUrls)) { + foreach (array_keys($this->cachedUrls) as $cache_key) { + // Set the URL cache to expire in 24 hours. + $expire = REQUEST_TIME + (60 * 60 * 24); + $this->cache->set($cache_key, $this->cachedUrls[$cache_key], $expire); + } + } + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $absolute = FALSE) { + $options = array(); + // We essentially inline the implementation from the Drupal UrlGenerator + // and avoid setting $options so that we increase the likelihood of caching. + if ($absolute) { + $options['absolute'] = $absolute; + } + return $this->generateFromRoute($name, $parameters, $options); + } + + /** + * {@inheritdoc} + */ + public function generateFromPath($path = NULL, $options = array()) { + $key = static::PATH_CACHE_PREFIX . hash('sha256', $path . serialize($options)); + $cache_key = $this->requestStack->getCurrentRequest()->getUri(); + if (!isset($this->cachedUrls[$cache_key][$key])) { + $this->cachedUrls[$cache_key][$key] = $this->urlGenerator->generateFromPath($path, $options); + $this->cacheNeedsWriting = TRUE; + } + return $this->cachedUrls[$cache_key][$key]; + } + + /** + * {@inheritdoc} + */ + public function generateFromRoute($name, $parameters = array(), $options = array()) { + // In some cases $name may be a Route object, rather than a string. + $key = static::ROUTE_CACHE_PREFIX . hash('sha256', serialize($name) . serialize($options) . serialize($parameters)); + $cache_key = $this->requestStack->getCurrentRequest()->getUri(); + if (!isset($this->cachedUrls[$cache_key][$key])) { + $generated_url = $this->urlGenerator->generateFromRoute($name, $parameters, $options); + $route = $this->getRoute($name); + if ($route->hasOption('_cacheable') && !$route->getOption('_cacheable')) { + return $generated_url; + } + else { + $this->cachedUrls[$cache_key][$key] = $generated_url; + $this->cacheNeedsWriting = TRUE; + } + } + return $this->cachedUrls[$cache_key][$key]; + } + + /** + * Find the route using the provided route name. + * + * @param string $name + * The route name to fetch + * + * @return \Symfony\Component\Routing\Route + * The found route. + * + * @throws \Symfony\Component\Routing\Exception\RouteNotFoundException + * Thrown if there is no route with that name in this repository. + * + * @see \Drupal\Core\Routing\RouteProviderInterface + */ + protected function getRoute($name) { + if ($name instanceof SymfonyRoute) { + $route = $name; + } + elseif (NULL === $route = clone $this->routeProvider->getRouteByName($name)) { + throw new RouteNotFoundException(sprintf('Route "%s" does not exist.', $name)); + } + return $route; + } + + /** + * {@inheritdoc} + */ + public function supports($name) { + return $this->urlGenerator->supports($name); + } + + /** + * {@inheritdoc} + */ + public function getRouteDebugMessage($name, array $parameters = array()) { + return $this->urlGenerator->getRouteDebugMessage($name, $parameters); + } + + /** + * {@inheritdoc} + */ + public function destruct() { + $this->writeCache(); + } + + /** + * {@inheritdoc} + */ + public function setContext(SymfonyRequestContext $context) { + $this->urlGenerator->setContext($context); + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return $this->urlGenerator->getContext(); + } + + /** + * {@inheritdoc} + */ + public function getPathFromRoute($name, $parameters = array()) { + return $this->urlGenerator->getPathFromRoute($name, $parameters); + } + +} diff --git a/core/lib/Drupal/Core/Routing/CachedUrlGeneratorInterface.php b/core/lib/Drupal/Core/Routing/CachedUrlGeneratorInterface.php new file mode 100644 index 0000000..d9bbbf3 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/CachedUrlGeneratorInterface.php @@ -0,0 +1,22 @@ +generateFromRoute($name, $parameters, $options); } diff --git a/core/modules/file/src/Tests/DownloadTest.php b/core/modules/file/src/Tests/DownloadTest.php index 9de1b3a..32f607f 100644 --- a/core/modules/file/src/Tests/DownloadTest.php +++ b/core/modules/file/src/Tests/DownloadTest.php @@ -101,6 +101,8 @@ protected function doPrivateFileTransferTest() { */ function testFileCreateUrl() { + $urlGenerator = \Drupal::service('url_generator'); + // Tilde (~) is excluded from this test because it is encoded by // rawurlencode() in PHP 5.2 but not in PHP 5.3, as per RFC 3986. // @see http://www.php.net/manual/function.rawurlencode.php#86506 @@ -125,6 +127,7 @@ function testFileCreateUrl() { $base_path = $request->getSchemeAndHttpHost() . $request->getBasePath(); $this->checkUrl('public', '', $basename, $base_path . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded); $this->checkUrl('private', '', $basename, $base_path . '/' . $script_path . 'system/files/' . $basename_encoded); + $urlGenerator->clearCache(); } $this->assertEqual(file_create_url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', t('Generated URL matches expected URL.')); } diff --git a/core/modules/language/src/Tests/LanguageConfigurationTest.php b/core/modules/language/src/Tests/LanguageConfigurationTest.php index 9a43437..f5870d9 100644 --- a/core/modules/language/src/Tests/LanguageConfigurationTest.php +++ b/core/modules/language/src/Tests/LanguageConfigurationTest.php @@ -65,6 +65,8 @@ function testLanguageConfiguration() { ); $this->drupalPostForm(NULL, $edit, t('Save configuration')); $this->rebuildContainer(); + // Clear cached urls in the local process too. + $this->container->get('url_generator')->clearCache(); $this->assertOptionSelected('edit-site-default-language', 'fr', 'Default language updated.'); $this->assertUrl(\Drupal::url('system.regional_settings', [], ['absolute' => TRUE, 'langcode' => 'fr']), [], 'Correct page redirection.'); diff --git a/core/modules/language/src/Tests/LanguageListTest.php b/core/modules/language/src/Tests/LanguageListTest.php index 88e227f..dfa5428 100644 --- a/core/modules/language/src/Tests/LanguageListTest.php +++ b/core/modules/language/src/Tests/LanguageListTest.php @@ -53,6 +53,8 @@ function testLanguageList() { 'direction' => Language::DIRECTION_LTR, ); $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language')); + // Clear cached urls in the local process too. + $this->container->get('url_generator')->clearCache(); $this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE])); $this->assertRaw('"edit-languages-' . $langcode .'-weight"', 'Language code found.'); $this->assertText(t($name), 'Test language added.'); @@ -69,6 +71,8 @@ function testLanguageList() { 'site_default_language' => $langcode, ); $this->drupalPostForm(NULL, $edit, t('Save configuration')); + // Clear cached urls in the local process too. + $this->container->get('url_generator')->clearCache(); $this->rebuildContainer(); $this->assertNoOptionSelected('edit-site-default-language', 'en', 'Default language updated.'); $this->assertUrl(\Drupal::url('system.regional_settings', [], ['absolute' => TRUE, 'language' => $language])); @@ -96,6 +100,8 @@ function testLanguageList() { ); $this->drupalPostForm($path, $edit, t('Save configuration')); $this->rebuildContainer(); + // Clear cached urls in the local process too. + $this->container->get('url_generator')->clearCache(); // Ensure 'delete' link works. $this->drupalGet('admin/config/regional/language'); $this->clickLink(t('Delete')); @@ -117,6 +123,8 @@ function testLanguageList() { $this->drupalGet('admin/config/regional/language/delete/' . $langcode); $this->assertResponse(404, 'Language no longer found.'); + // Clear cached urls in the local process too. + $this->container->get('url_generator')->clearCache(); // Delete French. $this->drupalPostForm('admin/config/regional/language/delete/fr', array(), t('Delete')); // Make sure the "language_count" state has been updated correctly. diff --git a/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php index 4d16686..fbf15a5 100644 --- a/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php @@ -473,9 +473,10 @@ function testLanguageDomain() { $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, format_string('The right HTTPS URL (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url))); - // Test HTTPS via current URL scheme. + // Switch from HTTP to HTTPS support. $request = Request::create('', 'GET', array(), array(), array(), array('HTTPS' => 'on')); $this->container->get('request_stack')->push($request); + $italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString(); $correct_link = 'https://' . $link; $this->assertTrue($italian_url == $correct_link, format_string('The right URL (via current URL scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url))); diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php index db439dc..c711bbe 100644 --- a/core/modules/rest/src/Routing/ResourceRoutes.php +++ b/core/modules/rest/src/Routing/ResourceRoutes.php @@ -71,6 +71,10 @@ protected function alterRoutes(RouteCollection $collection) { // Iterate over all enabled resource plugins. foreach ($enabled_resources as $id => $enabled_methods) { + // rest.settings.yml includes non-existing plugins at the moment. + if (!$this->manager->hasDefinition($id)) { + continue; + } $plugin = $this->manager->getInstance(array('id' => $id)); foreach ($plugin->routes() as $name => $route) { diff --git a/core/modules/system/src/Form/RegionalForm.php b/core/modules/system/src/Form/RegionalForm.php index 827f74f..1472fa8 100644 --- a/core/modules/system/src/Form/RegionalForm.php +++ b/core/modules/system/src/Form/RegionalForm.php @@ -8,9 +8,11 @@ namespace Drupal\system\Form; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Locale\CountryManagerInterface; -use Drupal\Core\Form\ConfigFormBase; +use Drupal\Core\Routing\CachedUrlGeneratorInterface; +use Drupal\Core\Routing\UrlGeneratorInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -26,16 +28,26 @@ class RegionalForm extends ConfigFormBase { protected $countryManager; /** + * The URL generator. + * + * @var \Drupal\Core\Routing\UrlGeneratorInterface + */ + protected $urlGenerator; + + /** * Constructs a RegionalForm object. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The factory for configuration objects. * @param \Drupal\Core\Locale\CountryManagerInterface $country_manager * The country manager. + * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator + * The url generator. */ - public function __construct(ConfigFactoryInterface $config_factory, CountryManagerInterface $country_manager) { + public function __construct(ConfigFactoryInterface $config_factory, CountryManagerInterface $country_manager, UrlGeneratorInterface $url_generator) { parent::__construct($config_factory); $this->countryManager = $country_manager; + $this->urlGenerator = $url_generator; } /** @@ -44,7 +56,8 @@ public function __construct(ConfigFactoryInterface $config_factory, CountryManag public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), - $container->get('country_manager') + $container->get('country_manager'), + $container->get('url_generator') ); } @@ -159,6 +172,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) { ->set('timezone.user.default', $form_state->getValue('user_default_timezone')) ->save(); + if ($this->urlGenerator instanceof CachedUrlGeneratorInterface) { + $this->urlGenerator->clearCache(); + } + parent::submitForm($form, $form_state); } diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 2bb9e62..d61efd0 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -381,11 +381,13 @@ system.theme_settings_theme: '': path: '' options: + _cacheable: false _only_fragment: TRUE - '': path: '' + options: + _cacheable: false system.modules_uninstall: path: '/admin/modules/uninstall' diff --git a/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorIntegrationTest.php b/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorIntegrationTest.php new file mode 100644 index 0000000..0965091 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorIntegrationTest.php @@ -0,0 +1,37 @@ +getAbsoluteUrl('/'); + + // Populate the cache. + $this->drupalGet($name); + + $cache = $this->container->get('cache.url_generator'); + // BAD - Use knowledge of an internal implementation detail to extract the + // cache data! + $data = $cache->get($name)->data; + $this->assertFalse(empty($data), 'The cache has been primed.'); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorTest.php b/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorTest.php new file mode 100644 index 0000000..14fafce --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorTest.php @@ -0,0 +1,245 @@ +urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); + $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->routeProvider = $this->getMock('Symfony\Cmf\Component\Routing\RouteProviderInterface'); + $this->route = $this->getMockBuilder('Symfony\Component\Routing\Route') + ->disableOriginalConstructor() + ->getMock(); + $this->routeProvider->expects($this->any()) + ->method('getRouteByName') + ->willReturn($this->route); + $this->requestStack = new RequestStack(); + $this->requestStack->push(new Request()); + $this->cachedUrlGenerator = new CachedUrlGenerator($this->urlGenerator, $this->cache, $this->routeProvider, $this->requestStack); + } + + /** + * Tests the generate method. + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generate() + */ + public function testGenerate() { + $this->urlGenerator->expects($this->once()) + ->method('generateFromRoute') + ->with('test_route') + ->will($this->returnValue('test-route-1')); + // First call will prime the cache. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromRoute('test_route')); + // Second call will fetch from the cache. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromRoute('test_route')); + } + + /** + * Tests the generate method with the same route name but different parameters. + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generate() + */ + public function testGenerateWithDifferentParameters() { + // We are generating URL's four times but since two of them are cached, + // we expect that generateFromRoute will only be called twice. + $this->urlGenerator->expects($this->exactly(2)) + ->method('generateFromRoute') + ->will($this->returnValueMap(array( + array('test_route', array('key' => 'value1'), array(), 'test-route-1/value1'), + array('test_route', array('key' => 'value2'), array(), 'test-route-1/value2'), + ))); + + // First call will prime the cache for key=value1. + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generate('test_route', array('key' => 'value1'))); + // Second call will fetch from the cache. + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generate('test_route', array('key' => 'value1'))); + // Third call uses the same route but since the parameters are different, + // the cache is not used and the generateFromRoute method will be called + // for the second time. + $this->assertEquals('test-route-1/value2', $this->cachedUrlGenerator->generate('test_route', array('key' => 'value2'))); + // Fourth call will fetch from the cache as it is now primed. + $this->assertEquals('test-route-1/value2', $this->cachedUrlGenerator->generate('test_route', array('key' => 'value2'))); + } + + /** + * Tests the generateFromPath method. + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generateFromPath() + */ + public function testGenerateFromPath() { + $this->urlGenerator->expects($this->once()) + ->method('generateFromPath') + ->with('test-route-1') + ->will($this->returnValue('test-route-1')); + + // First call will prime the cache. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1')); + // Second call will fetch from the cache. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1')); + } + + /** + * Tests the generate method with the same path but different options + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generateFromPath() + */ + public function testGenerateFromPathWithDifferentParameters() { + // We are generating URL's six times but since three of them are cached, + // we expect that generateFromPath will only be called three times. + $this->urlGenerator->expects($this->exactly(3)) + ->method('generateFromPath') + ->will($this->returnValueMap(array( + array('test-route-1', array('absolute' => TRUE), 'http://localhost/test-route-1'), + array('test-route-1', array('absolute' => FALSE), 'test-route-1'), + array('test-route-1', array('absolute' => TRUE), 'http://localhost/test-route-1'), + ))); + + // First call will prime the cache. + $this->assertEquals('http://localhost/test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => TRUE))); + // Second call will fetch from cache. + $this->assertEquals('http://localhost/test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => TRUE))); + // Third call uses the same path but since the options are different, the + // cache is not used and the generateFromPath method will be called for + // the second time. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => FALSE))); + // Fourth call will fetch from cache. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => FALSE))); + $new_request = Request::create('/foo'); + $this->requestStack->push($new_request); + // Fifth call uses the first path again but a new request. + $this->assertEquals('http://localhost/test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => TRUE))); + // Sixth call will fetch from cache. + $this->assertEquals('http://localhost/test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => TRUE))); + } + + + /** + * Tests the generateFromRoute method. + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generateFromRoute() + */ + public function testGenerateFromRoute() { + $this->urlGenerator->expects($this->once()) + ->method('generateFromRoute') + ->with('test_route') + ->will($this->returnValue('test-route-1')); + + // First call will prime the cache. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromRoute('test_route')); + // Second call will fetch from the cache. + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromRoute('test_route')); + } + + /** + * Tests the generateFromRoute method with the same path, different options. + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generateFromRoute() + */ + public function testGenerateFromRouteWithDifferentParameters() { + // We are generating URL's eight times but since two of them are cached, + // we expect that generateFromRoute will only be called four times. + $this->urlGenerator->expects($this->exactly(4)) + ->method('generateFromRoute') + ->will($this->returnValueMap(array( + array('test_route', array('key' => 'value1'), array(), 'test-route-1/value1'), + array('test_route', array('key' => 'value1'), array('absolute' => TRUE), 'http://localhost/test-route-1/value1'), + array('test_route', array('key' => 'value2'), array(), 'test-route-1/value2'), + array('test_route', array('key' => 'value2'), array('absolute' => TRUE), 'http://localhost/test-route-1/value2'), + ))); + + // First call will prime the cache. + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'))); + // Second call will fetch from the cache. + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'))); + // Third call uses the same route but since the options are different, the + // cache is not used and the generateFromRoute method will be called for + // the second time. + $this->assertEquals('http://localhost/test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'), array('absolute' => TRUE))); + // Fourth call will fetch from the cache. + $this->assertEquals('http://localhost/test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'), array('absolute' => TRUE))); + // Fifth call uses the same route but since the parameters are different, + // the cache is not used and the generateFromRoute method will be called for + // the third time. + $this->assertEquals('test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'))); + // Sixth call will fetch from the cache. + $this->assertEquals('test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'))); + // Seventh call uses the same route but since the options are different, the + // cache is not used and the generateFromRoute method will be called for + // the fourth time. + $this->assertEquals('http://localhost/test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'), array('absolute' => TRUE))); + // Eighth call will fetch from the cache. + $this->assertEquals('http://localhost/test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'), array('absolute' => TRUE))); + } + +}