Problem/Motivation

When you write a routing.yml file with a _custom_access check which points to a non existing method/class you get the following message:

InvalidArgumentException: The controller for URI "" is not callable. in Drupal\Core\Controller\ControllerResolver->getControllerFromDefinition() (line 81 of core/lib/Drupal/Core/Controller/ControllerResolver.php).
Drupal\Core\Access\CustomAccessCheck->access(Object, Object, Object)
call_user_func_array(Array, Array)
Drupal\Core\Access\AccessManager->performCheck('access_check.custom', Object)
Drupal\Core\Access\AccessManager->checkAll(Array, Object)
Drupal\Core\Access\AccessManager->check(Object, Object, Object, )
Drupal\Core\Access\AccessManager->checkRequest(Object, Object)
Drupal\Core\Routing\AccessAwareRouter->checkAccess(Object)
Drupal\Core\Routing\AccessAwareRo

Its not helpful

Proposed resolution

CustomAccessCheck should catch the InvalidArgumentException and convert it into a new exception
which explains which route / custom access check exactly is broken

Remaining tasks

User interface changes

API changes

Comments

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

Issue tags: +php-novice

This should be a php-novice task.

philipnorton42’s picture

Assigned: Unassigned » philipnorton42
philipnorton42’s picture

I have added a \BadMethodCallException here, which is still part of the SPL library and seems to make sense in this context. This now produces a the following message when this situation is encountered:

BadMethodCallException: The "\Drupal\mmodule\Controller\MyModuleController::accesscallback" method is not callable as a _custom_access callback in route "/mymodule" in Drupal\Core\Access\CustomAccessCheck->access() (line 67 of /core/lib/Drupal/Core/Access/CustomAccessCheck.php).

philipnorton42’s picture

Assigned: philipnorton42 » Unassigned
Status: Active » Needs review
philipnorton42’s picture

I did try to create a test for this particular exception being thrown, but due to the way in which the objects are mocked the exceptions are not produced when running under the testing environment. As a result I'm not sure this can be tested correctly in this instance.

For what it's worth here is the code I created for this:

  /*
   * Test that the access method throws an exception for invalid access callbacks.
   *
   * @expectedException \BadMethodCallException
   */
  public function testAccessException() {
    $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\NonExistantController::nonexistantmethod'));
    $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
    $account = $this->getMock('Drupal\Core\Session\AccountInterface');

    $this->accessChecker->access($route, $route_match, $account);
  }
dawehner’s picture

I'm wondering whether we should have a test for that.

philipnorton42’s picture

StatusFileSize
new1.19 KB

Just realised that the original patch didn't follow Drupal coding standards. So I'm applying those standards in an updated patch :)

dawehner’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

A small test would still be nice

philipnorton42’s picture

Not a problem. I will put something together soon and post it back :)

philipnorton42’s picture

Assigned: Unassigned » philipnorton42
philipnorton42’s picture

Assigned: philipnorton42 » Unassigned
Status: Needs work » Needs review
StatusFileSize
new3.19 KB

Ok, so I've had a go at writing the test for this. In order to properly simulate the conditions of the test I had to create the CustomAccessCheck object (i.e. instead of Mocking it). There is therefore quite a bit of object creation, but at least I was able to Mock some of the objects used in this process.

Attached is the patch containing the code change and the passing test :)

dawehner’s picture

+++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
@@ -106,6 +110,28 @@ public function testAccess() {
+
+    $this->setExpectedException('\BadMethodCallException');
+
...
+    $this->accessChecker->access($route, $route_match, $account);

A small suggestion: Move the setExpectedException right before the access call. This makes the overall test easier to read.

philipnorton42’s picture

StatusFileSize
new3.19 KB

Yeah, that makes sense to me. Corrected :)

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs tests

Cool, thank you!

jibran’s picture

Version: 8.2.x-dev » 8.3.x-dev
StatusFileSize
new2.42 KB
new3.26 KB

Fixed some cs issues. Moving it to 8.3.x so that it can be back ported after stable release of 8.2.x

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 17: add_helpful_exception-2405241-17.patch, failed testing.

jibran’s picture

Seems like unrelated

testDateRangeField
fail: [Other] Line 204 of core/modules/datetime_range/src/Tests/DateRangeFieldTest.php:
Separator not found on page

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

ivan berezhnov’s picture

Issue tags: +CSKyiv18

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

rakesh.gectcr’s picture

Issue tags: +Nwdug_may18
philipnorton42’s picture

Status: Needs work » Needs review
StatusFileSize
new3.75 KB

Re-rolled the patch against Drupal 8.6.x-dev.

Also replaced the static object creation in the test class with mocking and added some comments to the class so that it was clear what was going on.

philipnorton42’s picture

StatusFileSize
new3.75 KB

Uploading a new version of the patch file with the file suffix of ".patch" in order to trigger the Drupal unit testing bot.

andrewmacpherson’s picture

