Altering Routes - Restrict Route Access Via Custom Access Check

I needed to block the shipments tab from orders in Commerce when the order is complete. The shipment info is available on the order view page, but I wanted to block the ability to make changes. 

To do this, I had to alter the route with a RouteSubscriber and add a custom access check. Then I had to implement the check.

The documentation pages explain both separately, but I'll show you how to put them together. 

 

A RouteSubscriber is an EventSubscriber, with some defaults set in RouteSubscriberBase, like the event to subscribe to. I'm checking for the route I need, and add a '_custom_access' that points to my custom Access checker.

<?php

namespace Drupal\alter_route\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    if ($route = $collection->get('entity.commerce_shipment.collection')) {
      $route->setRequirement('_custom_access', '\Drupal\alter_route\Access\SynchronizedAccessCheck::access');
    }
  }

}

 

I had to check the commerce_order route parameter - it's a loaded entity on the order page, and an id on the shipments list. Then check the order state and block access if completed, or check the original permission.

<?php

namespace Drupal\alter_route\Access;

use Drupal\commerce_order\Entity\Order;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Checks access for displaying order edit form.
 */
class SynchronizedAccessCheck implements AccessInterface {

  /**
   * A custom access check.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   */
  public function access(AccountInterface $account) {
    $order = \Drupal::routeMatch()->getParameter('commerce_order');
    if (!$order instanceof \Drupal\commerce_order\Entity\Order) {
      $order = Order::load($order);
    }
    $state_properties = $order->getState()->getProperties();
    $order_state = $state_properties['value']->getValue();

    if ($order_state == 'completed') {
      return \Drupal\Core\Access\AccessResult::forbidden();
    }
    else {
      return \Drupal\Core\Access\AccessResult::allowedIfHasPermission($account, 'administer commerce_shipment');
    }
  }

}

 

Both of these are added to alter_route.services.php.

services:
  alter_route.access_checker:
    class: Drupal\alter_route\Access\SynchronizedAccessCheck
    arguments: ['@current_user']
    tags:
      - { name: access_check }

  alter_route.route_subscriber:
    class: Drupal\alter_route\Routing\RouteSubscriber
    tags:
      - { name: event_subscriber }