diff --git a/core/lib/Drupal/Core/Path/AliasManager.php b/core/lib/Drupal/Core/Path/AliasManager.php index 97a78c4..8046dfc 100644 --- a/core/lib/Drupal/Core/Path/AliasManager.php +++ b/core/lib/Drupal/Core/Path/AliasManager.php @@ -14,11 +14,18 @@ class AliasManager implements AliasManagerInterface, CacheDecoratorInterface { /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** * The path_alias storage. * * @var \Drupal\Core\Path\PathAliasStorageInterface */ - protected $storage; + protected $pathAliasStorage; /** * Cache backend service. @@ -106,7 +113,7 @@ class AliasManager implements AliasManagerInterface, CacheDecoratorInterface { * Cache backend. */ public function __construct(EntityTypeManagerInterface $entity_type_manager, AliasWhitelistInterface $whitelist, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) { - $this->storage = $entity_type_manager->getStorage('path_alias'); + $this->entityTypeManager = $entity_type_manager; $this->languageManager = $language_manager; $this->whitelist = $whitelist; $this->cache = $cache; @@ -167,7 +174,7 @@ public function getPathByAlias($alias, $langcode = NULL) { } // Look for path in storage. - if ($path = $this->storage->lookupPathSource($alias, $langcode)) { + if ($path = $this->getPathAliasStorage()->lookupPathSource($alias, $langcode)) { $this->lookupMap[$langcode][$path] = $alias; return $path; } @@ -221,7 +228,7 @@ public function getAliasByPath($path, $langcode = NULL) { // Load paths from cache. if (!empty($this->preloadedPathLookups[$langcode])) { - $this->lookupMap[$langcode] = $this->storage->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode); + $this->lookupMap[$langcode] = $this->getPathAliasStorage()->preloadPathAlias($this->preloadedPathLookups[$langcode], $langcode); // Keep a record of paths with no alias to avoid querying twice. $this->noAlias[$langcode] = array_flip(array_diff_key($this->preloadedPathLookups[$langcode], array_keys($this->lookupMap[$langcode]))); } @@ -238,7 +245,7 @@ public function getAliasByPath($path, $langcode = NULL) { } // Try to load alias from storage. - if ($alias = $this->storage->lookupPathAlias($path, $langcode)) { + if ($alias = $this->getPathAliasStorage()->lookupPathAlias($path, $langcode)) { $this->lookupMap[$langcode][$path] = $alias; return $alias; } @@ -298,4 +305,20 @@ protected function getRequestTime() { return defined('REQUEST_TIME') ? REQUEST_TIME : (int) $_SERVER['REQUEST_TIME']; } + /** + * Returns the path alias entity storage handler. + * + * We can not store it in the constructor because that leads to a circular + * dependency in the service container. + * + * @return \Drupal\Core\Path\PathAliasStorageInterface + * The path alias entity storage. + */ + protected function getPathAliasStorage() { + if (!$this->pathAliasStorage) { + $this->pathAliasStorage = $this->entityTypeManager->getStorage('path_alias'); + } + return $this->pathAliasStorage; + } + } diff --git a/core/lib/Drupal/Core/Path/AliasWhitelist.php b/core/lib/Drupal/Core/Path/AliasWhitelist.php index d35f37f..1672d94 100644 --- a/core/lib/Drupal/Core/Path/AliasWhitelist.php +++ b/core/lib/Drupal/Core/Path/AliasWhitelist.php @@ -21,11 +21,18 @@ class AliasWhitelist extends CacheCollector implements AliasWhitelistInterface { protected $state; /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** * The path_alias storage. * * @var \Drupal\Core\Path\PathAliasStorageInterface */ - protected $aliasStorage; + protected $pathAliasStorage; /** * Constructs an AliasWhitelist object. @@ -44,7 +51,7 @@ class AliasWhitelist extends CacheCollector implements AliasWhitelistInterface { public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) { parent::__construct($cid, $cache, $lock); $this->state = $state; - $this->aliasStorage = $entity_type_manager->getStorage('path_alias'); + $this->entityTypeManager = $entity_type_manager; } /** @@ -102,7 +109,7 @@ public function get($offset) { * {@inheritdoc} */ public function resolveCacheMiss($root) { - $exists = $this->aliasStorage->pathHasMatchingAlias('/' . $root); + $exists = $this->getPathAliasStorage()->pathHasMatchingAlias('/' . $root); $this->storage[$root] = $exists; $this->persist($root); if ($exists) { @@ -118,4 +125,20 @@ public function clear() { $this->loadMenuPathRoots(); } + /** + * Returns the path alias entity storage handler. + * + * We can not store it in the constructor because that leads to a circular + * dependency in the service container. + * + * @return \Drupal\Core\Path\PathAliasStorageInterface + * The path alias entity storage. + */ + protected function getPathAliasStorage() { + if (!$this->pathAliasStorage) { + $this->pathAliasStorage = $this->entityTypeManager->getStorage('path_alias'); + } + return $this->pathAliasStorage; + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php b/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php index 2e986d1..cc3e244 100644 --- a/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php +++ b/core/tests/Drupal/KernelTests/Core/Path/AliasStorageTest.php @@ -8,6 +8,7 @@ /** * @coversDefaultClass \Drupal\Core\Path\AliasStorage * @group path + * @group legacy */ class AliasStorageTest extends KernelTestBase { @@ -51,23 +52,92 @@ public function testLoad() { } /** - * @covers ::lookupPathAlias + * @covers ::load + * @covers ::save + * @covers ::delete */ - public function testLookupPathAlias() { - $this->storage->save('/test-source-Case', '/test-alias'); + public function testCRUD() { + // Prepare database table. + $connection = \Drupal::database(); + + $aliases = $this->sampleUrlAliases(); + + // Create a few aliases + foreach ($aliases as $idx => $alias) { + $this->storage->save($alias['source'], $alias['alias'], $alias['langcode']); + + $result = $connection->query('SELECT * FROM {path_alias} WHERE path = :path AND alias= :alias AND langcode = :langcode', [':path' => $alias['source'], ':alias' => $alias['alias'], ':langcode' => $alias['langcode']]); + $rows = $result->fetchAll(); + + $this->assertCount(1, $rows, "Created an entry for {$alias['alias']}."); + + // Cache the pid for further tests. + $aliases[$idx]['pid'] = $rows[0]->id; + } + + // Load a few aliases + foreach ($aliases as $alias) { + $pid = $alias['pid']; + $loadedAlias = $this->storage->load(['pid' => $pid]); + $this->assertEquals($alias, $loadedAlias, "Loaded the expected path with pid $pid."); + } + + // Load alias by source path. + $loadedAlias = $this->storage->load(['source' => '/node/1']); + $this->assertEquals('/alias_for_node_1_und', $loadedAlias['alias'], 'The last created alias loaded by default.'); - $this->assertEquals('/test-alias', $this->storage->lookupPathAlias('/test-source-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); - $this->assertEquals('/test-alias', $this->storage->lookupPathAlias('/test-source-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); + // Update a few aliases + foreach ($aliases as $alias) { + $fields = $this->storage->save($alias['source'], $alias['alias'] . '_updated', $alias['langcode'], $alias['pid']); + + $this->assertEquals($alias['alias'], $fields['original']['alias']); + + $result = $connection->query('SELECT id FROM {path_alias} WHERE path = :path AND alias= :alias AND langcode = :langcode', [':path' => $alias['source'], ':alias' => $alias['alias'] . '_updated', ':langcode' => $alias['langcode']]); + $pid = $result->fetchField(); + + $this->assertEquals($alias['pid'], $pid, "Updated entry for pid $pid."); + } + + // Delete a few aliases + foreach ($aliases as $alias) { + $pid = $alias['pid']; + $this->storage->delete(['pid' => $pid]); + + $result = $connection->query('SELECT * FROM {path_alias} WHERE id = :id', [':id' => $pid]); + $rows = $result->fetchAll(); + + $this->assertCount(0, $rows, "Deleted entry with pid $pid."); + } } /** - * @covers ::lookupPathSource + * Returns an array of URL aliases for testing. + * + * @return array of URL alias definitions. */ - public function testLookupPathSource() { - $this->storage->save('/test-source', '/test-alias-Case'); - - $this->assertEquals('/test-source', $this->storage->lookupPathSource('/test-alias-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); - $this->assertEquals('/test-source', $this->storage->lookupPathSource('/test-alias-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); + protected function sampleUrlAliases() { + return [ + [ + 'source' => '/node/1', + 'alias' => '/alias_for_node_1_en', + 'langcode' => 'en', + ], + [ + 'source' => '/node/2', + 'alias' => '/alias_for_node_2_en', + 'langcode' => 'en', + ], + [ + 'source' => '/node/1', + 'alias' => '/alias_for_node_1_fr', + 'langcode' => 'fr', + ], + [ + 'source' => '/node/1', + 'alias' => '/alias_for_node_1_und', + 'langcode' => 'und', + ], + ]; } /** diff --git a/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php b/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php index 0311195..ff80945 100644 --- a/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php +++ b/core/tests/Drupal/KernelTests/Core/Path/AliasTest.php @@ -3,17 +3,28 @@ namespace Drupal\KernelTests\Core\Path; use Drupal\Core\Cache\MemoryCounterBackend; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Path\AliasManager; use Drupal\Core\Path\AliasWhitelist; use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\Traits\Core\PathAliasTestTrait; /** * Tests path alias CRUD and lookup functionality. * + * @coversDefaultClass \Drupal\Core\Path\PathAliasStorage + * * @group path */ class AliasTest extends KernelTestBase { + use PathAliasTestTrait; + + /** + * @var \Drupal\Core\Path\PathAliasStorage + */ + protected $pathAliasStorage; + /** * {@inheritdoc} */ @@ -25,64 +36,64 @@ protected function setUp() { \Drupal::state()->set('router.path_roots', ['user', 'admin']); $this->installEntitySchema('path_alias'); + $this->pathAliasStorage = $this->container->get('entity_type.manager')->getStorage('path_alias'); + } + + /** + * @covers ::lookupPathAlias + */ + public function testLookupPathAlias() { + $this->createPathAlias('/test-source-Case', '/test-alias'); + + $this->assertEquals('/test-alias', $this->pathAliasStorage->lookupPathAlias('/test-source-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); + $this->assertEquals('/test-alias', $this->pathAliasStorage->lookupPathAlias('/test-source-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); } + /** + * @covers ::lookupPathSource + */ + public function testLookupPathSource() { + $this->createPathAlias('/test-source', '/test-alias-Case'); + + $this->assertEquals('/test-source', $this->pathAliasStorage->lookupPathSource('/test-alias-Case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); + $this->assertEquals('/test-source', $this->pathAliasStorage->lookupPathSource('/test-alias-case', LanguageInterface::LANGCODE_NOT_SPECIFIED)); + } + + /** + * @covers \Drupal\Core\Path\AliasManager::getPathByAlias + * @covers \Drupal\Core\Path\AliasManager::getAliasByPath + */ public function testLookupPath() { // Create AliasManager and Path object. $aliasManager = $this->container->get('path.alias_manager'); - $aliasStorage = $this->container->get('entity_type.manager')->getStorage('path_alias'); // Test the situation where the source is the same for multiple aliases. // Start with a language-neutral alias, which we will override. - /** @var \Drupal\Core\Path\PathAliasInterface $path_alias */ - $path_alias = $aliasStorage->create([ - 'path' => "/user/1", - 'alias' => '/foo', - ]); - - $aliasStorage->save($path_alias); + $path_alias = $this->createPathAlias('/user/1', '/foo'); $this->assertEqual($aliasManager->getAliasByPath($path_alias->getPath()), $path_alias->getAlias(), 'Basic alias lookup works.'); $this->assertEqual($aliasManager->getPathByAlias($path_alias->getAlias()), $path_alias->getPath(), 'Basic source lookup works.'); // Create a language specific alias for the default language (English). - $path_alias = $aliasStorage->create([ - 'path' => "/user/1", - 'alias' => "/users/Dries", - 'langcode' => 'en', - ]); - $aliasStorage->save($path_alias); + $path_alias = $this->createPathAlias('/user/1', '/users/Dries', 'en'); + // Hook that clears cache is not executed with unit tests. \Drupal::service('path.alias_manager')->cacheClear(); $this->assertEqual($aliasManager->getAliasByPath($path_alias->getPath()), $path_alias->getAlias(), 'English alias overrides language-neutral alias.'); $this->assertEqual($aliasManager->getPathByAlias($path_alias->getAlias()), $path_alias->getPath(), 'English source overrides language-neutral source.'); // Create a language-neutral alias for the same path, again. - $path_alias = $aliasStorage->create([ - 'path' => "/user/1", - 'alias' => '/bar', - ]); - $aliasStorage->save($path_alias); + $path_alias = $this->createPathAlias('/user/1', '/bar'); $this->assertEqual($aliasManager->getAliasByPath($path_alias->getPath()), "/users/Dries", 'English alias still returned after entering a language-neutral alias.'); // Create a language-specific (xx-lolspeak) alias for the same path. - $path_alias = $aliasStorage->create([ - 'path' => "/user/1", - 'alias' => '/LOL', - 'langcode' => 'xx-lolspeak', - ]); - $aliasStorage->save($path_alias); + $path_alias = $this->createPathAlias('/user/1', '/LOL', 'xx-lolspeak'); $this->assertEqual($aliasManager->getAliasByPath($path_alias->getPath()), "/users/Dries", 'English alias still returned after entering a LOLspeak alias.'); // The LOLspeak alias should be returned if we really want LOLspeak. $this->assertEqual($aliasManager->getAliasByPath($path_alias->getPath(), 'xx-lolspeak'), '/LOL', 'LOLspeak alias returned if we specify xx-lolspeak to the alias manager.'); // Create a new alias for this path in English, which should override the // previous alias for "user/1". - $path_alias = $aliasStorage->create([ - 'path' => "/user/1", - 'alias' => '/users/my-new-path', - 'langcode' => 'en', - ]); - $aliasStorage->save($path_alias); + $path_alias = $this->createPathAlias('/user/1', '/users/my-new-path', 'en'); // Hook that clears cache is not executed with unit tests. $aliasManager->cacheClear(); $this->assertEqual($aliasManager->getAliasByPath($path_alias->getPath()), $path_alias->getAlias(), 'Recently created English alias returned.'); @@ -90,18 +101,15 @@ public function testLookupPath() { // Remove the English aliases, which should cause a fallback to the most // recently created language-neutral alias, 'bar'. - $entities = $aliasStorage->loadByProperties(['langcode' => 'en']); - $aliasStorage->delete($entities); + $entities = $this->pathAliasStorage->loadByProperties(['langcode' => 'en']); + $this->pathAliasStorage->delete($entities); // Hook that clears cache is not executed with unit tests. $aliasManager->cacheClear(); $this->assertEqual($aliasManager->getAliasByPath($path_alias->getPath()), '/bar', 'Path lookup falls back to recently created language-neutral alias.'); // Test the situation where the alias and language are the same, but // the source differs. The newer alias record should be returned. - $aliasStorage->create([ - 'path' => "/user/2", - 'alias' => '/bar', - ])->save(); + $this->createPathAlias('/user/2', '/bar'); // Hook that clears cache is not executed with unit tests. $aliasManager->cacheClear(); @@ -115,7 +123,6 @@ public function testWhitelist() { $memoryCounterBackend = new MemoryCounterBackend(); // Create AliasManager and Path object. - $aliasStorage = $this->container->get('entity_type.manager')->getStorage('path_alias'); $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('entity_type.manager')); $aliasManager = new AliasManager($this->container->get('entity_type.manager'), $whitelist, $this->container->get('language_manager'), $memoryCounterBackend); @@ -128,28 +135,22 @@ public function testWhitelist() { $this->assertNull($whitelist->get($this->randomMachineName())); // Add an alias for user/1, user should get whitelisted now. - $aliasStorage->create([ - 'path' => '/user/1', - 'alias' => '/' . $this->randomMachineName(), - ])->save(); + $this->createPathAlias('/user/1', '/' . $this->randomMachineName()); $aliasManager->cacheClear(); $this->assertTrue($whitelist->get('user')); $this->assertNull($whitelist->get('admin')); $this->assertNull($whitelist->get($this->randomMachineName())); // Add an alias for admin, both should get whitelisted now. - $aliasStorage->create([ - 'path' => '/admin/something', - 'alias' => '/' . $this->randomMachineName(), - ])->save(); + $this->createPathAlias('/admin/something', '/' . $this->randomMachineName()); $aliasManager->cacheClear(); $this->assertTrue($whitelist->get('user')); $this->assertTrue($whitelist->get('admin')); $this->assertNull($whitelist->get($this->randomMachineName())); // Remove the user alias again, whitelist entry should be removed. - $entities = $aliasStorage->loadByProperties(['path' => '/user/1']); - $aliasStorage->delete($entities); + $entities = $this->pathAliasStorage->loadByProperties(['path' => '/user/1']); + $this->pathAliasStorage->delete($entities); $aliasManager->cacheClear(); $this->assertNull($whitelist->get('user')); $this->assertTrue($whitelist->get('admin')); @@ -182,7 +183,6 @@ public function testWhitelistCacheDeletionMidRequest() { $memoryCounterBackend = new MemoryCounterBackend(); // Create AliasManager and Path object. - $aliasStorage = $this->container->get('entity_type.manager')->getStorage('path_alias'); $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $this->container->get('entity_type.manager')); $aliasManager = new AliasManager($this->container->get('entity_type.manager'), $whitelist, $this->container->get('language_manager'), $memoryCounterBackend); @@ -190,8 +190,8 @@ public function testWhitelistCacheDeletionMidRequest() { $this->assertFalse($memoryCounterBackend->get('path_alias_whitelist')); // Add some aliases for both menu routes we have. - $aliasStorage->create(['path' => '/admin/something', 'alias' => '/' . $this->randomMachineName()])->save(); - $aliasStorage->create(['path' => '/user/something', 'alias' => '/' . $this->randomMachineName()])->save(); + $this->createPathAlias('/admin/something', '/' . $this->randomMachineName()); + $this->createPathAlias('/user/something', '/' . $this->randomMachineName()); $aliasManager->cacheClear(); // Lookup admin path in whitelist. It will query the DB and figure out