View providers API

Last updated on
22 November 2021

Introduction

Some modules define their own views data, allowing site administrators to create different types of views with non-standard structure of results and possibly showing multiple or custom entity types. Those modules need to communicate with VBO through the provided API and provide information about displayed entity types and the method to get entities from their view result rows.

NOTE: Views Bulk Operations uses the same API to get information about standard entity views. See \Drupal\views_bulk_operations\EventSubscriber\ViewsBulkOperationsEventSubscriber for reference.

Getting started

Views Bulk Operations use the Event Dispatcher to dispatch an event named 'views_bulk_operations.view_data'. Any module can subscribe to that event using an event subscriber service (more about events here).

To define a working event subscriber, one needs to define it in module.services.yml:

services:
  some_module.view_data_provider:
    class: Drupal\some_module\EventSubscriber\SomeModuleEventSubscriber
    tags:
      - { name: event_subscriber }

Next, SomeModuleEventSubscriber class must be included in /src/EventSubscriber/SomeModuleEventSubscriber.php:

<?php

namespace Drupal\some_module\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\views_bulk_operations\ViewsBulkOperationsEvent;

/**
 * Event subscriber class.
 */
class SomeModuleEventSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events = [];
    // The next line prevents hard dependency on VBO module.
    if (class_exists(ViewsBulkOperationsEvent::class)) {
      $events['views_bulk_operations.view_data'][] = ['provideViewData', 0];
    }
    return $events;
  }

  /**
   * Provide entity type data and entity getter to VBO.
   *
   * @param \Drupal\views_bulk_operations\ViewsBulkOperationsEvent $event
   *   The event object.
   */
  public function provideViewData(ViewsBulkOperationsEvent $event) {
    if ($event->getProvider() === 'some_module') {
      $event->setEntityTypeIds(['node']);
      $event->setEntityGetter([
        'file' => drupal_get_path('module', 'some_module') . '/src/someClass.php',
        'callable' => '\Drupal\some_module\someClass::getEntityFromRow',
      ]);
    }
  }

}

To use an object method for the entity getter, simply set 'callable' parameter to an array, the 'callable' parameter can be any of the options described in the PHP docs, even an injected service method:

$event->setEntityGetter([
  'callable' => [$this->rowGetter, 'getEntityFromRow'],
]);

The event is fired on every page where needed so serialization issues will not occur.

Providing data to Views Bulk Operations

VBO needs two things to operate: array of entity types provided by the view and a method to get entities from view results (see \Drupal\views_bulk_operations\EventSubscriber\ViewsBulkOperationsEventSubscriber::provideViewData). Both are passed to VBO through the $event object and two setter methods: $event->setEntityTypeIds and $event->setEntityGetter. The event object also has the following getter methods to provide data that may be needed to determine entity types and the getter method:

  • $event->getProvider() - gets the view provider (usually the name of the providing module),
  • $event->getViewData() - gets the view data defined by the provider,
  • $event->getView() - gets the entire ViewExecutable object.

The setEntityTypeIds method expects an array of entity type IDs.

The setEntityGetter method expects an array containing elements:

  • callable: the method (may even be a global function) that will get entities form view result rows,
  • file (optional): a file that contains the callable that needs to be included.

The entity getter method itself accepts parameters:

  1. \Drupal\views\ResultRow $row - the views result row as in $view->result,
  2. string $relationship_id - relationship ID of the view (usually 'none').
  3. \Drupal\views\ViewExecutable $view - the current view object.

Example entity getter method (according to file param in the previous code example it should reside in src/someClass.php):

<?php

namespace Drupal\some_module;

use Drupal\views\ViewExecutable;
use Drupal\views\ResultRow;
use Drupal\Core\TypedData\TranslatableInterface;

/**
 * Contains entity getter method.
 */
class SomeClass {
  
  /**
   * The entity getter method.
   *
   * @param \Drupal\views\ResultRow $row
   *   Views result row.
   * @param string $relationship_id
   *   Id of the view relationship.
   * @param \Drupal\views\ViewExecutable $view
   *   The current view object.
   */
  public static function getEntityFromRow(ResultRow $row, $relationship_id, ViewExecutable $view) {
    $entity = $row->_entity;

    if ($entity->isTranslatable()) {
      // May not always be reliable.
      $language_field = $entity->getEntityTypeId() . '_field_data_langcode';
      if ($entity instanceof TranslatableInterface && isset($row->{$language_field})) {
        return $entity->getTranslation($row->{$language_field});
      }
    }
    return $entity;
  }

}

NOTE: Remember about getting entity translation in the getter method if applicable. Standard view rows contain untranslated entities.

Additional considerations

Remember to specify a base field (typically an ID) in your implementation of hook_views_data(), e.g. $data['BASETABLE']['table']['base']['field'] = 'key'. Also, ViewsBulkOperationsActionProcessor::populateQueue() will call the addWhere() method on your Views query plugin (which generally inherits from Drupal\views\Plugin\views\query\QueryPluginBase) in order to filter the view using this field (and the IN operator), which means that:

  • this addWhere() method needs to exist and be fully functional,
  • your view has to be able to make a query using this property and the IN operator (which is not always so obvious, depending on the backend).

Help improve this page

Page status: No known problems

You can: