Score:1

Custom plugin definition not loading

ph flag

I'm trying to create my own plugin type in Drupal 9 but it's not finding my test definition and I can't figure out why.

Here's my services.yaml:

services:
  plugin.manager.reaction:
    class: Drupal\nm_events\ReactionManager
    parent: default_plugin_manager
  

Here's ReactionManager.php:

<?php

namespace Drupal\nm_events;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;

/**
 * Defines the reaction plugin manager.
 */
class ReactionManager extends DefaultPluginManager {

  /**
   * Constructs a ReactionManager object.
   *
   * @param \Traversable $namespaces
   *   An object that implements \Traversable which contains the root paths
   *   keyed by the corresponding namespace to look for plugin implementations.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   Cache backend instance to use.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   */
  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_h
andler) {
    parent::__construct('Plugin/Reaction', $namespaces, $module_handler, 'Drupal\nm_events\ReactionInterface', 'Drupal\nm_even
ts\ReactionAnnotation');
    $this->setCacheBackend($cache_backend, 'reaction_plugins');
    $this->alterInfo('reaction_info');
  }

}

Here's my Annotation class:

<?php

namespace Drupal\nm_events;

use Drupal\Component\Annotation\Plugin;

/**
 * Declare a class for event reactions.
 *
 * Plugin Namespace: Plugin\Reaction.
 *
 * @see plugin_api
 *
 * @Annotation
 */
class ReactionAnnotation extends Plugin {
  /**
   * The plugin ID.
   *
   * @var string
   */
  public $id;

  /**
   * The human-readable title of the plugin.
   *
   * @var \Drupal\Core\Annotation\Translation
   *
   * @ingroup plugin_translatable
   */
  public $title;

}

And here's my Test plugin (in src/Plugin/Reaction/Test.php

<?php

namespace Drupal\nm_events\Plugin\Reaction;

use Drupal\nm_events\ReactionInterface;

/**
 * Test reaction.
 *
 * @Reaction(
 *   id = "test",
 *   title = @Translation("Reaction for testing reactions")
 * )
 */
class Test implements ReactionInterface {
  public function processEvent($event, $entity) {
    var_export($event->title->value);
  }
}

But when I try to get the definitions:

$ drush cr
 [success] Cache rebuild complete.
$ drush php
Psy Shell v0.11.17 (PHP 8.0.15 — cli) by Justin Hileman
New/Mode base (Drupal 9.5.8)

