Change record status: 
Project: 
Introduced in branch: 
8.2.x
Description: 

In previous versions of Drupal, a plugin was capable of providing a single form. The plugin class itself has to contain the form:

namespace Drupal\my_module\MyPluginType;
/**
 * @MyPluginType(
 *   id = "my_plugin_id"
 * )
 */
class MyPlugin implement PluginFormInterface {
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    // ...
  }
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // ...
  }
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    // ...
  }
}

Now a plugin can support multiple forms, and designate any class for that form:

namespace Drupal\my_module\MyPluginType;

/**
 * @MyPluginType(
 *   id = "my_plugin_id",
 *   forms = {
 *     "add" = "\Drupal\my_module\PluginForm\MyPluginAddForm",
 *     "edit" = "\Drupal\my_module\PluginForm\MyPluginEditForm",
 *   }
 * )
 */
class MyPlugin implements PluginWithFormsInterface {
}
namespace Drupal\my_module\PluginForm;
class MyPluginAddForm implements PluginFormInterface {
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    // ...
  }
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // ...
  }
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    // ...
  }
}
namespace Drupal\my_module\PluginForm;
class MyPluginEditForm implements PluginFormInterface {
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    // ...
  }
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // ...
  }
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    // ...
  }
}

Most decoupled plugin forms will need access to the plugin itself, in which case they should implement \Drupal\Component\Plugin\PluginAwareInterface.

To simplify this, a base class is provided: \Drupal\Core\Plugin\PluginFormBase


In order to use this for your subsystem, use the plugin_form.factory service, which implements \Drupal\Core\Plugin\PluginFormFactoryInterface and has one method:
public function createInstance(PluginWithFormsInterface $plugin, $operation, $fallback_operation = NULL);
If a form is not found for the specific operation ("add" or "edit" in the above examples), a fallback operation can be requested.


In order maintain backwards compatibility with plugins that directly implement PluginFormInterface, the plugin annotation is automatically enhanced with a form for the "configure" operation pointing to the plugin itself.


This originally introduced a critical regression, due to the modifications made to the DefaultPluginManager: #2796953: [regression] Plugins extending from classes of uninstalled modules lead to fatal error during discovery. Any class (plugin) that implements PluginWithFormsInterface and relies on "configure" automatically being added to the definition can simply remove its (get|has)FormClass() implementations and use PluginWithFormsTrait.

Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done