class ContentEntityNormalizer in Default Content for D8 2.0.x
Normalizes and denormalizes content entities.
Hierarchy
- class \Drupal\default_content\Normalizer\ContentEntityNormalizer implements ContentEntityNormalizerInterface uses SerializedColumnNormalizerTrait
Expanded class hierarchy of ContentEntityNormalizer
1 string reference to 'ContentEntityNormalizer'
1 service uses ContentEntityNormalizer
File
- src/
Normalizer/ ContentEntityNormalizer.php, line 29
Namespace
Drupal\default_content\NormalizerView source
class ContentEntityNormalizer implements ContentEntityNormalizerInterface {
use SerializedColumnNormalizerTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The dependency information.
*
* Build during normalization, set and used to load entities during
* denormalization.
*
* @var array
*/
protected $dependencies;
/**
* Constructs an ContentEntityNormalizer object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, EntityRepositoryInterface $entity_repository, LanguageManagerInterface $language_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->moduleHandler = $module_handler;
$this->entityRepository = $entity_repository;
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function normalize(ContentEntityInterface $entity) {
// Define the generic metadata, define a version to allow to change the
// format later.
$normalized = [
'_meta' => [
'version' => '1.0',
'entity_type' => $entity
->getEntityTypeId(),
'uuid' => $entity
->uuid(),
],
];
$entity_type = $entity
->getEntityType();
if ($bundle_key = $entity_type
->getKey('bundle')) {
$normalized['_meta']['bundle'] = $entity
->bundle();
}
if ($langcode_key = $entity_type
->getKey('langcode')) {
$normalized['_meta']['default_langcode'] = $entity
->language()
->getId();
}
$is_root = FALSE;
if ($this->dependencies === NULL) {
$is_root = TRUE;
$this->dependencies = [];
}
$field_names = $this
->getFieldsToNormalize($entity);
// For menu links, add dependency information for the parent.
if ($entity instanceof MenuLinkContentInterface) {
if (strpos($entity
->getParentId(), PluginBase::DERIVATIVE_SEPARATOR) !== FALSE) {
[
$plugin_id,
$parent_uuid,
] = explode(PluginBase::DERIVATIVE_SEPARATOR, $entity
->getParentId());
if ($plugin_id === 'menu_link_content' && ($parent_entity = $this->entityRepository
->loadEntityByUuid('menu_link_content', $parent_uuid))) {
$this
->addDependency($parent_entity);
}
}
}
foreach ($entity
->getTranslationLanguages() as $langcode => $language) {
$translation = $entity
->getTranslation($langcode);
$normalized_translation = $this
->normalizeTranslation($translation, $field_names);
if ($translation
->isDefaultTranslation()) {
$normalized['default'] = $normalized_translation;
}
else {
$normalized['translations'][$langcode] = $normalized_translation;
}
}
if ($is_root) {
if ($this->dependencies) {
$normalized['_meta']['depends'] = $this->dependencies;
}
$this->dependencies = NULL;
}
return $normalized;
}
/**
* {@inheritdoc}
*/
public function denormalize(array $data) {
if (!isset($data['_meta']['entity_type'])) {
throw new UnexpectedValueException('The entity type metadata must be specified.');
}
if (!isset($data['_meta']['uuid'])) {
throw new UnexpectedValueException('The uuid metadata must be specified.');
}
$is_root = FALSE;
if ($this->dependencies === NULL && !empty($data['_meta']['depends'])) {
$is_root = TRUE;
$this->dependencies = $data['_meta']['depends'];
}
$entity_type = $this->entityTypeManager
->getDefinition($data['_meta']['entity_type']);
$values = [
'uuid' => $data['_meta']['uuid'],
];
if (!empty($data['_meta']['bundle'])) {
$values[$entity_type
->getKey('bundle')] = $data['_meta']['bundle'];
}
if (!empty($data['_meta']['default_langcode'])) {
$data = $this
->verifyNormalizedLanguage($data);
$values[$entity_type
->getKey('langcode')] = $data['_meta']['default_langcode'];
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->entityTypeManager
->getStorage($entity_type
->id())
->create($values);
foreach ($data['default'] as $field_name => $values) {
$this
->setFieldValues($entity, $field_name, $values);
}
if (!empty($data['translations'])) {
foreach ($data['translations'] as $langcode => $translation_data) {
if ($this->languageManager
->getLanguage($langcode)) {
$translation = $entity
->addTranslation($langcode, $entity
->toArray());
foreach ($translation_data as $field_name => $values) {
$this
->setFieldValues($translation, $field_name, $values);
}
}
}
}
if ($is_root) {
$this->dependencies = NULL;
}
return $entity;
}
/**
* Set field values based on the normalized data.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The content entity.
* @param string $field_name
* The name of the field.
* @param array $values
* The normalized data for the field.
*/
protected function setFieldValues(ContentEntityInterface $entity, string $field_name, array $values) {
if (!$entity
->hasField($field_name)) {
return;
}
foreach ($values as $delta => $item_value) {
if (!$entity
->get($field_name)
->get($delta)) {
$entity
->get($field_name)
->appendItem();
}
/** @var \Drupal\Core\Field\FieldItemInterface $item */
$item = $entity
->get($field_name)
->get($delta);
// Update the URI based on the target UUID for link fields.
if (isset($item_value['target_uuid']) && isset($item
->getProperties()['uri'])) {
$target_entity = $this
->loadEntityDependency($item_value['target_uuid']);
if ($target_entity) {
$item_value['uri'] = 'entity:' . $target_entity
->getEntityTypeId() . '/' . $target_entity
->id();
}
unset($item_value['target_uuid']);
}
$serialized_property_names = $this
->getCustomSerializedPropertyNames($item);
foreach ($item_value as $property_name => $value) {
if (\in_array($property_name, $serialized_property_names)) {
if (\is_string($value)) {
throw new \LogicException("Received string for serialized property {$field_name}.{$delta}.{$property_name}");
}
$value = serialize($value);
}
$property = $item
->get($property_name);
if ($property instanceof EntityReference) {
if (is_array($value)) {
$target_entity = $this
->denormalize($value);
}
else {
$target_entity = $this
->loadEntityDependency($value);
}
$property
->setValue($target_entity);
}
else {
$property
->setValue($value);
}
}
}
}
/**
* Returns a list of fields to be normalized.
*
* Ignores identifiers, fields that are already defined in the metadata,
* fields that are known to be overwritten like revision creation time
* and media thumbnail.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The content entity.
*
* @return string[]
* TThe list of fields to normalize.
*/
protected function getFieldsToNormalize(ContentEntityInterface $entity) : array {
$fields = TypedDataInternalPropertiesHelper::getNonInternalProperties($entity
->getTypedData());
// Unset identifiers.
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
$entity_type = $entity
->getEntityType();
unset($fields[$entity_type
->getKey('id')]);
unset($fields[$entity_type
->getKey('uuid')]);
if ($revision_key = $entity_type
->getKey('revision')) {
unset($fields[$revision_key]);
}
// Unset the bundle ang language code.
if ($bundle_key = $entity_type
->getKey('bundle')) {
unset($fields[$bundle_key]);
}
if ($langcode_key = $entity_type
->getKey('langcode')) {
unset($fields[$langcode_key]);
unset($fields[$entity_type
->getKey('default_langcode')]);
}
// Ignore the revision created timestamp, it is set on save.
if ($revision_created_key = $entity_type
->getRevisionMetadataKey('revision_created')) {
unset($fields[$revision_created_key]);
}
// Ignore the media thumbnail field, it is force regenerated for new
// media entities. See \Drupal\media\Entity\Media::shouldUpdateThumbnail().
if ($entity_type
->id() == 'media') {
unset($fields['thumbnail']);
}
// Ignore parent reference fields of composite entities.
$parent_reference_keys = [
'entity_revision_parent_type_field',
'entity_revision_parent_id_field',
'entity_revision_parent_field_name_field',
];
foreach ($parent_reference_keys as $parent_reference_key) {
if ($key_field_name = $entity_type
->get($parent_reference_key)) {
unset($fields[$key_field_name]);
}
}
return array_keys($fields);
}
/**
* Normalizes an entity (translation).
*
* @param \Drupal\Core\Entity\ContentEntityInterface $translation
* The entity to be normalized with its currently active language.
* @param string[] $field_names
* List of fields to normalize.
*
* @return array
* The normalized field values.
*/
protected function normalizeTranslation(ContentEntityInterface $translation, array $field_names) {
$translation_normalization = [];
foreach ($field_names as $field_name) {
if ($translation
->getFieldDefinition($field_name)
->getType() == 'changed') {
// Ignore the changed field.
continue;
}
if ($translation
->isDefaultTranslation() || $translation
->getFieldDefinition($field_name)
->isTranslatable()) {
foreach ($translation
->get($field_name) as $delta => $field_item) {
// Ignore empty field items.
if ($field_item
->isEmpty()) {
continue;
}
$serialized_property_names = $this
->getCustomSerializedPropertyNames($field_item);
/** @var \Drupal\Core\Field\FieldItemInterface $field_item */
foreach ($field_item
->getProperties(TRUE) as $property_name => $property) {
$value = $this
->getValueFromProperty($property, $field_item, $translation_normalization[$field_name][$delta]);
if ($value !== NULL) {
if (is_string($value) && in_array($property_name, $serialized_property_names)) {
$value = unserialize($value);
}
$translation_normalization[$field_name][$delta][$property_name] = $value;
}
}
}
}
}
return $translation_normalization;
}
/**
* Returns the value for a given property.
*
* @param \Drupal\Core\TypedData\TypedDataInterface $property
* The property to be normalized.
* @param \Drupal\Core\Field\FieldItemInterface $field_item
* The field item parent of the property.
* @param array|null $normalized_item
* The normalized values of the field item, can be used to set a value
* other than the current property.
*
* @return mixed|null
* The normalized value, a scalar, array or NULL to skip this property.
*/
protected function getValueFromProperty(TypedDataInterface $property, FieldItemInterface $field_item, &$normalized_item = NULL) {
$value = NULL;
// @todo Is there case where it is not the entity property?
if ($property
->getDataDefinition() instanceof DataReferenceTargetDefinition && $field_item->entity instanceof ContentEntityInterface) {
// Ignore broken references.
if (!$field_item->entity) {
return NULL;
}
// Ignore data reference target properties for content entities,
// except user 0 and 1, which can be referenced by ID unlike
// their UUIDs, which are expected to changed.
if (!$field_item->entity instanceof UserInterface || !in_array($field_item->entity
->id(), [
0,
1,
])) {
return NULL;
}
$value = $property
->getCastedValue();
}
elseif ($property instanceof EntityReference && $property
->getValue() instanceof ContentEntityInterface) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $target */
$target = $property
->getValue();
// Ignore user 0 and 1, they are stored with their ID.
if ($field_item->entity instanceof UserInterface && in_array($field_item->entity
->id(), [
0,
1,
])) {
return NULL;
}
// Regular entity references are referenced by UUID, entity
// types like paragraphs that are child entities are embedded
// directly.
if ($field_item
->getFieldDefinition()
->getType() == 'entity_reference_revisions' && $target
->getEntityType()
->get('entity_revision_parent_type_field')) {
$value = $this
->normalize($target);
}
else {
$this
->addDependency($target);
$value = $target
->uuid();
}
}
elseif ($property instanceof Uri) {
$value = $property
->getValue();
$scheme = parse_url($value, PHP_URL_SCHEME);
if ($scheme === 'entity') {
// Normalize entity URI's as UUID, do not set the URI property.
$path = parse_url($value, PHP_URL_PATH);
[
$target_entity_type_id,
$target_id,
] = explode('/', $path);
$target = $this->entityTypeManager
->getStorage($target_entity_type_id)
->load($target_id);
$this
->addDependency($target);
$normalized_item['target_uuid'] = $target
->uuid();
$value = NULL;
}
}
elseif ($property
->getName() == 'pid' && $field_item instanceof PathItem) {
// Ignore the pid attribute of path fields so that they are
// correctly-created.
return NULL;
}
elseif ($property instanceof PathautoState && $property
->getValue() !== NULL) {
// Explicitly include the pathauto state.
$value = (int) $property
->getValue();
}
elseif ($property instanceof PrimitiveInterface) {
$value = $property
->getCastedValue();
}
elseif (!$property
->getDataDefinition()
->isComputed()) {
$value = $property
->getValue();
}
return $value;
}
/**
* Adds an entity dependency to the normalization root.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity.
*/
protected function addDependency(ContentEntityInterface $entity) {
$this->dependencies[$entity
->uuid()] = $entity
->getEntityTypeId();
}
/**
* Loads the entity dependency by its UUID.
*
* @param string $target_uuid
* The entity UUID.
*
* @return \Drupal\Core\Entity\ContentEntityInterface|null
* The loaded entity.
*/
protected function loadEntityDependency(string $target_uuid) {
if (isset($this->dependencies[$target_uuid])) {
return $this->entityRepository
->loadEntityByUuid($this->dependencies[$target_uuid], $target_uuid);
}
return NULL;
}
/**
* Verifies that the site knows the default language of the normalized entity.
*
* Will attempt to switch to an alternative translation or just import it
* with the site default language.
*
* @param array $data
* The normalized entity data.
*
* @return array
* The normalized entity data, possibly with altered default language
* and translations.
*/
protected function verifyNormalizedLanguage(array $data) {
// Check the language. If the default language isn't known, import as one
// of the available translations if one exists with those values. If none
// exists, create the entity in the default language.
// During the installer, when installing with an alternative language,
// EN is still when modules are installed so check the default language
// instead.
if (!$this->languageManager
->getLanguage($data['_meta']['default_langcode']) || InstallerKernel::installationAttempted() && $this->languageManager
->getDefaultLanguage()
->getId() != $data['_meta']['default_langcode']) {
$use_default = TRUE;
if (isset($data['translations'])) {
foreach ($data['translations'] as $langcode => $translation_data) {
if ($this->languageManager
->getLanguage($langcode)) {
$data['_meta']['default_langcode'] = $langcode;
$data['default'] = \array_merge($data['default'], $translation_data);
unset($data['translations'][$langcode]);
$use_default = FALSE;
break;
}
}
}
if ($use_default) {
$data['_meta']['default_langcode'] = $this->languageManager
->getDefaultLanguage()
->getId();
}
}
return $data;
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ContentEntityNormalizer:: |
protected | property | The dependency information. | |
ContentEntityNormalizer:: |
protected | property | The entity repository. | |
ContentEntityNormalizer:: |
protected | property | The entity type manager. | |
ContentEntityNormalizer:: |
protected | property | The language manager. | |
ContentEntityNormalizer:: |
protected | property | The module handler. | |
ContentEntityNormalizer:: |
protected | function | Adds an entity dependency to the normalization root. | |
ContentEntityNormalizer:: |
public | function |
Converts the normalized data back into a content entity. Overrides ContentEntityNormalizerInterface:: |
|
ContentEntityNormalizer:: |
protected | function | Returns a list of fields to be normalized. | |
ContentEntityNormalizer:: |
protected | function | Returns the value for a given property. | |
ContentEntityNormalizer:: |
protected | function | Loads the entity dependency by its UUID. | |
ContentEntityNormalizer:: |
public | function |
Normalizes the entity into an array structure. Overrides ContentEntityNormalizerInterface:: |
|
ContentEntityNormalizer:: |
protected | function | Normalizes an entity (translation). | |
ContentEntityNormalizer:: |
protected | function | Set field values based on the normalized data. | |
ContentEntityNormalizer:: |
protected | function | Verifies that the site knows the default language of the normalized entity. | |
ContentEntityNormalizer:: |
public | function | Constructs an ContentEntityNormalizer object. | |
SerializedColumnNormalizerTrait:: |
protected | function | Checks if there is a serialized string for a column. | |
SerializedColumnNormalizerTrait:: |
protected | function | Checks if the data contains string value for serialize column. | |
SerializedColumnNormalizerTrait:: |
protected | function | Gets the names of all properties the plugin treats as serialized data. | |
SerializedColumnNormalizerTrait:: |
protected | function | Gets the names of all serialized properties. |