@philipnorton42 - patches just want a .patch extension, not .patch.txt, otherwise the test bots don't pick it up. (EDIT: snap, we both posted in the same minute.)

Can you upload a patch without the .txt extension, increment the comment number in the patch name, and set it back to needs-review? Then the test-bots will do their thing.

Also, it would be good to provide an interdiff between this and the earlier patch makes it easy to see how it is progressing. So we're looking for an interdiff-2405241-17-26.txt, say.

Review of patch 25:

  1. The new exception sounds like it's going to be more useful, I like it.
  2. + $controllerResolver = $this->getMockBuilder(\Drupal\Core\DependencyInjection\ClassResolverInterface::class)->getMock();
    We already have a use \Drupal\Core\DependencyInjection\ClassResolverInterface;, so do we need to pass the fully-qualified name here?
  3. + $this->controllerResolver = $this->getMockBuilder(\Drupal\Core\Controller\ControllerResolver::class)
    We already have a use \Drupal\Core\Controller\ControllerResolver;, so do we need to pass the fully-qualified name here?
  4. +use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
    Where's this being used? I don't see it being used by any of the new code added by this patch.
  5. +use Symfony\Component\DependencyInjection\ContainerBuilder;
    Where's this being used? I don't see it being used by any of the new code added by this patch.
  6. NonExistantController::nonexistantmethod
    This is a real nitpick, but shouldn't this be NonExistentController::nonExistentMethod - spelling "existent", and camel-case for method name.
  7. +    // Set that we are to expect an exception
    +    $this->setExpectedException(\BadMethodCallException::class);

    The comment is a bit redundant, we can understand this from the method name.

The test only checks what exception class we get. Should we check the error message formatting too?

philipnorton42’s picture

StatusFileSize
new3.78 KB
new2.97 KB

Thanks very much for the guidance Andrew, really appreciate it!

I'd spotted the .patch thing literally minutes before you commented on it, but I've sorted that as well.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
@@ -61,7 +61,14 @@ public function __construct(ControllerResolverInterface $controller_resolver, Ac
+      throw new \BadMethodCallException(sprintf('The "%s" method is not callable as a _custom_access callback in route "%s"', $route->getRequirement('_custom_access'), $route->getPath()));

I'm curious, whether we could / should leverage https://github.com/symfony/debug/blob/122391f69203b96b0c92b4a4b58c89ed99... to provide a better error message.

philipnorton42’s picture

Potentially. Using UndefinedFunctionFatalErrorHandler::handleError looks like a nice approach, but we would need to move the symfony/debug library from require-dev into require in the composer.json file. Would that have implications in other parts of Drupal?

I can see only one instance of this component actually being used in Drupal and that is in the testing class EntityReferenceSettingsTest.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

That's a good point. Maybe it is worth exploring this in a follow up.

alexpott’s picture

credited @dawehner and @andrewmacpherson for reviews.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 7c1de1e and pushed to 8.6.x. Thanks!

diff --git a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
index 53c05d65be..aeb7f791bb 100644
--- a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Tests\Core\Access;
 
-use Drupal\Core\Access\AccessArgumentsResolverFactoryInterface;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Access\CustomAccessCheck;
 use Drupal\Core\Controller\ControllerResolver;
@@ -112,8 +111,8 @@ public function testAccess() {
     $this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account));
   }
 
-  /*
-   * Test the access method exception for invalid access callbacks.
+  /**
+   * Tests the access method exception for invalid access callbacks.
    */
   public function testAccessException() {
     // Create two mocks for the ControllerResolver constructor.
@@ -130,15 +129,16 @@ public function testAccessException() {
     $this->accessChecker = new CustomAccessCheck($this->controllerResolver, $this->argumentsResolverFactory);
 
     // Add a route with a _custom_access route that doesn't exist.
-    $route = new Route('/test-route', array(), array('_custom_access' => '\Drupal\Tests\Core\Access\NonExistantController::nonExistantMethod'));
+    $route = new Route('/test-route', [], ['_custom_access' => '\Drupal\Tests\Core\Access\NonExistentController::nonExistentMethod']);
     $route_match = $this->getMock(RouteMatchInterface::class);
     $account = $this->getMock(AccountInterface::class);
 
-    $this->setExpectedException(\BadMethodCallException::class, 'The "\Drupal\Tests\Core\Access\NonExistantController::nonExistantMethod" method is not callable as a _custom_access callback in route "/test-route"');
+    $this->setExpectedException(\BadMethodCallException::class, 'The "\Drupal\Tests\Core\Access\NonExistentController::nonExistentMethod" method is not callable as a _custom_access callback in route "/test-route"');
 
     // Run the access check.
     $this->accessChecker->access($route, $route_match, $account);
   }
+
 }
 
 class TestController {

Coding standards fixed on commit.

  • alexpott committed 7c1de1e on 8.6.x
    Issue #2405241 by philipnorton42, jibran, dawehner, andrewmacpherson:...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.