diff --git a/core/core.services.yml b/core/core.services.yml index 6f8cce6..1bff04e 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 @@ -232,9 +239,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']] tags: diff --git a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php index a5dc955..ad5bdb5 100644 --- a/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/PathSubscriber.php @@ -9,7 +9,6 @@ use Drupal\Core\CacheDecorator\AliasManagerCacheDecorator; use Drupal\Core\PathProcessor\InboundPathProcessorInterface; -use Drupal\Core\Routing\PathBasedGeneratorInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseEvent; diff --git a/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php b/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php new file mode 100644 index 0000000..83d5a49 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/CachedUrlGenerator.php @@ -0,0 +1,198 @@ +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 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/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php b/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php index 79ed373..e40fbdc 100644 --- a/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php +++ b/core/lib/Drupal/Core/Routing/PathBasedGeneratorInterface.php @@ -8,11 +8,12 @@ namespace Drupal\Core\Routing; use Symfony\Component\HttpFoundation\Request; +use Symfony\Cmf\Component\Routing\VersatileGeneratorInterface; /** * Defines an interface for generating a url from a path as opposed to a route. */ -interface PathBasedGeneratorInterface { +interface PathBasedGeneratorInterface extends VersatileGeneratorInterface { /** * Generates an internal or external URL. 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 8eb4458..8ac4bb7 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php @@ -12,7 +12,7 @@ use Drupal\Core\Language\Language; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Path\AliasManagerInterface; -use Drupal\Core\Routing\UrlGenerator; +use Drupal\Core\Routing\PathBasedGeneratorInterface; use Drupal\menu_link\MenuLinkStorageControllerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -38,19 +38,23 @@ class MenuLinkFormController extends EntityFormController implements EntityContr /** * The URL generator. * - * @var \Drupal\Core\Routing\UrlGenerator + * @var \Drupal\Core\Routing\PathBasedGeneratorInterface */ protected $urlGenerator; /** * Constructs a new MenuLinkFormController object. * - * @param \Drupal\Core\Extension\ModuleHandlerInterface + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. + * @param MenuLinkStorageControllerInterface $menu_link_storage_controller + * The menu link storage controller * @param \Drupal\Core\Path\AliasManagerInterface $path_alias_manager * The path alias manager. + * @param \Drupal\Core\Routing\PathBasedGeneratorInterface $url_generator + * The url generator. */ - public function __construct(ModuleHandlerInterface $module_handler, MenuLinkStorageControllerInterface $menu_link_storage_controller, AliasManagerInterface $path_alias_manager, UrlGenerator $url_generator) { + public function __construct(ModuleHandlerInterface $module_handler, MenuLinkStorageControllerInterface $menu_link_storage_controller, AliasManagerInterface $path_alias_manager, PathBasedGeneratorInterface $url_generator) { parent::__construct($module_handler); $this->menuLinkStorageController = $menu_link_storage_controller; $this->pathAliasManager = $path_alias_manager; 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..0bd0a60 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Routing/CachedUrlGeneratorTest.php @@ -0,0 +1,124 @@ + '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\PathBasedGeneratorInterface'); + $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))); + } + +} + diff --git a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php index d6006ad..72308f0 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.