> $p = $container->get('plugin.manager.reaction')
= Drupal\nm_events\ReactionManager {#2893}

> $p->getDefinitions();
= []

Edit: Here's the interface definition:

<?php

namespace Drupal\nm_events;

use Drupal\Component\Plugin\PluginInspectionInterface;

/**
 * Defines an interface for a Reaction plugin.
 *
 * @see plugin_api
 */
interface ReactionInterface extends PluginInspectionInterface {
  public function processEvent($event, $entity);
}
Score:1
gb flag

You have two options:

  • A. Adjust Test class with correct annotation id
  • B. Rewrite your code to follow common approaches with plugin definition.

A. The Test.php reaction plugin uses wrong annotation name. Instead of @Reaction there sholud be @ReactionAnnotation.

B. Code Refactor. There are couple of issues with your code:

  1. Rename and move ReactionAnnotation.php from nm_events/src/ReactionAnnotation.php into nm_events/src/Annotation/Reaction.php and adjust namespace to Drupal\nm_events\Annotation and class name to Reaction
  2. Change annotation name in ReactionManager.php from Drupal\nm_events\ReactionAnnotation to Drupal\nm_events\Annotation\Reaction
  3. Change annotation id within Test.php from @ReactionAnnotation to @Reaction

After this adjustments $p = $container->get('plugin.manager.reaction')->getDifitions() should show your test plugin.

However. When you try to create plugin instance: $test = $container->get('plugin.manager.reaction')->createInstance('test', [])

You will get fatal error Test contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods. To fix it you need extend it with Drupal\Core\Plugin\PluginBase class.

Now instance could be created.

Note. I would recommend to take a look how other core modules deal with plugins, there are lot's of useful classes out of the box in Drupal. Here is example: ImageEffectManager.php. And maybe you also need plugin collection similar to: ImageEffectPluginCollection

To save time you can use drush generate plugin:manager command and create all stuff (including service, annotation, interface, manager, test plugin, plugin interface) using provided wizard.

Score:1
us flag

I have played a little bit and changed the structure slightly, but finally made it work. Here's my example:

Final structure:

├── pm_test.info.yml
├── pm_test.services.yml
└── src
    ├── Annotation
    │   └── ReactionAnnotation.php
    └── Plugin
        ├── Reaction
        │   └── Test.php
        ├── ReactionInterface.php
        └── ReactionManager.php

services.yml looks like this:

services:
  plugin.manager.reaction:
    class: Drupal\pm_test\Plugin\ReactionManager
    parent: default_plugin_manager

Annotation class(src/Annotation/ReactionAnnotation.php)

<?php

namespace Drupal\pm_test\Annotation;

use Drupal\Component\Annotation\Plugin;

/**
 * Declare a class for event reactions.
 *
 * Plugin Namespace: Plugin\Reaction.
 *
 * @see plugin_api
 *
 * @Annotation
 */
class ReactionAnnotation extends Plugin {

  /**
   * The plugin ID.
   *
   * @var string
   */
  public $id;

  /**
   * The human-readable label of the plugin.
   *
   * @var \Drupal\Core\Annotation\Translation
   *
   * @ingroup plugin_translatable
   */
  public $label;

}

The ReactionInterface (src/Plugin/ReactionInterface.php)

<?php

namespace Drupal\pm_test\Plugin;

use Drupal\Component\Plugin\PluginInspectionInterface;

/**
 * Defines an interface for a Reaction plugin.
 *
 * @see plugin_api
 */
interface ReactionInterface extends PluginInspectionInterface {

  public function processEvent($event, $entity);

}

The ReactionManager(src/Plugin/ReactionManager.php)

<?php

namespace Drupal\pm_test\Plugin;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;

/**
 * Defines the reaction plugin manager.
 */
class ReactionManager extends DefaultPluginManager {

  /**
   * Constructs a ReactionManager object.
   *
   * @param \Traversable $namespaces
   *   An object that implements \Traversable which contains the root paths
   *   keyed by the corresponding namespace to look for plugin implementations.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   Cache backend instance to use.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   */
  public function __construct(
    \Traversable $namespaces,
    CacheBackendInterface $cache_backend,
    ModuleHandlerInterface $module_handler,
  ) {
    parent::__construct(
      'Plugin/Reaction',
      $namespaces,
      $module_handler,
      '\Drupal\pm_test\Plugin\ReactionInterface',
      '\Drupal\pm_test\Annotation\ReactionAnnotation'
    );

    $this->alterInfo('reaction_info');
    $this->setCacheBackend($cache_backend, 'reaction_info_plugins');
  }

}

And finally the Test(/src/Plugin/Reaction/Test.php):

<?php

namespace Drupal\pm_test\Plugin\Reaction;

use Drupal\Component\Plugin\PluginBase;
use Drupal\pm_test\Annotation\ReactionAnnotation;
use Drupal\pm_test\Plugin\ReactionInterface;

/**
 * Test reaction.
 *
 * @ReactionAnnotation(
 *   id = "test",
 *   title = @Translation("Reaction for testing reactions")
 * )
 */
class Test extends PluginBase implements ReactionInterface {
  public function processEvent($event, $entity) {
    var_export($event->title->value);
  }

}

At the end, I checked the same way with drush and here's the result:

Psy Shell v0.11.18 (PHP 8.0.24 — cli) by Justin Hileman
Drush Site-Install (Drupal 9.5.9)
> $p = $container->get('plugin.manager.reaction');
= Drupal\pm_test\Plugin\ReactionManager {#7435}

> $p->getDefinitions();
= [
    "test" => [
      "id" => "test",
      "title" => Drupal\Core\StringTranslation\TranslatableMarkup {#7452},
      "class" => "Drupal\pm_test\Plugin\Reaction\Test",
      "provider" => "pm_test",
    ],
  ]

In my opinion the biggest issue with your code was the Annotation. Instead of using

 * @Reaction(
 *   id = "test",
 *   title = @Translation("Reaction for testing reactions")
 * )

it should have been

 * @ReactionAnnotation(
 *   id = "test",
 *   title = @Translation("Reaction for testing reactions")
 * )

after the name of the annotation class. I hope this helps

gb flag
Heh) Almost in same time)). Funny, what is a chance of such event could happen.
Lambic avatar
ph flag
Thanks to you both, I switched @Reaction to ReactionAnnotation and it worked, but I'll probably apply the other refactoring too. I'm not sure who to give the bounty to now!
Ivaylo Tsandev avatar
us flag
You may see the entire refactored structure here, including the Test class extension of the PluginBase, so it's fully functional
Ivaylo Tsandev avatar
us flag
@VladMoyseenko - indeed, funny case :D
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.