Skip to main content
Home
Drupal life hacks

Main navigation

  • Drupal
  • React
  • WP
  • Contact
  • About
User account menu
  • Log in

Breadcrumb

  1. Home

Drupal Routing Explained: Passing ‘op’ Parameters to Controllers the Right Way

By admin, 28 June, 2025

Your YAML defines two Drupal routes for enabling and disabling blocks, using the controller BlockController::performOperation with the operations enable and disable.

Here is a brief explanation:

entity.block.enable:
  path: '/admin/structure/block/manage/{block}/enable'       # URL to enable a block
  defaults:
    _controller: '\Drupal\block\Controller\BlockController::performOperation'  # Controller and method
    op: enable                                               # Operation "enable"
  requirements:
    _entity_access: 'block.enable'                           # Access permission check
    _csrf_token: 'TRUE'                                      # CSRF token required

entity.block.disable:
  path: '/admin/structure/block/manage/{block}/disable'     # URL to disable a block
  defaults:
    _controller: '\Drupal\block\Controller\BlockController::performOperation'  # Controller and method
    op: disable                                              # Operation "disable"
  requirements:
    _entity_access: 'block.disable'                          # Access permission check
    _csrf_token: 'TRUE'                                      # CSRF token required

This is a standard way to declare routes in Drupal 8/9/10. If you want, I can help with an example of how to extend or modify this route, or with the controller implementation. What exactly do you need?

 

Here is an explanation about the line op: enable (and op: disable):

This is simply a parameter (argument) passed to the controller method performOperation. In this case, it indicates which operation should be performed — enable (enable) or disable (disable) the block.

So, when accessing the URL /admin/structure/block/manage/{block}/enable, the method

BlockController::performOperation($block, $op = 'enable')

is called, where $op is the string 'enable'.

Therefore:

  • op: enable tells the controller to perform the "enable" operation.
  • op: disable tells the controller to perform the "disable" operation.

This is simply a way to use one method performOperation to handle different actions by receiving the op parameter from the route.

 

Great question!

Why is the operation enable or disable not passed as a query parameter in the URL (for example, /manage/{block}?op=enable), but instead defined as part of the route settings? Here’s why:

  1. Clean and Semantic URLs
    /admin/structure/block/manage/{block}/enable is a clear, human-readable path that explicitly indicates the action being performed — enabling a specific block. Such URLs are easier to understand and better for SEO (though SEO matters less for admin pages).
  2. Routing Convenience and Security
    Drupal routing is based on paths and parameters strictly defined in YAML. If the operation was in the query string (?op=enable), Drupal wouldn’t consider it when matching routes, requiring extra logic to read and verify the op parameter from the query, which is less straightforward and potentially less secure.
  3. Semantic Route Separation
    Each route in Drupal is a unique path with its own access requirements (_entity_access) and CSRF protections. It’s cleaner to have separate routes for different operations, each with its own access checks and security policies.
  4. REST Support and Link Generation
    When the operation is part of the path, it’s easier to generate links like <a href="/admin/structure/block/manage/block_id/enable">Enable</a> without needing to append query parameters.

In summary:
Putting the operation as part of the URL path is a common Drupal practice that results in clearer URLs, strict access control, and simpler routing logic.

If it were /manage/{block}?op=enable, then:

  • You’d have a single route /manage/{block},
  • The controller would need to inspect $_GET['op'] (which is less convenient and secure),
  • And you’d have to implement access checks based on that parameter separately.

 

Great question!

Why is the parameter op: disable passed in defaults when the URL already contains /disable — isn’t that duplication?


Short answer:
It’s not duplication, but a way to explicitly tell the controller which operation to perform.


Detailed explanation:

  • The route path /admin/structure/block/manage/{block}/disable includes /disable to let Drupal select this specific route.
  • However, the same controller method performOperation is used for multiple routes.
  • To know exactly what to do inside that method (enable or disable the block), the controller needs an explicit operation parameter.
  • This parameter op is not parsed from the URL string, but provided via the route’s defaults: op: disable.
  • Thus, performOperation receives $op = 'disable' or $op = 'enable' depending on the matched route.
  • Without passing op via defaults, the controller would have to parse the URL manually (e.g., extract the last path segment), which is cumbersome and error-prone.

Summary:
The op parameter in defaults is a clean and reliable way to pass the operation to the controller. The URL defines the unique route, and op tells the controller what exactly to do. This is not duplication but good design.

 

Great question!

