4

I have a discussion content type. I would like the primary viewing experience (at "discussion/discussion-name") to be a cacheable form using a form mode.

Basically, I'm trying to create something like a web app experience where anyone who can view it, can edit it, so there is no point to separating view and edit, a more integrated experience is desired.

It feels like it should be easy, but I haven't found anything about how to override the routing for just one content type.

How can I override the route for a content type?

7

You'll need to alter the entity.node.canonical route to do this. You can do this via a RouteSubscriber::alterRoutes() in a custom module to have the route driven by your custom logic. In src/Routing/RouteSubscriber.php:

namespace Drupal\mymodule\Routing;

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

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

  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {
   
    // Alter the canonical node route to our custom route
    if ($route = $collection->get('entity.node.canonical')) {
      $route->setDefault('_controller', '\Drupal\mymodule\Controller\NodeRedirectController::view');
    }
  }
}

Then build the logic in your custom controller to redirect based on your node type, in src/Controller/NodeRedirectController.php:

namespace Drupal\mymodule\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Entity\EntityInterface;
use Drupal\node\Controller\NodeViewController;


/**
 * Custom node redirect controller
 */
class NodeRedirectController extends NodeViewController {

  public function view(EntityInterface $node, $view_mode = 'full', $langcode = NULL) {
    // Redirect to the edit path on the discussion type
    if ($node->bundle() === 'discussion') {
      return new RedirectResponse('/node/' . $node->id() . '/edit');
    }
    // Otherwise, fall back to the parent route controller.
    else {
      return parent::view($node, $view_mode, $langcode);
    }
  }
}

Lastly, register your routeSubscriber service in mymodules.services.yml:

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

Documentation is on drupal.org: Altering existing routes and adding new routes based on dynamic ones.

5
  • 1
    Thanks Shawn, that's really helpful. You saved me a lot of time with this. For benefits of future people, 2 small points: I think "namespace Drupal\test\Controller;" should be "namespace Drupal\mymodule\Controller; ", and also a mymodule.services.yml entry is needed " mymodule.route_subscriber: class: Drupal\mymodule\Routing\RouteSubscriber tags: - { name: event_subscriber }". drupal.org/node/2187643 is the docs for this approach.
    – Jonathan
    Sep 18 '16 at 20:26
  • No problem. Yeah the namespace was a typo which I've fixed. Feed free to edit the answer with any further details that help you solved the problem.
    – Shawn Conn
    Sep 18 '16 at 20:32
  • This solves the problem with a redirect, isn't there a way to override the route without redirecting?
    – mpp
    Jan 8 at 22:09
  • @mpp In the controller, you could render the edit form instead of redirecting.
    – apaderno
    Jan 11 at 8:25
  • Thanks a lot @Shawn, because of your answer here I was able to solve my problem mentioned at drupal.stackexchange.com/questions/303048/… Really appreciate it :) May 24 at 15:52
1

The answer provided by Shawn works great, but if you would prefer to have separate controllers per content type eg. PageController, ArticleController etc. This is achievable by the Kernel Subscriber.

services:
  app.kernel_subscriber:
    class: Drupal\app\EventSubscriber\KernelSubscriber
    arguments: ['@controller_resolver']
    tags:
      - { name: event_subscriber }

  plugin.manager.late_controller:
    class: Drupal\app\PluginManager\LateControllerPluginManager
    parent: default_plugin_manager

The subscriber listens for the KernelEvents::CONTROLLER event, whereupon you can query the Request and check which node type you have - keeping this logic off the controller.

<?php

declare(strict_types=1);

namespace Drupal\app\EventSubscriber;

use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Subscribe to kernel events.
 */
class KernelSubscriber implements EventSubscriberInterface {

  /**
   * Constructs a new PathSubscriber instance.
   */
  public function __construct(
    protected ControllerResolverInterface $controllerResolver,
  ) {}


  public static function getSubscribedEvents(): array {
    return [
      KernelEvents::CONTROLLER => 'onController',
    ];
  }

  public function onController(ControllerEvent $event): void {
    $request = $event->getRequest();
    $attributes = $request->attributes;

    if ($attributes->has('_route') === FALSE) {
      return;
    }

    $route = $attributes->get('_route');

    $manager = \Drupal::service('plugin.manager.late_controller');
    if ($manager->hasDefinition($route) === FALSE) {
      return;
    }

    $plugin = $manager->createInstance($route);
    $controller_definition = $plugin->getControllerDefinition($request);

    $controller = $this->controllerResolver->getControllerFromDefinition($controller_definition);
    $event->setController($controller);
  }
}

So in the example above my subscriber references a custom plugin type that I've created to check for alternative controllers, but you could load your logic straight into the subscriber if that is your preference.

If you would like a walkthrough of the plugin you can find this code at https://kanso.agency or watch on YouTube as I fight my way through terrible hayfever to explain the issue YouTube

Hope this helps someone looking for a slightly more granular approach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.