9

I am trying to create a new custom tab that appears on all routes that are descendants of entity.{entity_type}.canonical. I have tried extending the DeriverBase class, specifically overriding the getDerivativeDefinitions method. I created the tab itself by extending LocalTaskDefault and overriding the getRouteParameters method. The tab appears when you visit a standard Drupal user path such as www.mysite.com/user/1/ or www.mysite.com/user/1/edit. However, when we add our new custom user routes such as www.mysite.com/user/1/subscribe, no tabs appear. Is there a special way to define local menu tasks on custom routes? A sample of the code:

 $this->derivatives['recurly.subscription_tab'] = [
  'title' => $this->t('Subscription'),
  'weight' => 5,
  'route_name' => 'recurly.subscription_list',
  'base_route' => "entity.$entity_type.canonical",
];

foreach ($this->derivatives as &$entry) {
  $entry += $base_plugin_definition;
}

Thanks in advance of any help.

  • Sounds very close to what devel is doing with it's /devel route/local task, I suggest you have a look at how it's implementing that. – Berdir Jan 5 '16 at 22:49
  • @Berdir that was the starting point but I still seem to be missing something. – tflanagan Jan 5 '16 at 23:02
  • Did you try to add 'yourmodule.links.task.yml' file with settings for your custom tab? – Andrew Jan 12 '16 at 11:30
7
+500

As suggested by Berdir you can look at Devel module and at how it's implementing that. The following code was "extracted" from Devel

1) Create the routes

Create the file mymodule.routing.yml in and inside define a route callback (which is used for create the dynamic routes)

route_callbacks:
  - '\Drupal\mymodule\Routing\MyModuleRoutes::routes'

Create the class MyModuleRoutes for generate yours dynamic routes in src/Routing

<?php

namespace Drupal\mymodule\Routing;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

class MyModuleRoutes implements ContainerInjectionInterface {

  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager')
    );
  }

  public function routes() {
    $collection = new RouteCollection();

    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
      if ($entity_type->hasLinkTemplate('canonical')) {
        $route = new Route("/mymodule/$entity_type_id/{{$entity_type_id}}");
        $route
          ->addDefaults([
            '_controller' => '\Drupal\mymodule\Controller\MyModuleController::doStuff',
            '_title' => 'My module route title',
          ])
          ->addRequirements([
            '_permission' => 'access mymodule permission',
          ])
          ->setOption('_mymodule_entity_type_id', $entity_type_id)
          ->setOption('parameters', [
            $entity_type_id => ['type' => 'entity:' . $entity_type_id],
          ]);

        $collection->add("entity.$entity_type_id.mymodule", $route);
      }
    }

    return $collection;
  }

}

2) Create the dynamic local tasks

Create the file mymodule.links.task.yml and inside define a deriver

mymodule.tasks:
  class: \Drupal\Core\Menu\LocalTaskDefault
  deriver: \Drupal\mymodule\Plugin\Derivative\MyModuleLocalTasks

Create the class MyModuleLocalTasks for generate yours dynamic routes in src/Plugin/Derivative

<?php

namespace Drupal\mymodule\Plugin\Derivative;

use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class MyModuleLocalTasks extends DeriverBase implements ContainerDeriverInterface {

  protected $entityTypeManager;

  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  public static function create(ContainerInterface $container, $base_plugin_id) {
    return new static(
      $container->get('entity_type.manager')
    );
  }

  public function getDerivativeDefinitions($base_plugin_definition) {
    $this->derivatives = array();

    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
      if ($entity_type->hasLinkTemplate('canonical')) {
        $this->derivatives["$entity_type_id.mymodule_tab"] = [
          'route_name' => "entity.$entity_type_id.mymodule",
          'title' => t('Mymodule title'),
          'base_route' => "entity.$entity_type_id.canonical",
          'weight' => 100,
        ] + $base_plugin_definition;
      }
    }

    return $this->derivatives;
  }

}

3) Create the controller

Create the class MyModuleController in src/Controller

namespace Drupal\mymodule\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Routing\RouteMatchInterface;

class MyModuleController extends ControllerBase {

  public function doStuff(RouteMatchInterface $route_match) {
    $output = [];

    $parameter_name = $route_match->getRouteObject()->getOption('_mymodule_entity_type_id');
    $entity = $route_match->getParameter($parameter_name);

    if ($entity && $entity instanceof EntityInterface) {
      $output = ['#markup' => $entity->label()];
    }

    return $output;
  }

}
  • 3
    This was very similar to what I ended up implementing. Passing in the RouteMatchInterface $route_match was the solve to my problem. From there my entity object was available to my controller. – tflanagan Jan 15 '16 at 15:29

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.