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

Previously a module providing an action would implement hook_action_info() and declare the action information there, keyed by the action callback.

An action consisted of two or three parts:

  • an action definition (returned by the hook)
  • a function which performed the action (array key in the hook)
  • an optional form definition function that defined a configuration form (which had the name of the action function with '_form' appended to it.)

Actions have now been converted to annotated plugins.
The callback functions have become methods.
The data from the info hooks is now in the annotation.
Actions use the annotation class of Drupal\Core\Annotation\Action, and extend Drupal\Core\Action\ActionBase or Drupal\Core\Action\ConfigurableActionBase (if the action is configurable).

To implement an action in a module create a class in {module}/src/Plugin/Action/{ActionName}.php and extend ConfigurableActionBase.

hook_action_info() has been removed.

Drupal 7

/**
 * Implements hook_action_info().
 */
function system_action_info() {
  return array(
    'system_goto_action' => array(
      'type' => 'system',
      'label' => t('Redirect to URL'),
      'configurable' => TRUE,
      'triggers' => array('any'),
    ),
  );
}

/**
 * Settings form for system_goto_action().
 */
function system_goto_action_form($context) {
  $form['url'] = array(
    '#type' => 'textfield',
    '#title' => t('URL'),
    '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'),
    '#default_value' => isset($context['url']) ? $context['url'] : '',
    '#required' => TRUE,
  );
  return $form;
}

function system_goto_action_submit($form, $form_state) {
  return array(
    'url' => $form_state['values']['url']
  );
}

/**
 * Redirects to a different URL.
 *
 * @param $entity
 *   Ignored.
 * @param array $context
 *   Array with the following elements:
 *   - 'url': URL to redirect to. This will be passed through
 *     token_replace().
 *   - Other elements will be used as the data for token replacement.
 *
 * @ingroup actions
 */
function system_goto_action($entity, $context) {
  drupal_goto(token_replace($context['url'], $context));
}

Drupal 8

/**
 * @file
 * Contains \Drupal\action\Plugin\Action\GotoAction.
 */

namespace Drupal\action\Plugin\Action;

use Drupal\Core\Annotation\Action;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Action\ConfigurableActionBase;

/**
 * Redirects to a different URL.
 *
 * @Action(
 *   id = "action_goto_action",
 *   label = @Translation("Redirect to URL"),
 *   type = "system"
 * )
 */
class GotoAction extends ConfigurableActionBase {

  /**
   * {@inheritdoc}
   */
  public function execute($object = NULL) {
    drupal_goto($this->configuration['url']);
  }

  /**
   * {@inheritdoc}
   */
  protected function getDefaultConfiguration() {
    return array(
      'url' => '',
    );
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, array &$form_state) {
    $form['url'] = array(
      '#type' => 'textfield',
      '#title' => t('URL'),
      '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like @url.', array('@url' => 'http://drupal.org')),
      '#default_value' => $this->configuration['url'],
      '#required' => TRUE,
    );
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submit(array &$form, array &$form_state) {
    $this->configuration['url'] = $form_state['values']['url'];
  }

}

Configured actions

A configured action can be executed without additional user input.
For example, an administrator can create an instance of "system_goto_action" with the url already configured.
Drupal 7 called such actions "advanced actions", and used custom storage for them.

In Drupal 8 configured actions are configurable entities, which allows them to be managed through regular Entity API. For example:

// Creating a new configured action.
use Drupal\system\Entity\Action;
$action = Action::create(array(
  'id' => 'comment_unpublish_by_keyword_action',
  'label' => $this->randomName(),
  'type' => 'comment',
  'configuration' => array(
     'keywords' => array($keyword_1, $keyword_2),
  ),
  'plugin' => 'comment_unpublish_by_keyword_action',
));
$action->save();

// Loading all configured actions for the comment entity type.
$actions = entity_load_multiple_by_properties('action', array('type' => 'comment'))

A module can also provide configured actions through yml files. You need to do this when it's a non-configurable action. It needs to be stored in the /config/install directory and it's run on the module installation.

Example from core/modules/comment/config/install/system.action.comment_unpublish_action.yml:

id: comment_unpublish_action
label: 'Unpublish comment'
status: '1'
langcode: en
type: comment
plugin: comment_unpublish_action
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

Comments

claudiu.cristea’s picture

So, nothing was left out. To define actions we can: annotate, create entity, describe in yml :)

This isn't DX, sorry

Claudiu Cristea | Webikon.com

bojanz’s picture

This just made actions behave like the rest of core.
If you have a problem with annotated plugins, or entities that use yml files as storage, you can open an issue in the issue queue.

claudiu.cristea’s picture

Sorry, in fact I really appreciate the work on D8. What I don't understand right now is why using 2 non-PHP ways to describe structures. It's OK that they were removed from PHP in terms of loading but why both, annotations and yml? Why not having a single one? E.g. describe Entity Types, Actions in yml too. This may be confusing (at least for me).

Claudiu Cristea | Webikon.com

bojanz’s picture

The annotation holds what was previously in hook_action_info().
The yml file holds what was previously a row in the {actions} table (a configured action / "advanced action"). It's just storage for the "action" config entity type.

Please edit the change notice if you know how to make this distinction more clear.

mikejw’s picture

See ActionManager https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Action%21...
and for example, the content moderation module uses hook_action_info_alter

John Pitcairn’s picture

Indeed. So does Contact Storage, and Views Bulk Operations also dispatches the alter event.