diff --git a/core/core.services.yml b/core/core.services.yml index 2b27f69..c5385e0 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -520,6 +520,12 @@ services: arguments: ['@state'] tags: - { name: event_subscriber } + router.cached: + class: Drupal\Core\Routing\CachedUrlMatcher + arguments: ['@cache.data'] + calls: + - [setContext, ['@router.request_context']] + - [setUrlMatcher, ['@router.no_access_checks']] entity.query: class: Drupal\Core\Entity\Query\QueryFactory arguments: ['@entity.manager'] @@ -711,6 +717,8 @@ services: access_manager: class: Drupal\Core\Access\AccessManager arguments: ['@router.route_provider', '@paramconverter_manager', '@access_arguments_resolver_factory', '@current_user', '@access_manager.check_provider'] + calls: + - [setUrlMatcher, ['@router.cached']] access_manager.check_provider: class: Drupal\Core\Access\CheckProvider calls: diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php index 576ffb7..d9583f5 100644 --- a/core/lib/Drupal/Core/Access/AccessManager.php +++ b/core/lib/Drupal/Core/Access/AccessManager.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; /** * Attaches access check services to routes and runs them on request. @@ -60,6 +61,13 @@ class AccessManager implements AccessManagerInterface { protected $checkProvider; /** + * The URL matcher. + * + * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface + */ + protected $urlMatcher; + + /** * Constructs a AccessManager instance. * * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider @@ -85,7 +93,7 @@ public function __construct(RouteProviderInterface $route_provider, ParamConvert */ public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL, $return_as_object = FALSE) { try { - $route = $this->routeProvider->getRouteByName($route_name, $parameters); + $route = $this->routeProvider->getRouteByName($route_name); // ParamConverterManager relies on the route object being available // from the parameters array. @@ -221,5 +229,19 @@ protected function performCheck($service_id, ArgumentsResolverInterface $argumen return $service_access; } + /** + * {@inheritdoc} + */ + public function checkPath($path, AccountInterface $account = NULL, $return_as_object = FALSE) { + $path = '/' . ltrim($path, '/'); + $attributes = $this->urlMatcher->match($path); + return $this->checkNamedRoute($attributes[RouteObjectInterface::ROUTE_NAME], $attributes, $account, $return_as_object); + } + + public function setUrlMatcher(UrlMatcherInterface $url_matcher) { + $this->urlMatcher = $url_matcher; + + return $this; + } } diff --git a/core/lib/Drupal/Core/Access/AccessManagerInterface.php b/core/lib/Drupal/Core/Access/AccessManagerInterface.php index 206a801..9dbabc1 100644 --- a/core/lib/Drupal/Core/Access/AccessManagerInterface.php +++ b/core/lib/Drupal/Core/Access/AccessManagerInterface.php @@ -100,4 +100,17 @@ public function checkRequest(Request $request, AccountInterface $account = NULL, */ public function check(RouteMatchInterface $route_match, AccountInterface $account = NULL, Request $request = NULL, $return_as_object = FALSE); + /** + * Checks access for a given path. + * + * @param string $path + * The path. + * @param \Drupal\Core\Session\AccountInterface $account + * (optional) Run access checks for this account. Defaults to the current + * user. + * @param bool $return_as_object + * (optional) Defaults to FALSE. + */ + public function checkPath($path, AccountInterface $account = NULL, $return_as_object = FALSE); + } diff --git a/core/lib/Drupal/Core/Routing/CachedUrlMatcher.php b/core/lib/Drupal/Core/Routing/CachedUrlMatcher.php new file mode 100644 index 0000000..aa9adf6 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/CachedUrlMatcher.php @@ -0,0 +1,85 @@ +cacheBackend = $cache_backend; + } + + /** + * {@inheritdoc} + */ + public function __call($name, $arguments) { + return call_user_func_array($name, $arguments); + } + + public function setUrlMatcher(UrlMatcherInterface $url_matcher) { + $this->urlMatcher = $url_matcher; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) { + if ($cache = $this->cacheBackend->get('cached_url_matcher.' . $pathinfo)) { + return $cache->data; + } + else { + $result = $this->urlMatcher->match($pathinfo); + $this->cacheBackend->set('cached_url_matcher.' . $pathinfo, $result); + return $result; + } + } + + /** + * {@inheritdoc} + */ + public function setContext(BaseRequestContext $context) { + $this->context = $context; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getContext() { + return $this->context; + } + +} diff --git a/core/modules/system/src/Tests/Common/FormatDateTest.php b/core/modules/system/src/Tests/Common/FormatDateTest.php index f8ad3b7..fd39f9f 100644 --- a/core/modules/system/src/Tests/Common/FormatDateTest.php +++ b/core/modules/system/src/Tests/Common/FormatDateTest.php @@ -97,6 +97,7 @@ function testFormatDate() { // Change the default language and timezone. \Drupal::config('system.site')->set('langcode', static::LANGCODE)->save(); + \Drupal::service('language_manager')->reset(); date_default_timezone_set('America/Los_Angeles'); $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.'); diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php index 98cffaf..8614ebd 100644 --- a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php @@ -88,6 +88,13 @@ class AccessManagerTest extends UnitTestCase { protected $checkProvider; /** + * The mocked URL matcher. + * + * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $urlMatcher; + + /** * {@inheritdoc} */ protected function setUp() { @@ -104,9 +111,9 @@ protected function setUp() { $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); $map = array(); foreach ($this->routeCollection->all() as $name => $route) { - $map[] = array($name, array(), $route); + $map[] = array($name, $route); } - $map[] = array('test_route_4', array('value' => 'example'), $this->routeCollection->get('test_route_4')); + $map[] = array('test_route_4', $this->routeCollection->get('test_route_4')); $this->routeProvider->expects($this->any()) ->method('getRouteByName') ->will($this->returnValueMap($map)); @@ -125,7 +132,10 @@ protected function setUp() { $this->checkProvider = new CheckProvider(); $this->checkProvider->setContainer($this->container); + $this->urlMatcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); + $this->accessManager = new AccessManager($this->routeProvider, $this->paramConverter, $this->argumentsResolverFactory, $this->currentUser, $this->checkProvider); + $this->accessManager->setUrlMatcher($this->urlMatcher); } /** @@ -459,7 +469,7 @@ public function testCheckNamedRouteWithUpcastedValues() { $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); $this->routeProvider->expects($this->any()) ->method('getRouteByName') - ->with('test_route_1', array('value' => 'example')) + ->with('test_route_1') ->will($this->returnValue($route)); $map = array(); @@ -508,7 +518,7 @@ public function testCheckNamedRouteWithDefaultValue() { $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); $this->routeProvider->expects($this->any()) ->method('getRouteByName') - ->with('test_route_1', array()) + ->with('test_route_1') ->will($this->returnValue($route)); $map = array(); @@ -656,6 +666,32 @@ protected function setupAccessArgumentsResolverFactory($constraint = NULL) { })); } + /** + * Tests checkPath(). + * + * @covers ::checkPath + */ + public function testCheckPath() { + $this->urlMatcher->expects($this->once()) + ->method('match') + ->with('/test') + ->willReturn([ + RouteObjectInterface::ROUTE_NAME => 'test_route_2', + RouteObjectInterface::ROUTE_OBJECT => $this->routeCollection->get('test_route_2'), + ]); + $this->setupAccessChecker(); + $this->checkProvider->setChecks($this->routeCollection); + $this->setupAccessArgumentsResolverFactory(); + + $this->paramConverter->expects($this->at(0)) + ->method('convert') + ->will($this->returnValue([])); + + // Tests the access with routes with parameters without given request. + $this->assertTrue($this->accessManager->checkPath('test', $this->account)); + + } + } /** diff --git a/core/tests/Drupal/Tests/Core/Routing/CachedUrlMatcherTest.php b/core/tests/Drupal/Tests/Core/Routing/CachedUrlMatcherTest.php new file mode 100644 index 0000000..39ea3d5 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Routing/CachedUrlMatcherTest.php @@ -0,0 +1,104 @@ +cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->urlMatcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); + + $this->cachedUrlMatcher = new CachedUrlMatcher($this->cache); + $this->cachedUrlMatcher->setUrlMatcher($this->urlMatcher); + } + + /** + * @covers ::setContext + * @covers ::getContext + */ + public function testSetContext() { + $request_context = new RequestContext(); + $this->assertSame($this->cachedUrlMatcher, $this->cachedUrlMatcher->setContext($request_context)); + $this->assertSame($request_context, $this->cachedUrlMatcher->getContext()); + } + + /** + * @covers ::match + */ + public function testMatchWithoutCachedMatching() { + $this->cache->expects($this->at(0)) + ->method('get') + ->with('cached_url_matcher./test'); + $attributes = [RouteObjectInterface::ROUTE_NAME => 'test_route']; + $this->cache->expects($this->at(1)) + ->method('set') + ->with('cached_url_matcher./test', $attributes); + + $this->urlMatcher->expects($this->once()) + ->method('match') + ->with('/test') + ->willReturn($attributes); + + $this->cachedUrlMatcher->match('/test'); + } + + /** + * @covers ::match + */ + public function testMatchWithCachedMatching() { + $attributes = [RouteObjectInterface::ROUTE_NAME => 'test_route']; + $this->cache->expects($this->at(0)) + ->method('get') + ->with('cached_url_matcher./test') + ->willReturn((object) ['data' => $attributes]); + $this->cache->expects($this->never()) + ->method('set'); + + $this->urlMatcher->expects($this->never()) + ->method('match'); + + $this->cachedUrlMatcher->match('/test'); + } + +} +