5

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?

2 Answers 2

9

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
    Commented Sep 18, 2016 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
    Commented Sep 18, 2016 at 20:32
  • This solves the problem with a redirect, isn't there a way to override the route without redirecting?
    – mpp
    Commented Jan 8, 2021 at 22:09
  • @mpp In the controller, you could render the edit form instead of redirecting.
    – avpaderno
    Commented Jan 11, 2021 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 :) Commented May 24, 2021 at 15:52
1

Here is the other answer that was here but for some reason was deleted today.

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.node_view_subscriber:
    class: Drupal\app\EventSubscriber\NodeViewSubscriber
    arguments: ['@controller_resolver']
    tags:
      - { name: event_subscriber }

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

namespace Drupal\app\EventSubscriber;

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


class NodeViewSubscriber implements EventSubscriberInterface {

  /**
   * The controller resolver.
   *
   * @var \Symfony\Component\HttpKernel\Controller\ControllerResolverInterface
   */
  protected $controllerResolver;

  /**
   * Constructs a new PathSubscriber instance.
   */
  public function __construct(ControllerResolverInterface $controllerResolver) {
    $this->controllerResolver = $controllerResolver;
  }

  /**
   * Returns subscribed events.
   *
   * @return string[]
   *   Subscribed events.
   */
  public static function getSubscribedEvents(): array {
    return [
      KernelEvents::CONTROLLER => 'onController'
    ];
  }

  /**
   * Sets custom controller for a content type.
   *
   * @param \Symfony\Component\HttpKernel\Event\ControllerEvent $event
   *   The event.
   */
  public function onController(ControllerEvent $event): void {
    $request = $event->getRequest();
    $attributes = $request->attributes;

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

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

    if ($route === 'entity.node.canonical') {
      $node = $attributes->get('node');
      if ($node instanceof NodeInterface) {
        switch ($node->bundle()) {
          case 'article': // Node type
            $controller = $this->controllerResolver->getControllerFromDefinition('\Drupal\app\Controller\ArticlesController::collection');
            break;

          default:
            $controller = $this->controllerResolver->getControllerFromDefinition('\Drupal\node\Controller\NodeViewController::view');
            break;
        }
        $event->setController($controller);
      }
    }
  }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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