6

I would like to expose to the user their choice of field formatters and the respective field formatter options. The selected formatter would then render an arbitrary array of items in the same format as a value for that field type, without any kind of parent entity.

Again, I want to render what looks to be an array of field values, without having an actual field or entity with a field value.

In Drupal 7 I could accomplish this using the following code (Gist formatted version):

// Render a file using the 'Generic file' formatter (a nice link with an icon):
$file = file_load(1);
$field = array('type' => 'file');
// File fields are stupid with their field values.
$items = array(0 => array('fid' => $file->fid) + (array) $file);
$display = array('type' => 'file_default');
$output = field_items_render($field, array(), $items, $display);
return drupal_render($output);

// Render node 1 with an entityreference formatter
$field = array('type' => 'entityreference');
$items = array(0 => array('target_id' => 1));
$display = array('type' => 'entityreference_label', 'settings' => array('link' => TRUE));
$output = field_items_render($field, array(), $items, $display);
return drupal_render($output);
// Outputs <a href="/node/1">Title of node 1</a>

function field_items_render(array $field, array $instance, array $items, array $display, $langcode = LANGUAGE_NONE) {
  $field += field_info_field_types($field['type']);

  $display += field_info_formatter_types($display['type']);

  $instance += array(
    'entity_type' => 'node',
    'bundle' => '_fake',
    'settings' => array(),
  );
  $instance['settings'] += $field['instance_settings'];

  $entity_type = 'node';
  $entity = NULL;
  $entities = array(0 => $entity);
  $entity_items = array(0 => &$items);

  // Run hook_field_prepare_view() on the field module.
  $function = $field['module'] . '_field_prepare_view';
  if (function_exists($function)) {
    $function($entity_type, $entities, $field, $instances, $langcode, $entity_items);
  }

  // Run hook_field_formatter_prepare_view() on the formatter module.
  $function = $display['module'] . '_field_formatter_prepare_view';
  if (function_exists($function)) {
    $function($entity_type, $entities, $field, array(0 => $instance), $langcode, $entity_items, array(0 => $display));
  }

  // Run hook_field_formatter_View() on the formatter module.
  $function = $display['module'] . '_field_formatter_view';
  $result = $function($entity_type, NULL, $field, $instance, $langcode, $items, $display);

  return $result;
}

While I can do this in Drupal 7 very relatively easily, how can I accomplish the same in Drupal 8 (or at least help lead me down the right path through all the major parts of this)?

5
  • I think it might help to clarify that you aren't just rendering the entity itself, but you are rendering it with a field formatter. i.e. essentially you are trying to render a field with a parent entity and it just so happens that the field data contains an entity.
    – Mikey P
    May 24 '14 at 2:51
  • I've said in the title itself 'without a parent entity' but I will clarify.
    – Dave Reid
    May 24 '14 at 2:51
  • For reference, this code is copied pretty much from field_view_field in D7. I know that it's equivelent in D8 is EntityViewBuilder::viewField() but my pain points are figuring out FieldItemListInterface because it seems that everything must have a parent entity in D8.
    – Dave Reid
    May 24 '14 at 2:55
  • If you know the field name and the formatter name as well as the formatter setting you should be able to create a fake entity and render it.```` $display = array( 'type' => $formatter_name, 'settings' => $formatter_settings, 'label' => 'hidden', ); $render_array = $entity->get($field_name)->view($display);```` May 24 '14 at 9:35
  • @DanielWehner there's no actual field, so I can't actually do that.
    – Dave Reid
    May 25 '14 at 18:09
10

Yeah, you can't get around having an entity object, but it doesn't matter what it is, the only place where it's needed in the following code is FormatterBase::view(), to set #entity_type, which will later be used for the class name.

<?php

use Drupal\Core\Field\FieldDefinition;
use Drupal\node\Entity\Node;

// Load the node, you can also create a temporary object or have your own
// dummy object that implements EntityInterface. (Might need ContentEntityInterface, not sure).
$node = Node::load(1);

// Create the field definition, some might need more settings, it currently
// doesn't load in the field type defaults. https://drupal.org/node/2116341
// Field name is only set to avoid broken CSS classes.
$definition = FieldDefinition::create('entity_reference')
  ->setName('whatever')
  ->setSetting('target_type', 'node');

