Presented by: Tom Friedhof

Tom Friedhof

...around since Drupal 4.7

ActiveLAMP partners with organizations to develop compelling digital experiences that solve specific organizational goals.

  • Our team has extensive industry experience building large software projects at scale.
  • Based in Los Angeles, CA
  • Higher Education, Non-Profits, Enterprise...
ActiveLAMP Logo

What I'm going to cover:

  • What / Why plugin architecture
  • A practical example using plugins
  • Bare necessities to begin writing plugin types in D8

What I'm not going to cover:

Not a deep dive of the plugin system:

  • Derivatives
  • Discovery Options
  • The D8 Plugin system internals

Pragmatic Plugin Types

Prerequisites for this talk:

Basic OOP knowledge

  • Interfaces
  • Inheritance
  • Encapsulation

What is a plugin?

Plugins are small pieces of functionality that are swappable. Plugins that perform similar functionality are of the same plugin type.
https://www.drupal.org/developing/api/8/plugins

Example Scenario:

I want to build a webpage that shows all the open tasks for a project, and the tasks exist in an external system (i.e. Jira)

I can write a module to do that!

jira_tasklist

Module Requirments

  • Define a route for the page
  • Define the controller logic for the route
    • Authenticate with Jira
    • Request a list of tasks from Jira
  • Define a template for the page to pass task data to


What if I want to use Trello for some projects?

Plugins are small pieces of functionality that are swappable. Plugins that perform similar functionality are of the same plugin type.

Don't we have this capability in PHP already?

Interfaces

Object interfaces allow you to create code which specifies which methods a class must implement, without having to define how these methods are handled.

Code to an interface


/**
 * Defines an interface for for interacting with project management systems.
 */
interface ProjectManagementInterface {

  /**
   * Authenticate with the the PM system.
   */
  public function auth();

  /**
   * Return a task list from PM system.
   */
  public function getTasks();
}
						

PHP will enforce the contract


/**
 * Get stuff from Jira.
 */
class JiraTaskList implements ProjectManagementInterface {}
						

/**
 * Get stuff from Trello.
 */
class TrelloTaskList implements ProjectManagementInterface {}
						

Yay! Swappable functionality!

Don't celebrate yet.

How do you swap the functionality out?

The service container silly

tasklist.services.yml


services:
  tasklist.pm_system:
    class: Drupal\tasklist\JiraTaskList
						

MyModuleController.php


/**
 * Module Controller.
 */
class MyController extends ControllerBase {
  function index() {
    $taskList = \Drupal::getContainer()->get('tasklist.pm_system');
    ...
  }
}
						
BTW... don't access the service container this way.

Code deploy just to swap out the functionality?

Fail

Plugin's are more than swappable functionality

What is a plugin?

Plugins are much like PHP native interfaces with a little extra: the plugin system can discover every implementation of an interface (the default is magic namespacing), deals with metadata (by default this is provided by annotations) and provides a factory for the plugin classes.
https://www.drupal.org/node/1637614

Why plugins?

The Drupal 8 plugin system provides a way to discover plugins within modules, grab metadata about the plugin, and provide a method of instantiating the plugin class when needed.

  • Discovery
  • Metadata
  • Factory

Plugins vs Services

How to start using plugins

D8 needs to know:

  • Plugin Type (The "PluginManager")
    • The Interface for the Plugin
  • Plugin Discovery (Commonly using Annotations)
  • Plugin Factory (Also the "PluginManager")

Responsible developers provide a BaseClass

PluginManager

Start by extending a base class: DefaultPluginManager


/**
 * Project management system plugin manager.
 */
class ProjectManagementManager extends DefaultPluginManager {

  /**
   * Constructs an ProjectManagementManager 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 to invoke the alter hook with.
   */
  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
    parent::__construct('Plugin/ProjectManagement', $namespaces, $module_handler, 'Drupal\projectmanagement\ProjectManagementInterface', 'Drupal\projectmanagement\Annotation\ProjectManagement');
  }
}
  					

Define how the Annotation


use Drupal\Component\Annotation\Plugin;

/**
 *
 * @Annotation
 */
class ProjectManagment extends Plugin {

  /**
   * The plugin name.
   *
   * @var string
   */
  public $name;

  /**
   * The plugin description.
   *
   * @var string
   */
  public $description;
}
  					

Define the Plugin


/**
 * Define the Jira plugin.
 *
 * @ProjectManagment(
 *   id = "jira"
 * )
 */
 class Jira {}
  					

Define the Interface


/**
 * Defines an interface for for interacting with project management systems.
 */
interface ProjectManagementInterface extends PluginInspectionInterface {

  /**
   * Authenticate with the the PM system.
   */
  public function auth();

  /**
   * Return a task list from PM system.
   */
  public function getTasks();
}
  					

Define a base class for common functionality


class ProjectManagementBase extends PluginBase implements ProjectManagmentInterface {

}
  					

Extend the base class


/**
 * Define the Jira plugin.
 *
 * @ProjectManagment(
 *   id = "jira",
 *   label = "Atlassian Jira"
 * )
 */
 class Jira extends ProjectManagementBase {
   public function auth() {}
   public function getTasks() {}
 }
  					

Notice that the metadata is baked into the same plugin file

All plugin functionality is Encapsulated

  • No hook_tasklist_info()
  • No guessing what functions to define to make it function
  • PHP Interfaces enforce the contract

Demo

Pro tip: Use Drupal Console

  • drupal generate:module
  • drupal generate:controller
  • drupal generate:service
  • drupal generate:plugin:type:annotation
  • drupal generate:form:config

Subscribe to our YouTube Channel

http://youtube.com/activelamp

Questions?

Send me an email:

tom@activelamp.com

or in IRC #drupal-la