Change record status: 
Project: 
Introduced in branch: 
8.7.x
Introduced in version: 
8.7.0
Description: 

When presenting a (content) entity for interaction or preview by a content author, many factors should be taken into account: depending on the entity type, the entity may be revisionable, translatable or even both. The basic case of an entity type not being neither revisionable nor translatable is also supported but it is not very common, at least in core.

When dealing with such an entity it is now possible to retrieve the fittest entity variant (generic term referring to both revisions and translations), for the specified context, which by default is the current context. This new entity variant negotiation API relies on the Context API to provide information about the context in which the entity will be exposed for interaction or preview. The current core implementation only uses content language from the contextual information, however, since it is meant to be replaced by the more advanced one proposed in #3023194: [PP-2] Add parallel revisioning support, all available contexts are assumed when no explicit contexts are specified. In fact, alternative implementations might use any contextual information available as well. Contexts aside, any implementation of the API should only take care of negotiating the fittest variant for the specified context, access control will need to be applied on top of that.

This API introduces/formalizes two concepts:

  • canonical variant, which is typically the variant that would be displayed on the entity's canonical route;
  • active variant, which is the most up-to-date variant (in the context scope) and thus is suitable for editing and previewing in editorial workflows.

It provides methods to retrieve these different types of variants and also their multiple counterparts, allowing to apply the same negotiation logic to multiple entities at the same time. API implementations are expected to perform both revision and translation negotiation, thus language fallback rules should be applied in both cases.

The EntityStorageInterface::load() and RevisionableStorageInterface::loadRevision() methods (and their multiple counterparts) are unaffected by this addition, in fact the entity repository relies on the storage layer to perform the negotiation, so storage methods should still be used when needing to directly load a specific entity or revision.

Code examples

/** @var \Drupal\Core\Experimental\Entity\EntityRepositoryInterface $entity_repository */
$entity_repository = \Drupal::service('entity.repository');

// This is the node revision that would typically be displayed on "/it/node/1".
// The current context, including the current content language, is used by
// default to perform negotiation.
$canonical = $entity_repository->getCanonical('node', 1);

// This is the node revision that is loaded in the node form or displayed on
// "/it/node/1/latest" when Content Moderation is enabled for this node type.
// Again, the current context, including the current content language, is used
// by default to perform negotiation.
$active = $entity_repository->getActive('node', 1);
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;

// Contextual information can be explicitly specified, a typical example could
// be sending out a notification email containing the node preview.
$language_manager = \Drupal::service('language_manager');
$contexts = [
  '@language.current_language_context:' . LanguageInterface::TYPE_CONTENT => new Context(new ContextDefinition('language'), $language_manager->getLanguage('ro'),
];
$active = $entity_repository->getActive('node', 1, $contexts);

// If the default contextual information needs to be kept, aside from the bits
// being customized, the following code should be used. 
/** @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface $contexts_repository */
$contexts_repository = \Drupal::service('context.repository');
$contexts = $contexts_repository->getAvailableContexts();
$contexts['@language.current_language_context:' . LanguageInterface::TYPE_CONTENT] = new Context(new ContextDefinition('language'), 'ro');
$entity = $this->entityRepository->getActive('node', $value, $contexts);

See \Drupal\KernelTests\Core\Entity\EntityRepositoryTest for more examples.

Impacts: 
Module developers