Why can’t the controller just read the last segment of the URL instead of receiving the operation via the op parameter from defaults? Here’s why:

  1. Routing is separated from controller logic
    Drupal’s routing system maps URLs to controllers and passes well-defined parameters. The controller should work with these clean parameters instead of parsing the URL itself.
  2. Security and predictability
    If the controller manually parsed the URL (e.g., taking the last path segment as the operation), changes in the URL structure or unexpected paths could cause errors or security issues.
  3. Consistency and validation
    Parameters passed by the router (like {block}) are validated and well-defined. Parameters from defaults are controlled values. This makes code easier to maintain and test.
  4. Convenience for other components
    Other Drupal parts (like link generators or access checkers) rely on these parameters. If the operation was parsed manually from the URL, integration would be more complex.
  5. Performance and readability
    Parsing the URL string is extra work. Receiving $op directly lets the controller simply check the parameter and perform the action — simpler and faster.

Example:

Routing:

entity.block.enable:
  path: '/admin/structure/block/manage/{block}/enable'
  defaults:
    _controller: 'BlockController::performOperation'
    op: enable

Controller:

public function performOperation($block, $op) {
  if ($op === 'enable') {
    // Enable the block
  }
  elseif ($op === 'disable') {
    // Disable the block
  }
}

Without op in defaults, the controller would have to receive the URL string, extract the word enable from /admin/structure/block/manage/123/enable, and then decide what to do — which complicates the code.


In summary:
The op parameter in defaults is a clean, reliable, and standard way to pass important data to the controller, avoiding manual URL parsing. This improves security, testability, and maintainability.

 

A great option is to use the operation as a path parameter like this:

path: '/admin/structure/block/manage/{block}/{op}'

This is a common pattern, where op becomes a route parameter that can be received as a method argument in the controller.


Advantages of this approach:

  • The route becomes generic: one route handles multiple operations (enable, disable, delete, etc.)
  • You avoid repeating route definitions for each operation.
  • You can restrict allowed values for {op} in requirements, for example:
requirements:
  op: 'enable|disable'
  _entity_access: 'block.' ~ op

Considerations / potential drawbacks:

  1. Access permissions
    You need to ensure _entity_access checks are correctly set per operation. The dynamic concatenation shown above ('block.' ~ op) might not be supported in all Drupal versions directly in YAML — sometimes it requires PHP code or a hook.
  2. CSRF token
    For operations that change state (enable/disable), CSRF protection should be enabled and configured properly when using a single route.
  3. Caching and documentation
    Combining multiple operations in one route can complicate link generation, documentation, and maintenance.

Summary:
Yes, this is a valid and common approach. You can use a single route with a {op} parameter if you handle access control and security properly for each operation.

 

Here's a concise summary of everything you've explained and a practical example using a single route with a dynamic {op} parameter:


✅ Summary: How to Pass Operation Parameters in Drupal Routes

In Drupal routing, it's common to pass an operation such as enable or disable via the route definition using the defaults section. This allows the controller to receive a clear, predefined parameter instead of parsing it from the URL manually.

Why this approach is used:

  • Clean and semantic URLs (e.g., /block/manage/{block}/enable)
  • Safe and predictable parameter passing
  • Single controller method can handle multiple operations
  • Better integration with Drupal’s routing, access checks, and CSRF protection

You can also make the route more generic using {op} in the path, which allows for fewer route definitions — but you'll need to ensure access control and CSRF are correctly handled per operation.


🧪 Example: Dynamic {op} Route in *.routing.yml

entity.block.operation:
  path: '/admin/structure/block/manage/{block}/{op}'
  defaults:
    _controller: '\Drupal\block\Controller\BlockController::performOperation'
  requirements:
    op: 'enable|disable'
    _entity_access: 'block.custom_access'
    _csrf_token: 'TRUE'
  options:
    parameters:
      block:
        type: entity:block

🧩 Custom Access Check in block.custom_access (example)

Create a service tagged as access_check or use a RouteAccessCheck class to validate permissions based on the op value.

Or, for simple logic, use a controller method like this:

public function performOperation(Block $block, $op) {
  if ($op === 'enable') {
    // Enable logic
  } elseif ($op === 'disable') {
    // Disable logic
  } else {
    throw new \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException();
  }

  // Return a redirect or response
}

🛡️ CSRF and Permissions

  • CSRF protection is already enabled via _csrf_token: 'TRUE'
  • Access control should depend on op, and if dynamic YAML access keys are not supported, implement logic in a custom access check class or inside the controller

Comments

About text formats

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Powered by Drupal