Ok, I found one way to do this: Instead of registering just one handler, you register one handler per entity type. To save time, we can reuse the stuff already registered by Entity API, with entity_views_data(). This means, of course, the module needs to add Entity API as a dependency.
/** * Implements hook_views_data_alter(). */ function EXAMPLE_views_data_alter(&$data) { foreach (entity_get_info() as $entity_type => $info) { if (!isset($data['views_entity_' . $entity_type]['rendered_entity'])) { // Only register for those entity types where Entity API already did its job. continue; } $data['views_entity_' . $entity_type]['EXAMPLE'] = array( 'title' => t('Entity display plugin'), 'help' => t('The @entity-type of the current relationship rendered using an entity display plugin.', array('@entity-type' => $info['label'])), 'field' => array( 'handler' => ExampleViewsFieldHandler::class, 'type' => $entity_type, // The EntityFieldHandlerHelper treats the 'entity object' data // selector as special case for loading the base entity. // @todo Not sure if we really need this. 'real field' => 'entity object', ), ); } }
And below is the field handler class, just a bit simplified.
Maybe it does a bit more than we really need here. E.g. we don't really need to create a render array for each row, we could work with html directly. But I think it is a good start.
class ExampleViewsFieldHandler extends \views_handler_field { function query() { // do nothing -- to override the parent query. } /** * Run before any fields are rendered. * * This gives the handlers some time to set up before any handler has * been rendered. * * @param object[] $rows * An array of all objects returned from the query. */ function pre_render(&$rows) { /** * @var string $entityType * @var object[] $entities */ list($entityType, $entities) = $this->getResultEntities($rows); // Build the entities. $builds = $this->buildMultiple($entityType, $entities); foreach ($rows as $rowIndex => $row) { if (isset($builds[$rowIndex])) { /** @noinspection PhpUndefinedFieldInspection */ $row->example_field_builds[$this->position] = $builds[$rowIndex]; } else { /** @noinspection PhpUndefinedFieldInspection */ unset($row->example_field_builds[$this->position]); } } } /** * @param object[] $rows * * @return mixed[] * * @see EntityFieldHandlerHelper::pre_render() */ protected function getResultEntities(array $rows) { $relationship = !empty($this->relationship) ? $this->relationship : NULL; $field_alias = isset($this->real_field) ? $this->real_field : NULL; // Some views query classes want/allow a third parameter specifying the field name. /** @noinspection PhpMethodParametersCountMismatchInspection */ list($entityType, $entities) = $this->view->query->get_result_entities($rows, $relationship, $field_alias); return array($entityType, $entities); } /** * @param object $row * * @return string */ function render($row) { /** @noinspection PhpUndefinedFieldInspection */ return isset($row->example_field_builds[$this->position]) ? drupal_render($row->example_field_builds[$this->position]) : NULL; } /** * @param string $entityType * @param object[] $entities * * @return array[] * A render array for each entity. */ protected function buildMultiple($entityType, array $entities) { $builds = array(); foreach ($entities as $rowIndex => $entity) { $builds[$rowIndex] = array( '#markup' => entity_label($entityType, $entity), ); } return $builds; } }