If you inject the current route match service (current_route_match) and use it inside an access() method, your access check will only work when the user is actually visiting that route in the browser.
However, if access is checked programmatically from another page (e.g., via an access API or a block), the current route match will be from that other page, not the one you actually want to check access for.
Example of a Problematic Approach with current_route_match
Let’s say we want to check access to a user profile page (/user/{user}/profile). If we use current_route_match, the access check might work only when visiting that route directly.
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
class ProfileAccessCheck {
protected $routeMatch;
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
public function access(AccountInterface $account) {
// Get the user ID from the route
$user = $this->routeMatch->getParameter('user');
if ($user && $account->id() === $user->id()) {
return AccessResult::allowed();
}
return AccessResult::forbidden();
}
}
The Problem
- If the user visits
/user/5/profile,current_route_matchwill return the correct route, and everything will work fine. - But if access is checked from another context (e.g., a block on the homepage
/),current_route_match->getParameter('user')will be NULL because the homepage doesn’t have auserparameter. - This can cause access checks to fail or always return forbidden when checked programmatically.
The Correct Approach
Instead of using current_route_match, pass the required object explicitly in the access() method. This is how Drupal handles access checks properly:
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\UserInterface;
class ProfileAccessCheck {
public function access(UserInterface $user, AccountInterface $account) {
return $account->id() === $user->id()
? AccessResult::allowed()
: AccessResult::forbidden();
}
}
Now, when checking access programmatically:
$access = \Drupal::service('access_check.profile')->access($some_user, $current_user);
it will work in any context because the required data is passed explicitly.
Key Takeaways
❌ Bad practice: Using current_route_match inside access()
✅ Best practice: Pass required objects (e.g., UserInterface $user) as parameters in the access() method.
This ensures reliable access checks in all contexts! 🚀
Comments