diff --git a/core/core.services.yml b/core/core.services.yml index 8a24ee7..3223c72 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -58,6 +58,13 @@ services: factory_method: get factory_service: cache_factory arguments: [path] + cache.url_generator: + class: Drupal\Core\Cache\CacheBackendInterface + tags: + - { name: cache.bin } + factory_method: get + factory_service: cache_factory + arguments: [url_generator] config.cachedstorage.storage: class: Drupal\Core\Config\FileStorage factory_class: Drupal\Core\Config\FileStorageFactory @@ -234,9 +241,12 @@ services: arguments: ['@router.route_provider'] calls: - [setFinalMatcher, ['@router.matcher.final_matcher']] - url_generator: + url_generator.uncached: class: Drupal\Core\Routing\UrlGenerator arguments: ['@router.route_provider', '@path_processor_manager', '@config.factory', '@settings'] + url_generator: + class: Drupal\Core\Routing\CachedUrlGenerator + arguments: ['@url_generator.uncached', '@cache.url_generator'] calls: - [setRequest, ['@?request']] - [setContext, ['@?router.request_context']] diff --git a/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php b/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php new file mode 100644 index 0000000..2f9c58d --- /dev/null +++ b/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php @@ -0,0 +1,214 @@ +urlGenerator = $url_generator; + $this->cache = $cache; + } + + /** + * Writes the cache of generated URLs. + */ + protected function writeCache() { + if ($this->cacheNeedsWriting && !empty($this->cachedUrls) && !empty($this->cacheKey)) { + // Set the URL cache to expire in 24 hours. + $expire = REQUEST_TIME + (60 * 60 * 24); + $this->cache->set($this->cacheKey, $this->cachedUrls, $expire); + } + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) { + // We can only cache the url if $name is a string, not the actual route + // object. + $parameter_hash = hash('sha256', serialize($parameters)) . (string) $referenceType; + if (is_scalar($name) && isset($this->cachedUrls[self::ROUTE_CACHE_PREFIX . $name . $parameter_hash])) { + return $this->cachedUrls[self::ROUTE_CACHE_PREFIX . $name . $parameter_hash]; + } + $url = $this->urlGenerator->generate($name, $parameters, $referenceType); + if (is_scalar($name)) { + // Cache the url for this route. + $this->cachedUrls[self::ROUTE_CACHE_PREFIX . $name . $parameter_hash] = $url; + $this->cacheNeedsWriting = TRUE; + } + return $url; + } + + /** + * {@inheritdoc} + */ + public function generateFromPath($path = NULL, $options = array()) { + $options_hash = hash('sha256', serialize($options)); + if (isset($this->cachedUrls[self::PATH_CACHE_PREFIX . $path . $options_hash])) { + return $this->cachedUrls[self::PATH_CACHE_PREFIX . $path . $options_hash]; + } + $url = $this->urlGenerator->generateFromPath($path, $options); + // Cache the url for this route. + $this->cachedUrls[self::PATH_CACHE_PREFIX . $path . $options_hash] = $url; + $this->cacheNeedsWriting = TRUE; + return $url; + } + + /** + * {@inheritdoc} + */ + public function generateFromRoute($name, $parameters = array(), $options = array()) { + $hash = hash('sha256', serialize($name) . serialize($options) . serialize($parameters)); + if (isset($this->cachedUrls[self::PATH_CACHE_PREFIX . $hash])) { + return $this->cachedUrls[self::PATH_CACHE_PREFIX . $hash]; + } + $url = $this->urlGenerator->generateFromRoute($name, $parameters, $options); + // Cache the url for this route. + $this->cachedUrls[self::PATH_CACHE_PREFIX . $hash] = $url; + $this->cacheNeedsWriting = TRUE; + return $url; + } + + /** + * {@inheritdoc} + */ + public function setRequest(Request $request) { + $this->cacheKey = $request->attributes->get('system_path'); + $cached = $this->cache->get($this->cacheKey); + if ($cached) { + $this->cachedUrls = $cached->data; + } + $this->urlGenerator->setRequest($request); + } + + /** + * {@inheritdoc} + */ + public function setBaseUrl($url) { + $this->urlGenerator->setBaseUrl($url); + } + + /** + * {@inheritdoc} + */ + public function setBasePath($path) { + $this->urlGenerator->setBasePath($path); + } + + /** + * {@inheritdoc} + */ + public function setScriptPath($path) { + $this->urlGenerator->setScriptPath($path); + } + + /** + * {@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(RequestContext $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/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php index e94a0a6..ebfadd6 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php @@ -10,7 +10,8 @@ use Drupal\Core\Entity\EntityFormController; use Drupal\Core\Language\Language; use Drupal\Core\Path\AliasManagerInterface; -use Drupal\Core\Routing\UrlGenerator; +use Drupal\Core\Routing\PathBasedGeneratorInterface; +use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\menu_link\MenuLinkStorageControllerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -36,7 +37,7 @@ class MenuLinkFormController extends EntityFormController { /** * The URL generator. * - * @var \Drupal\Core\Routing\UrlGenerator + * @var \Drupal\Core\Routing\UrlGeneratorInterface */ protected $urlGenerator; @@ -47,10 +48,10 @@ class MenuLinkFormController extends EntityFormController { * The menu link storage. * @param \Drupal\Core\Path\AliasManagerInterface $path_alias_manager * The path alias manager. - * @param \Drupal\Core\Routing\UrlGenerator $url_generator + * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator * The URL generator. */ - public function __construct(MenuLinkStorageControllerInterface $menu_link_storage_controller, AliasManagerInterface $path_alias_manager, UrlGenerator $url_generator) { + public function __construct(MenuLinkStorageControllerInterface $menu_link_storage_controller, AliasManagerInterface $path_alias_manager, UrlGeneratorInterface $url_generator) { $this->menuLinkStorageController = $menu_link_storage_controller; $this->pathAliasManager = $path_alias_manager; $this->urlGenerator = $url_generator; 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..f1a8a14 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorTest.php @@ -0,0 +1,163 @@ + 'Cached UrlGenerator', + 'description' => 'Confirm that the cached UrlGenerator is functioning properly.', + 'group' => 'Routing', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); + $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + + $this->cachedUrlGenerator = new CachedUrlGenerator($this->urlGenerator, $this->cache); + } + + /** + * Tests the generate method. + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generate() + */ + public function testGenerate() { + $this->urlGenerator->expects($this->once()) + ->method('generate') + ->with('test_route') + ->will($this->returnValue('test-route-1')); + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generate('test_route')); + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generate('test_route')); + } + + /** + * Tests the generate method with the same route name but different parameters. + * + * @see \Drupal\Core\Routing\CachedUrlGenerator::generate() + */ + public function testGenerateWithDifferentParameters() { + $this->urlGenerator->expects($this->exactly(2)) + ->method('generate') + ->will($this->returnValueMap(array( + array('test_route', array('key' => 'value1'), CachedUrlGenerator::ABSOLUTE_PATH, 'test-route-1/value1'), + array('test_route', array('key' => 'value2'), CachedUrlGenerator::ABSOLUTE_PATH, 'test-route-1/value2'), + ))); + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generate('test_route', array('key' => 'value1'))); + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generate('test_route', array('key' => 'value1'))); + $this->assertEquals('test-route-1/value2', $this->cachedUrlGenerator->generate('test_route', array('key' => 'value2'))); + $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')); + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1')); + $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() { + $this->urlGenerator->expects($this->exactly(2)) + ->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'), + ))); + $this->assertEquals('http://localhost/test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => TRUE))); + $this->assertEquals('http://localhost/test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => TRUE))); + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => FALSE))); + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromPath('test-route-1', array('absolute' => FALSE))); + } + + + /** + * 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')); + $this->assertEquals('test-route-1', $this->cachedUrlGenerator->generateFromRoute('test_route')); + $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() { + $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'), + ))); + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'))); + $this->assertEquals('test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'))); + $this->assertEquals('http://localhost/test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'), array('absolute' => TRUE))); + $this->assertEquals('http://localhost/test-route-1/value1', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value1'), array('absolute' => TRUE))); + $this->assertEquals('test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'))); + $this->assertEquals('test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'))); + $this->assertEquals('http://localhost/test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'), array('absolute' => TRUE))); + $this->assertEquals('http://localhost/test-route-1/value2', $this->cachedUrlGenerator->generateFromRoute('test_route', array('key' => 'value2'), array('absolute' => TRUE))); + } + +} + diff --git a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php index 75d051d..88eb5fe 100644 --- a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php @@ -8,12 +8,8 @@ namespace Drupal\Tests\Core\Routing; use Drupal\Component\Utility\Settings; -use Drupal\Core\Config\ConfigFactory; -use Drupal\Core\Config\NullStorage; -use Drupal\Core\Config\Context\ConfigContextFactory; use Drupal\Core\PathProcessor\PathProcessorAlias; use Drupal\Core\PathProcessor\PathProcessorManager; -use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -22,6 +18,7 @@ use Drupal\Tests\UnitTestCase; use Drupal\Core\Routing\UrlGenerator; +use Drupal\Core\Routing\CachedUrlGenerator; /** * Basic tests for the Route.