diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 73db361..9972985 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -580,6 +580,9 @@ field.field_settings.entity_reference: type: mapping label: 'Entity reference field settings' mapping: + serialize_embedded_entities: + type: boolean + label: 'Serialize entity references together with the parent entity' handler: type: string label: 'Reference method' diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 3834dae..309a37d 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -325,7 +325,7 @@ protected function has($id, EntityInterface $entity) { * @return \Drupal\Core\Entity\EntityInterface[] * Array of entities from the entity cache. */ - protected function getFromStaticCache(array $ids) { + public function getFromStaticCache(array $ids) { $entities = array(); // Load any available entities from the internal cache. if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) { @@ -347,7 +347,7 @@ protected function getFromStaticCache(array $ids) { * @param \Drupal\Core\Entity\EntityInterface[] $entities * Entities to store in the cache. */ - protected function setStaticCache(array $entities) { + public function setStaticCache(array $entities) { if ($this->entityType->isStaticallyCacheable()) { $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys()); foreach ($entities as $id => $entity) { diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 1d69be2..b3fbc18 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Entity; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Plugin\DataType\EntityReference; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Language\Language; @@ -51,6 +52,16 @@ protected $fieldDefinitions; /** + * Local cache for field definitions filtered by setting. + * + * @see ContentEntityBase::getFieldDefinitionsFilteredBySetting() + * + * @var array + */ + protected $fieldDefinitionsFilteredBySetting; + + + /** * Local cache for the available language objects. * * @var \Drupal\Core\Language\LanguageInterface[] @@ -407,15 +418,72 @@ protected function clearTranslationCache() { } /** + * Gets an array of field definitions of all contained fields filtered by the + * given setting and its value. + * + * @param string $setting + * The name of the setting. + * @param string $value + * The value of the setting. + * + * @return \Drupal\Core\Field\FieldDefinitionInterface[] + * An array of field definitions, keyed by field name. + * + * @see \Drupal\Core\Entity\EntityFieldManagerInterface::getFieldDefinitions() + */ + protected function getFieldDefinitionsFilteredBySetting($setting, $value) { + if (!isset($this->fieldDefinitionsFilteredBySetting[$setting][$value])) { + // Initialize the combination of setting and a value in case there is + // nothing found to not iterate over the field definitions again. + $this->fieldDefinitionsFilteredBySetting[$setting][$value] = []; + foreach ($this->getFieldDefinitions() as $name => $definition) { + if ($definition->getSetting($setting) == $value) { + $this->fieldDefinitionsFilteredBySetting[$setting][$value][$name] = $definition; + } + } + } + + return $this->fieldDefinitionsFilteredBySetting[$setting][$value]; + } + + /** * {@inheritdoc} */ public function __sleep() { + $serialize_fields_with_embedded_entities = $this->getFieldDefinitionsFilteredBySetting('serialize_embedded_entities', TRUE); + $langcodes = array_keys($this->getTranslationLanguages()); + foreach ($serialize_fields_with_embedded_entities as $name => $definition) { + // Initialize not yet initialized fields for which the computed field + // values have to be serialized. + $iterate_langcodes = $definition->isTranslatable() ? $langcodes : [$this->language()->getId()]; + foreach ($iterate_langcodes as $langcode) { + if (!isset($this->fields[$name][$langcode])) { + $this->getTranslatedField($name, $langcode); + } + } + } + // Get the values of instantiated field objects, only serialize the values. foreach ($this->fields as $name => $fields) { foreach ($fields as $langcode => $field) { - $this->values[$name][$langcode] = $field->getValue(); + + // TODO remove this check after https://www.drupal.org/node/2828133 + // gets in. + if (!$serialize_fields_with_embedded_entities[$name]->isTranslatable() && ($langcode != LanguageInterface::LANGCODE_DEFAULT)) { + continue; + } + + $this->values[$name][$langcode] = $field->getValue(isset($serialize_fields_with_embedded_entities[$name])); + if (isset($serialize_fields_with_embedded_entities[$name])) { + foreach ($this->values[$name][$langcode] as $value) { + if (isset($value['entity']) && ($value['entity'] instanceof EntityInterface)) { + $this->cacheTags = Cache::mergeTags($this->cacheTags, $value['entity']->getCacheTags()); + } + } + } } } + $this->fields = array(); $this->fieldDefinitions = NULL; $this->languages = NULL; @@ -424,6 +492,37 @@ public function __sleep() { return parent::__sleep(); } + public function __wakeup() { + parent::__wakeup(); + + $entity_storages = []; + foreach (array_keys($this->getFieldDefinitionsFilteredBySetting('serialize_embedded_entities', TRUE)) as $field_name) { + foreach ($this->values[$field_name] as $langcode => $values) { + foreach ($values as $delta => $value) { + /** @var EntityInterface $entity */ + $entity = isset($value['entity']) ? $value['entity'] : NULL; + if ($entity && !$entity->isNew()) { + // Cover the case where an entity reference field might reference + // different entity types at once. + $entity_type_id = $entity->getEntityTypeId(); + $current_entity_storage = isset($entity_storages[$entity_type_id]) ? $entity_storages[$entity_type_id] : $this->entityTypeManager()->getStorage($entity_type_id); + $consider_static_cache = $entity->getEntityType()->isRevisionable() && $entity instanceof RevisionableInterface && $entity->isDefaultRevision() || !($entity instanceof RevisionableInterface); + if (!$consider_static_cache) { + continue; + } + elseif ($static_cache_entities = $current_entity_storage->getFromStaticCache([$entity->id()])) { + $value['entity'] = $static_cache_entities[$entity->id()]; + } + else { + $current_entity_storage->setStaticCache([$entity->id() => $entity]); + } + } + } + } + } + + } + /** * {@inheritdoc} */ @@ -807,7 +906,7 @@ protected function initializeTranslation($langcode) { * {@inheritdoc} */ public function hasTranslation($langcode) { - if ($langcode == $this->defaultLangcode) { + if ($langcode == $this->defaultLangcode) {# $langcode = LanguageInterface::LANGCODE_DEFAULT; } return !empty($this->translations[$langcode]['status']); diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index afdce40..5daf137 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -596,7 +596,7 @@ protected function getFromPersistentCache(array &$ids = NULL) { foreach ($ids as $index => $id) { $cid = $cid_map[$id]; if (isset($cache[$cid])) { - $entities[$id] = $cache[$cid]->data; + $entities[$id] = unserialize($cache[$cid]->data); unset($ids[$index]); } } @@ -620,7 +620,11 @@ protected function setPersistentCache($entities) { 'entity_field_info', ); foreach ($entities as $id => $entity) { - $this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags); + // We first have to serialize the entity as during the serialization + // process the entity cache tags might be extended. + $serialized_entity = serialize($entity); + $entity_cache_tags = Cache::mergeTags($cache_tags, $entity->getCacheTags()); + $this->cacheBackend->set($this->buildCacheId($id), $serialized_entity, CacheBackendInterface::CACHE_PERMANENT, $entity_cache_tags); } } diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index f583121..634dc95 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -132,7 +132,7 @@ public function resetCache(array $ids = NULL) { * @return \Drupal\Core\Entity\EntityInterface[] * Array of entities from the entity cache. */ - protected function getFromStaticCache(array $ids) { + public function getFromStaticCache(array $ids) { $entities = array(); // Load any available entities from the internal cache. if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) { @@ -147,7 +147,7 @@ protected function getFromStaticCache(array $ids) { * @param \Drupal\Core\Entity\EntityInterface[] $entities * Entities to store in the cache. */ - protected function setStaticCache(array $entities) { + public function setStaticCache(array $entities) { if ($this->entityType->isStaticallyCacheable()) { $this->entities += $entities; } diff --git a/core/lib/Drupal/Core/Field/EntityReferenceInlineWidgetBase.php b/core/lib/Drupal/Core/Field/EntityReferenceInlineWidgetBase.php new file mode 100644 index 0000000..251129c --- /dev/null +++ b/core/lib/Drupal/Core/Field/EntityReferenceInlineWidgetBase.php @@ -0,0 +1,19 @@ +getSetting('serialize_embedded_entities'); + } + +} diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index 63bd90d..fd84fa1 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -53,6 +53,7 @@ public static function defaultStorageSettings() { */ public static function defaultFieldSettings() { return array( + 'serialize_embedded_entities' => FALSE, 'handler' => 'default', 'handler_settings' => array(), ) + parent::defaultFieldSettings(); @@ -189,7 +190,7 @@ public function setValue($values, $notify = TRUE) { // If the entity has been saved and we're trying to set both the // target_id and the entity values with a non-null target ID, then the // value for target_id should match the ID of the entity value. - if (!$this->entity->isNew() && $values['target_id'] !== NULL && ($entity_id !== $values['target_id'])) { + if (!$this->entity->isNew() && $values['target_id'] !== NULL && ($entity_id != $values['target_id'])) { throw new \InvalidArgumentException('The target id and entity passed to the entity reference item do not match.'); } } @@ -204,12 +205,12 @@ public function setValue($values, $notify = TRUE) { /** * {@inheritdoc} */ - public function getValue() { + public function getValue($include_computed = FALSE) { $values = parent::getValue(); // If there is an unsaved entity, return it as part of the field item values // to ensure idempotency of getValue() / setValue(). - if ($this->hasNewEntity()) { + if ($include_computed || $this->hasNewEntity()) { $values['entity'] = $this->entity; } return $values; @@ -339,6 +340,14 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#element_validate' => array(array(get_class($this), 'fieldSettingsFormValidate')), ); + + $form['serialize_embedded_entities'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Serialize entity references together with the parent entity'), + '#description' => $this->t('If the field widget to be used for this field is an entity reference inline widget then the setting must be set.'), + '#default_value' => $field->getSetting('serialize_embedded_entities'), + ); + $form['handler'] = array( '#type' => 'details', '#title' => t('Reference type'), diff --git a/core/modules/field/field.install b/core/modules/field/field.install index ecd2b31..0c30a1b 100644 --- a/core/modules/field/field.install +++ b/core/modules/field/field.install @@ -104,3 +104,30 @@ function field_update_8003() { } } } + +/** + * Populate the new 'serialize_embedded_entities' setting for entity reference + * fields. + */ +function field_update_8004() { + $config = \Drupal::configFactory(); + /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */ + $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); + + // Iterate over all fields. + foreach ($config->listAll('field.field.') as $field_id) { + $field = $config->getEditable($field_id); + $class = $field_type_manager->getPluginClass($field->get('field_type')); + + // Deal only with entity reference fields and descendants. + if ($class == EntityReferenceItem::class || is_subclass_of($class, EntityReferenceItem::class)) { + $settings = $field->get('settings'); + + if (!isset($settings['serialize_embedded_entities'])) { + $settings['serialize_embedded_entities'] = FALSE; + } + + $field->set('settings', $settings)->save(TRUE); + } + } +}