/* @var \Drupal\Core\Field\FieldItemListInterface $items $items */
// Create a field item list object, 1 is the value, array('target_id' => 1)
// would work too, or multiple values. 1 is passed down from the list to the
// field item, which knows that an integer is the ID.
$items = \Drupal::typedDataManager()->create($definition, 1, $definition->getName(), $node);

/* @var \Drupal\Core\Field\FormatterInterface $formatter */
// Create the formatter plugin. Will use the default formatter for that field
// type if none is passed.
$formatter = \Drupal::service('plugin.manager.field.formatter')->getInstance(array(
  'field_definition' => $definition,
  'view_mode' => 'default',
  'configuration' => array(
    'label' => 'hidden',
    'type' => 'entityreference_label',
    'settings' => array('link' => TRUE),
  )
));

// Prepare, expects an array of items, keyed by parent entity ID, not sure if
// actually used, just array($items) worked too.
$formatter->prepareView(array($node->id() => $items));
print \Drupal::service('renderer')->renderRoot($formatter->view($items));

Yes, finding this out is not trivial, but I disagree with you that the 7.x code is relatively easy, it's just that you know the 7.x field API and internal magic callbacks very well. The main difference is that instead of building arbitrary array structures and passing them around, you need to find the right object/class/service and call methods on there. Once you found them, it's not that hard, FormatterPluginManager::getInstance() for example documents very well what it expects.

5
  • The trickiest part here is probably the typed data manager line, I wrote that based on looking up how a content entity creates it (ContentEntityBase::getTranslatedField(), which calls getPropertyInstance(), which then calls create()
    – Berdir
    May 24 '14 at 10:48
  • Yes this seems to mostly work, thank you! Although when using a file or image field, the FieldItemListInterface creation doesn't seem to work. When attempting to render it, I get Fatal error: Call to undefined method Drupal\entity_reference\ConfigurableEntityReferenceItem::isDisplayed(). It seems it is not using the item class (FileItem or ImageItem).
    – Dave Reid
    May 30 '14 at 4:13
  • Filed drupal.org/node/2276749
    – Dave Reid
    May 30 '14 at 4:51
  • 2
    Note that Drupal\Core\Field\FieldDefinition no longer exists; it was replaced with Drupal\Core\Field\BaseFieldDefinition during the Drupal 8 development cycle.
    – mlncn
    May 10 '16 at 18:25
  • Btw. You should use \Drupal::service('renderer')->renderRoot() instead of deprecated drupal_render().
    – kenorb
    Aug 4 '17 at 22:40
1

Yes, the various field plugins (field types, formatters, widgets) all assume there is a parent entity to the Items object they work with.

That is the 95% use case you have in mind when you write a widget or formatter, and having to litter the code with "if (I really have an entity)" checks would be really painful. + IIRC, allowing a field witout a parent entity would be tricky internally (no actual example in mind right now, sorry).

@Berdir's example code is correct, but after you've created the $items object, you should be able to simply do $items->view($display_settings), which takes care of instanciating the formatter plugin and calling the right methods on it. It's D8's version of field_view_field().

Also, the code about \Drupal::typedDataManager()->create($definition) should only be needed if you need to work on an arbitrary field definition, that is not part of the "official" fields for the entity type & bundle.

If e.g working on the "official" body field as defined on article nodes, simply doing

$your_stub_entity->body->value = "foobar";
$your_stub_entity->body->view($display_settings);

should work ?

3
  • I think displaying a field that doesn't actually exist as a real field on a real entity is exactly his use case, so $field->view() doesn't work as that calls back on to the entity, which doesn't know anything about the field.
    – Berdir
    May 24 '14 at 22:14
  • Damn, true. Filed drupal.org/node/2274169. The Entity Field API as a whole relies on the notion of a "field definitions registry", and constantly pulls from it. Thus, it doesn't lend itself to working with arbitrary runtime definitions. I.e : field definitions are pulled from the registry, but it's hard to inject them...
    – yched
    May 25 '14 at 21:44
  • Posted a preliminary patch in drupal.org/node/2274169
    – yched
    Jun 13 '14 at 19:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.