Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Safely extending Drupal 8 plugin classes without fear of constructor changes

Parent Feed: 

From time to time you may find you need to extend another module's plugins to add new functionality.

You may also find you need to alter the signature of the constructor in order to inject additional dependencies.

However plugin constructors are considered internal in Drupal's BC policy.

So how do you safely do this without introducing the risk of breakage if things change.

In this article we'll show you a quick trick learned from Search API module to avoid this issue.

So let's consider a plugin constructor that has some arguments.

Here's the constructor and factory method for Migrate's SQL map plugin

/**
   * Constructs an SQL object.
   *
   * Sets up the tables and builds the maps,
   *
   * @param array $configuration
   *   The configuration.
   * @param string $plugin_id
   *   The plugin ID for the migration process to do.
   * @param mixed $plugin_definition
   *   The configuration for the plugin.
   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
   *   The migration to do.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->migration = $migration;
    $this->eventDispatcher = $event_dispatcher;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration,
      $container->get('event_dispatcher')
    );
  }

As you can see, there are two additional dependencies injected beyond the standard plugin constructor arguments - the event dispatcher and the migration.

Now if you subclass this and extend the constructor and factory to inject additional arguments, should the base plugin change its constructor, you're going to be in trouble.

Instead, you can use this approach that Search API takes - leave the constructor as is (don't override it) and use setter injection for the new dependencies.

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    $instance = parent::create(
      $container,
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration
    );
    $instance->setFooMeddler($container->get('foo.meddler');
    return $instance;
  }
    
    /**
    * Sets foo meddler.
    */
    public function setFooMeddler(FooMeddlerInterface $fooMeddler) {
      $this->fooMeddler = $fooMeddler;
    }

Because the signature of the parent create method is enforced by the public API of \Drupal\Core\Plugin\ContainerFactoryPluginInterface you're guaranteed that it won't change.

Thanks to Thomas Seidl for this pattern

Posted by Lee Rowlands
Senior Drupal Developer

Dated

Comments

Nice!! Thank you for sharing it!

Pagination

Add new comment

Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web