Update to Drupal 8.0.0 beta 14. For more information, see https://drupal.org/node/2544542
This commit is contained in:
parent
3b2511d96d
commit
81ccda77eb
2155 changed files with 54307 additions and 46870 deletions
|
@ -138,12 +138,33 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
protected $isDefaultRevision = TRUE;
|
||||
|
||||
/**
|
||||
* Holds entity keys like the ID, bundle and revision ID.
|
||||
* Holds translatable entity keys such as the ID, bundle and revision ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entityKeys = array();
|
||||
|
||||
/**
|
||||
* Holds translatable entity keys such as the label.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $translatableEntityKeys = array();
|
||||
|
||||
/**
|
||||
* Whether entity validation was performed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $validated = FALSE;
|
||||
|
||||
/**
|
||||
* Whether entity validation is required before saving the entity.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $validationRequired = FALSE;
|
||||
|
||||
/**
|
||||
* Overrides Entity::__construct().
|
||||
*/
|
||||
|
@ -165,14 +186,36 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
$this->values = $values;
|
||||
foreach ($this->getEntityType()->getKeys() as $key => $field_name) {
|
||||
if (isset($this->values[$field_name])) {
|
||||
if (is_array($this->values[$field_name]) && isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
|
||||
if (is_array($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
|
||||
if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'])) {
|
||||
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'];
|
||||
if (is_array($this->values[$field_name])) {
|
||||
// We store untranslatable fields into an entity key without using a
|
||||
// langcode key.
|
||||
if (!$this->getFieldDefinition($field_name)->isTranslatable()) {
|
||||
if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
|
||||
if (is_array($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
|
||||
if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'])) {
|
||||
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT];
|
||||
// We save translatable fields such as the publishing status of a node
|
||||
// into an entity key array keyed by langcode as a performance
|
||||
// optimization, so we don't have to go through TypedData when we
|
||||
// need these values.
|
||||
foreach ($this->values[$field_name] as $langcode => $field_value) {
|
||||
if (is_array($this->values[$field_name][$langcode])) {
|
||||
if (isset($this->values[$field_name][$langcode][0]['value'])) {
|
||||
$this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode][0]['value'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->translatableEntityKeys[$key][$langcode] = $this->values[$field_name][$langcode];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +263,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
*/
|
||||
public function setNewRevision($value = TRUE) {
|
||||
if (!$this->getEntityType()->hasKey('revision')) {
|
||||
throw new \LogicException(SafeMarkup::format('Entity type @entity_type does not support revisions.', ['@entity_type' => $this->getEntityTypeId()]));
|
||||
throw new \LogicException("Entity type {$this->getEntityTypeId()} does not support revisions.");
|
||||
}
|
||||
|
||||
if ($value && !$this->newRevision) {
|
||||
|
@ -302,6 +345,23 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
return !empty($bundles[$this->bundle()]['translatable']) && !$this->getUntranslated()->language()->isLocked() && $this->languageManager()->isMultilingual();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
// An entity requiring validation should not be saved if it has not been
|
||||
// actually validated.
|
||||
if ($this->validationRequired && !$this->validated) {
|
||||
// @todo Make this an assertion in https://www.drupal.org/node/2408013.
|
||||
throw new \LogicException('Entity validation was skipped.');
|
||||
}
|
||||
else {
|
||||
$this->validated = FALSE;
|
||||
}
|
||||
|
||||
parent::preSave($storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -312,10 +372,26 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate() {
|
||||
$this->validated = TRUE;
|
||||
$violations = $this->getTypedData()->validate();
|
||||
return new EntityConstraintViolationList($this, iterator_to_array($violations));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isValidationRequired() {
|
||||
return (bool) $this->validationRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setValidationRequired($required) {
|
||||
$this->validationRequired = $required;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear entity translation object cache to remove stale references.
|
||||
*/
|
||||
|
@ -388,15 +464,14 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
*/
|
||||
protected function getTranslatedField($name, $langcode) {
|
||||
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
|
||||
$message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
|
||||
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $this->activeLangcode)));
|
||||
throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated.");
|
||||
}
|
||||
// Populate $this->fields to speed-up further look-ups and to keep track of
|
||||
// fields objects, possibly holding changes to field values.
|
||||
if (!isset($this->fields[$name][$langcode])) {
|
||||
$definition = $this->getFieldDefinition($name);
|
||||
if (!$definition) {
|
||||
throw new \InvalidArgumentException('Field ' . SafeMarkup::checkPlain($name) . ' is unknown.');
|
||||
throw new \InvalidArgumentException("Field $name is unknown.");
|
||||
}
|
||||
// Non-translatable fields are always stored with
|
||||
// LanguageInterface::LANGCODE_DEFAULT as key.
|
||||
|
@ -413,7 +488,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
if (isset($this->values[$name][$langcode])) {
|
||||
$value = $this->values[$name][$langcode];
|
||||
}
|
||||
$field = \Drupal::service('plugin.manager.field.field_type')->createFieldItemList($this, $name, $value);
|
||||
$field = \Drupal::service('plugin.manager.field.field_type')->createFieldItemList($this->getTranslation($langcode), $name, $value);
|
||||
if ($default) {
|
||||
// $this->defaultLangcode might not be set if we are initializing the
|
||||
// default language code cache, in which case there is no valid
|
||||
|
@ -454,6 +529,19 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTranslatableFields($include_computed = TRUE) {
|
||||
$fields = [];
|
||||
foreach ($this->getFieldDefinitions() as $name => $definition) {
|
||||
if (($include_computed || !$definition->isComputed()) && $definition->isTranslatable()) {
|
||||
$fields[$name] = $this->get($name);
|
||||
}
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -537,12 +625,12 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
// Get the language code if the property exists.
|
||||
// Try to read the value directly from the list of entity keys which got
|
||||
// initialized in __construct(). This avoids creating a field item object.
|
||||
if (isset($this->entityKeys['langcode'])) {
|
||||
$this->defaultLangcode = $this->entityKeys['langcode'];
|
||||
if (isset($this->translatableEntityKeys['langcode'][$this->activeLangcode])) {
|
||||
$this->defaultLangcode = $this->translatableEntityKeys['langcode'][$this->activeLangcode];
|
||||
}
|
||||
elseif ($this->hasField($this->langcodeKey) && ($item = $this->get($this->langcodeKey)) && isset($item->language)) {
|
||||
$this->defaultLangcode = $item->language->getId();
|
||||
$this->entityKeys['langcode'] = $this->defaultLangcode;
|
||||
$this->translatableEntityKeys['langcode'][$this->activeLangcode] = $this->defaultLangcode;
|
||||
}
|
||||
|
||||
if (empty($this->defaultLangcode)) {
|
||||
|
@ -583,8 +671,13 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
// that check, as it ready only and must not change, unsetting it could
|
||||
// lead to recursions.
|
||||
if ($key = array_search($name, $this->getEntityType()->getKeys())) {
|
||||
if (isset($this->entityKeys[$key]) && $key != 'bundle') {
|
||||
unset($this->entityKeys[$key]);
|
||||
if ($key != 'bundle') {
|
||||
if (isset($this->entityKeys[$key])) {
|
||||
unset($this->entityKeys[$key]);
|
||||
}
|
||||
elseif (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) {
|
||||
unset($this->translatableEntityKeys[$key][$this->activeLangcode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -663,8 +756,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
}
|
||||
|
||||
if (empty($translation)) {
|
||||
$message = 'Invalid translation language (@langcode) specified.';
|
||||
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $langcode)));
|
||||
throw new \InvalidArgumentException("Invalid translation language ($langcode) specified.");
|
||||
}
|
||||
|
||||
return $translation;
|
||||
|
@ -710,8 +802,6 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
$translation->enforceIsNew = &$this->enforceIsNew;
|
||||
$translation->newRevision = &$this->newRevision;
|
||||
$translation->translationInitialize = FALSE;
|
||||
// Reset language-dependent properties.
|
||||
unset($translation->entityKeys['label']);
|
||||
$translation->typedData = NULL;
|
||||
|
||||
return $translation;
|
||||
|
@ -733,8 +823,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
public function addTranslation($langcode, array $values = array()) {
|
||||
$this->getLanguages();
|
||||
if (!isset($this->languages[$langcode]) || $this->hasTranslation($langcode)) {
|
||||
$message = 'Invalid translation language (@langcode) specified.';
|
||||
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $langcode)));
|
||||
throw new \InvalidArgumentException("Invalid translation language ($langcode) specified.");
|
||||
}
|
||||
|
||||
// Instantiate a new empty entity so default values will be populated in the
|
||||
|
@ -784,8 +873,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
$this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED;
|
||||
}
|
||||
else {
|
||||
$message = 'The specified translation (@langcode) cannot be removed.';
|
||||
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $langcode)));
|
||||
throw new \InvalidArgumentException("The specified translation ($langcode) cannot be removed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -918,8 +1006,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
*/
|
||||
public function createDuplicate() {
|
||||
if ($this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_REMOVED) {
|
||||
$message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
|
||||
throw new \InvalidArgumentException(SafeMarkup::format($message, array('@langcode' => $this->activeLangcode)));
|
||||
throw new \InvalidArgumentException("The entity object refers to a removed translation ({$this->activeLangcode}) and cannot be manipulated.");
|
||||
}
|
||||
|
||||
$duplicate = clone $this;
|
||||
|
@ -962,7 +1049,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
}
|
||||
foreach ($values as $langcode => $items) {
|
||||
$this->fields[$name][$langcode] = clone $items;
|
||||
$this->fields[$name][$langcode]->setContext($name, $this->getTypedData());
|
||||
$this->fields[$name][$langcode]->setContext($name, $this->getTranslation($langcode)->getTypedData());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1020,18 +1107,34 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
* The value of the entity key, NULL if not defined.
|
||||
*/
|
||||
protected function getEntityKey($key) {
|
||||
if (!isset($this->entityKeys[$key]) || !array_key_exists($key, $this->entityKeys)) {
|
||||
if ($this->getEntityType()->hasKey($key)) {
|
||||
$field_name = $this->getEntityType()->getKey($key);
|
||||
$property = $this->getFieldDefinition($field_name)->getFieldStorageDefinition()->getMainPropertyName();
|
||||
$this->entityKeys[$key] = $this->get($field_name)->$property;
|
||||
// If the value is known already, return it.
|
||||
if (isset($this->entityKeys[$key])) {
|
||||
return $this->entityKeys[$key];
|
||||
}
|
||||
if (isset($this->translatableEntityKeys[$key][$this->activeLangcode])) {
|
||||
return $this->translatableEntityKeys[$key][$this->activeLangcode];
|
||||
}
|
||||
|
||||
// Otherwise fetch the value by creating a field object.
|
||||
$value = NULL;
|
||||
if ($this->getEntityType()->hasKey($key)) {
|
||||
$field_name = $this->getEntityType()->getKey($key);
|
||||
$definition = $this->getFieldDefinition($field_name);
|
||||
$property = $definition->getFieldStorageDefinition()->getMainPropertyName();
|
||||
$value = $this->get($field_name)->$property;
|
||||
|
||||
// Put it in the right array, depending on whether it is translatable.
|
||||
if ($definition->isTranslatable()) {
|
||||
$this->translatableEntityKeys[$key][$this->activeLangcode] = $value;
|
||||
}
|
||||
else {
|
||||
$this->entityKeys[$key] = NULL;
|
||||
$this->entityKeys[$key] = $value;
|
||||
}
|
||||
|
||||
}
|
||||
return $this->entityKeys[$key];
|
||||
else {
|
||||
$this->entityKeys[$key] = $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -86,9 +86,6 @@ abstract class ContentEntityConfirmFormBase extends ContentEntityForm implements
|
|||
'submit' => array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->getConfirmText(),
|
||||
'#validate' => array(
|
||||
array($this, 'validate'),
|
||||
),
|
||||
'#submit' => array(
|
||||
array($this, 'submitForm'),
|
||||
),
|
||||
|
@ -121,9 +118,10 @@ abstract class ContentEntityConfirmFormBase extends ContentEntityForm implements
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate(array $form, FormStateInterface $form_state) {
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// Override the default validation implementation as it is not necessary
|
||||
// nor possible to validate an entity in a confirmation form.
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -64,17 +64,28 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
|
|||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Note that extending classes should not override this method to add entity
|
||||
* validation logic, but define further validation constraints using the
|
||||
* entity validation API and/or provide a new validation constraint if
|
||||
* necessary. This is the only way to ensure that the validation logic
|
||||
* is correctly applied independently of form submissions; e.g., for REST
|
||||
* requests.
|
||||
* For more information about entity validation, see
|
||||
* https://www.drupal.org/node/2015613.
|
||||
*/
|
||||
public function validate(array $form, FormStateInterface $form_state) {
|
||||
public function buildEntity(array $form, FormStateInterface $form_state) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = parent::buildEntity($form, $form_state);
|
||||
|
||||
// Mark the entity as requiring validation.
|
||||
$entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated'));
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Button-level validation handlers are highly discouraged for entity forms,
|
||||
* as they will prevent entity validation from running. If the entity is going
|
||||
* to be saved during the form submission, this method should be manually
|
||||
* invoked from the button-level validation handler, otherwise an exception
|
||||
* will be thrown.
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $this->buildEntity($form, $form_state);
|
||||
|
||||
|
@ -87,10 +98,10 @@ class ContentEntityForm extends EntityForm implements ContentEntityFormInterface
|
|||
|
||||
$this->flagViolations($violations, $form, $form_state);
|
||||
|
||||
// @todo Remove this.
|
||||
// Execute legacy global validation handlers.
|
||||
$form_state->setValidateHandlers([]);
|
||||
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
|
||||
// The entity was validated.
|
||||
$entity->setValidationRequired(FALSE);
|
||||
$form_state->setTemporaryValue('entity_validated', TRUE);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ interface ContentEntityFormInterface extends EntityFormInterface {
|
|||
* The form display that the current form operates with.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFormDisplay(EntityFormDisplayInterface $form_display, FormStateInterface $form_state);
|
||||
|
||||
|
@ -56,9 +58,26 @@ interface ContentEntityFormInterface extends EntityFormInterface {
|
|||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return boolean
|
||||
* @return bool
|
||||
* Returns TRUE if the entity form language matches the entity one.
|
||||
*/
|
||||
public function isDefaultFormLangcode(FormStateInterface $form_state);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Note that extending classes should not override this method to add entity
|
||||
* validation logic, but define further validation constraints using the
|
||||
* entity validation API and/or provide a new validation constraint if
|
||||
* necessary. This is the only way to ensure that the validation logic
|
||||
* is correctly applied independently of form submissions; e.g., for REST
|
||||
* requests.
|
||||
* For more information about entity validation, see
|
||||
* https://www.drupal.org/node/2015613.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityTypeInterface
|
||||
* The built entity.
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state);
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryException;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
|
||||
/**
|
||||
|
@ -85,25 +84,25 @@ class ContentEntityNullStorage extends ContentEntityStorageBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doLoadFieldItems($entities, $age) {
|
||||
protected function doLoadRevisionFieldItems($revision_id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSaveFieldItems(EntityInterface $entity, $update) {
|
||||
protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteFieldItems(EntityInterface $entity) {
|
||||
protected function doDeleteFieldItems($entities) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDeleteFieldItemsRevision(EntityInterface $entity) {
|
||||
protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -21,16 +22,35 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
*/
|
||||
protected $bundleKey = FALSE;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cacheBackend;
|
||||
|
||||
/**
|
||||
* Constructs a ContentEntityStorageBase object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend to be used.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type) {
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, CacheBackendInterface $cache) {
|
||||
parent::__construct($entity_type);
|
||||
|
||||
$this->bundleKey = $this->entityType->getKey('bundle');
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->cacheBackend = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +58,9 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type
|
||||
$entity_type,
|
||||
$container->get('entity.manager'),
|
||||
$container->get('cache.entity')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -60,7 +82,7 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
$bundle = FALSE;
|
||||
if ($this->bundleKey) {
|
||||
if (!isset($values[$this->bundleKey])) {
|
||||
throw new EntityStorageException(SafeMarkup::format('Missing bundle for entity type @type', array('@type' => $this->entityTypeId)));
|
||||
throw new EntityStorageException('Missing bundle for entity type ' . $this->entityTypeId);
|
||||
}
|
||||
$bundle = $values[$this->bundleKey];
|
||||
}
|
||||
|
@ -157,6 +179,146 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
*/
|
||||
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) { }
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadRevision($revision_id) {
|
||||
$revision = $this->doLoadRevisionFieldItems($revision_id);
|
||||
|
||||
if ($revision) {
|
||||
$entities = [$revision->id() => $revision];
|
||||
$this->invokeStorageLoadHook($entities);
|
||||
$this->postLoad($entities);
|
||||
}
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually loads revision field item values from the storage.
|
||||
*
|
||||
* @param int|string $revision_id
|
||||
* The revision identifier.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The specified entity revision or NULL if not found.
|
||||
*/
|
||||
abstract protected function doLoadRevisionFieldItems($revision_id);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave($id, EntityInterface $entity) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
|
||||
if ($entity->isNew()) {
|
||||
// Ensure the entity is still seen as new after assigning it an id, while
|
||||
// storing its data.
|
||||
$entity->enforceIsNew();
|
||||
if ($this->entityType->isRevisionable()) {
|
||||
$entity->setNewRevision();
|
||||
}
|
||||
$return = SAVED_NEW;
|
||||
}
|
||||
else {
|
||||
// @todo Consider returning a different value when saving a non-default
|
||||
// entity revision. See https://www.drupal.org/node/2509360.
|
||||
$return = $entity->isDefaultRevision() ? SAVED_UPDATED : FALSE;
|
||||
}
|
||||
|
||||
$this->populateAffectedRevisionTranslations($entity);
|
||||
$this->doSaveFieldItems($entity);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity field values to the storage.
|
||||
*
|
||||
* This method is responsible for allocating entity and revision identifiers
|
||||
* and updating the entity object with their values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity object.
|
||||
* @param string[] $names
|
||||
* (optional) The name of the fields to be written to the storage. If an
|
||||
* empty value is passed all field values are saved.
|
||||
*/
|
||||
abstract protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doPreSave(EntityInterface $entity) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityBase $entity */
|
||||
|
||||
// Sync the changes made in the fields array to the internal values array.
|
||||
$entity->updateOriginalValues();
|
||||
|
||||
return parent::doPreSave($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doPostSave(EntityInterface $entity, $update) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
|
||||
if ($update && $this->entityType->isTranslatable()) {
|
||||
$this->invokeTranslationHooks($entity);
|
||||
}
|
||||
|
||||
parent::doPostSave($entity, $update);
|
||||
|
||||
// The revision is stored, it should no longer be marked as new now.
|
||||
if ($this->entityType->isRevisionable()) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete($entities) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
|
||||
foreach ($entities as $entity) {
|
||||
$this->invokeFieldMethod('delete', $entity);
|
||||
}
|
||||
$this->doDeleteFieldItems($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entity field values from the storage.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* An array of entity objects to be deleted.
|
||||
*/
|
||||
abstract protected function doDeleteFieldItems($entities);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteRevision($revision_id) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
|
||||
if ($revision = $this->loadRevision($revision_id)) {
|
||||
// Prevent deletion if this is the default revision.
|
||||
if ($revision->isDefaultRevision()) {
|
||||
throw new EntityStorageException('Default revision can not be deleted');
|
||||
}
|
||||
$this->invokeFieldMethod('deleteRevision', $revision);
|
||||
$this->doDeleteRevisionFieldItems($revision);
|
||||
$this->invokeHook('revision_delete', $revision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes field values of an entity revision from the storage.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $revision
|
||||
* An entity revision object to be deleted.
|
||||
*/
|
||||
abstract protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision);
|
||||
|
||||
/**
|
||||
* Checks translation statuses and invoke the related hooks if needed.
|
||||
*
|
||||
|
@ -179,31 +341,101 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes hook_entity_storage_load().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* List of entities, keyed on the entity ID.
|
||||
*/
|
||||
protected function invokeStorageLoadHook(array &$entities) {
|
||||
if (!empty($entities)) {
|
||||
// Call hook_entity_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations('entity_storage_load') as $module) {
|
||||
$function = $module . '_entity_storage_load';
|
||||
$function($entities, $this->entityTypeId);
|
||||
}
|
||||
// Call hook_TYPE_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_storage_load') as $module) {
|
||||
$function = $module . '_' . $this->entityTypeId . '_storage_load';
|
||||
$function($entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function invokeHook($hook, EntityInterface $entity) {
|
||||
if ($hook == 'presave') {
|
||||
$this->invokeFieldMethod('preSave', $entity);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
|
||||
switch ($hook) {
|
||||
case 'presave':
|
||||
$this->invokeFieldMethod('preSave', $entity);
|
||||
break;
|
||||
|
||||
case 'insert':
|
||||
$this->invokeFieldPostSave($entity, FALSE);
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
$this->invokeFieldPostSave($entity, TRUE);
|
||||
break;
|
||||
}
|
||||
|
||||
parent::invokeHook($hook, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a method on the Field objects within an entity.
|
||||
*
|
||||
* Any argument passed will be forwarded to the invoked method.
|
||||
*
|
||||
* @param string $method
|
||||
* The method name.
|
||||
* The name of the method to be invoked.
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @return array
|
||||
* A multidimensional associative array of results, keyed by entity
|
||||
* translation language code and field name.
|
||||
*/
|
||||
protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
|
||||
$result = [];
|
||||
$args = array_slice(func_get_args(), 2);
|
||||
foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
foreach ($translation->getFields() as $field) {
|
||||
$field->$method();
|
||||
// For non translatable fields, there is only one field object instance
|
||||
// across all translations and it has as parent entity the entity in the
|
||||
// default entity translation. Therefore field methods on non translatable
|
||||
// fields should be invoked only on the default entity translation.
|
||||
$fields = $translation->isDefaultTranslation() ? $translation->getFields() : $translation->getTranslatableFields();
|
||||
foreach ($fields as $name => $items) {
|
||||
// call_user_func_array() is way slower than a direct call so we avoid
|
||||
// using it if have no parameters.
|
||||
$result[$langcode][$name] = $args ? call_user_func_array([$items, $method], $args) : $items->{$method}();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the post save method on the Field objects within an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The entity object.
|
||||
* @param bool $update
|
||||
* Specifies whether the entity is being updated or created.
|
||||
*/
|
||||
protected function invokeFieldPostSave(ContentEntityInterface $entity, $update) {
|
||||
// For each entity translation this returns an array of resave flags keyed
|
||||
// by field name, thus we merge them to obtain a list of fields to resave.
|
||||
$resave = [];
|
||||
foreach ($this->invokeFieldMethod('postSave', $entity, $update) as $translation_results) {
|
||||
$resave += array_filter($translation_results);
|
||||
}
|
||||
if ($resave) {
|
||||
$this->doSaveFieldItems($entity, array_keys($resave));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,4 +490,118 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures integer entity IDs are valid.
|
||||
*
|
||||
* The identifier sanitization provided by this method has been introduced
|
||||
* as Drupal used to rely on the database to facilitate this, which worked
|
||||
* correctly with MySQL but led to errors with other DBMS such as PostgreSQL.
|
||||
*
|
||||
* @param array $ids
|
||||
* The entity IDs to verify.
|
||||
*
|
||||
* @return array
|
||||
* The sanitized list of entity IDs.
|
||||
*/
|
||||
protected function cleanIds(array $ids) {
|
||||
$definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId);
|
||||
$id_definition = $definitions[$this->entityType->getKey('id')];
|
||||
if ($id_definition->getType() == 'integer') {
|
||||
$ids = array_filter($ids, function ($id) {
|
||||
return is_numeric($id) && $id == (int) $id;
|
||||
});
|
||||
$ids = array_map('intval', $ids);
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the persistent cache backend.
|
||||
*
|
||||
* @param array|null &$ids
|
||||
* If not empty, return entities that match these IDs. IDs that were found
|
||||
* will be removed from the list.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface[]
|
||||
* Array of entities from the persistent cache.
|
||||
*/
|
||||
protected function getFromPersistentCache(array &$ids = NULL) {
|
||||
if (!$this->entityType->isPersistentlyCacheable() || empty($ids)) {
|
||||
return array();
|
||||
}
|
||||
$entities = array();
|
||||
// Build the list of cache entries to retrieve.
|
||||
$cid_map = array();
|
||||
foreach ($ids as $id) {
|
||||
$cid_map[$id] = $this->buildCacheId($id);
|
||||
}
|
||||
$cids = array_values($cid_map);
|
||||
if ($cache = $this->cacheBackend->getMultiple($cids)) {
|
||||
// Get the entities that were found in the cache.
|
||||
foreach ($ids as $index => $id) {
|
||||
$cid = $cid_map[$id];
|
||||
if (isset($cache[$cid])) {
|
||||
$entities[$id] = $cache[$cid]->data;
|
||||
unset($ids[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the persistent cache backend.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* Entities to store in the cache.
|
||||
*/
|
||||
protected function setPersistentCache($entities) {
|
||||
if (!$this->entityType->isPersistentlyCacheable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_tags = array(
|
||||
$this->entityTypeId . '_values',
|
||||
'entity_field_info',
|
||||
);
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
if ($ids) {
|
||||
$cids = array();
|
||||
foreach ($ids as $id) {
|
||||
unset($this->entities[$id]);
|
||||
$cids[] = $this->buildCacheId($id);
|
||||
}
|
||||
if ($this->entityType->isPersistentlyCacheable()) {
|
||||
$this->cacheBackend->deleteMultiple($cids);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entities = array();
|
||||
if ($this->entityType->isPersistentlyCacheable()) {
|
||||
Cache::invalidateTags(array($this->entityTypeId . '_values'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the cache ID for the passed in entity ID.
|
||||
*
|
||||
* @param int $id
|
||||
* Entity ID for which the cache ID should be built.
|
||||
*
|
||||
* @return string
|
||||
* Cache ID that can be passed to the cache backend.
|
||||
*/
|
||||
protected function buildCacheId($id) {
|
||||
return "values:{$this->entityTypeId}:$id";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ class EntityViewController implements ContainerInjectionInterface {
|
|||
|
||||
$page['#pre_render'][] = [$this, 'buildTitle'];
|
||||
$page['#entity_type'] = $_entity->getEntityTypeId();
|
||||
$page['#' . $page['#entity_type']] = $_entity;
|
||||
|
||||
|
||||
return $page;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
@ -24,6 +25,8 @@ use Drupal\Core\Url;
|
|||
*/
|
||||
abstract class Entity implements EntityInterface {
|
||||
|
||||
use RefinableCacheableDependencyTrait;
|
||||
|
||||
use DependencySerializationTrait {
|
||||
__sleep as traitSleep;
|
||||
}
|
||||
|
@ -192,10 +195,7 @@ abstract class Entity implements EntityInterface {
|
|||
$uri = call_user_func($uri_callback, $this);
|
||||
}
|
||||
else {
|
||||
throw new UndefinedLinkTemplateException(SafeMarkup::format('No link template "@rel" found for the "@entity_type" entity type', array(
|
||||
'@rel' => $rel,
|
||||
'@entity_type' => $this->getEntityTypeId(),
|
||||
)));
|
||||
throw new UndefinedLinkTemplateException("No link template '$rel' found for the '{$this->getEntityTypeId()}' entity type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,12 +381,7 @@ abstract class Entity implements EntityInterface {
|
|||
if ($this->getEntityType()->getBundleOf()) {
|
||||
// Throw an exception if the bundle ID is longer than 32 characters.
|
||||
if (Unicode::strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) {
|
||||
throw new ConfigEntityIdLengthException(SafeMarkup::format(
|
||||
'Attempt to create a bundle with an ID longer than @max characters: @id.', array(
|
||||
'@max' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
|
||||
'@id' => $this->id(),
|
||||
)
|
||||
));
|
||||
throw new ConfigEntityIdLengthException("Attempt to create a bundle with an ID longer than " . EntityTypeInterface::BUNDLE_MAX_LENGTH . " characters: $this->id().");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -440,23 +435,36 @@ abstract class Entity implements EntityInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return [];
|
||||
return $this->cacheContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
public function getCacheTagsToInvalidate() {
|
||||
// @todo Add bundle-specific listing cache tag?
|
||||
// https://www.drupal.org/node/2145751
|
||||
if ($this->isNew()) {
|
||||
return [];
|
||||
}
|
||||
return [$this->entityTypeId . ':' . $this->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
if ($this->cacheTags) {
|
||||
return Cache::mergeTags($this->getCacheTagsToInvalidate(), $this->cacheTags);
|
||||
}
|
||||
return $this->getCacheTagsToInvalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
return $this->cacheMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -511,7 +519,7 @@ abstract class Entity implements EntityInterface {
|
|||
}
|
||||
if ($update) {
|
||||
// An existing entity was updated, also invalidate its unique cache tag.
|
||||
$tags = Cache::mergeTags($tags, $this->getCacheTags());
|
||||
$tags = Cache::mergeTags($tags, $this->getCacheTagsToInvalidate());
|
||||
}
|
||||
Cache::invalidateTags($tags);
|
||||
}
|
||||
|
@ -532,7 +540,7 @@ abstract class Entity implements EntityInterface {
|
|||
// other pages than the one it's on. The one it's on is handled by its own
|
||||
// cache tag, but subsequent list pages would not be invalidated, hence we
|
||||
// must invalidate its list cache tags as well.)
|
||||
$tags = Cache::mergeTags($tags, $entity->getCacheTags());
|
||||
$tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
|
||||
}
|
||||
Cache::invalidateTags($tags);
|
||||
}
|
||||
|
|
|
@ -81,9 +81,6 @@ abstract class EntityConfirmFormBase extends EntityForm implements ConfirmFormIn
|
|||
'submit' => array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->getConfirmText(),
|
||||
'#validate' => array(
|
||||
array($this, 'validate'),
|
||||
),
|
||||
'#submit' => array(
|
||||
array($this, 'submitForm'),
|
||||
),
|
||||
|
|
|
@ -97,7 +97,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
|
|||
public function applyUpdates() {
|
||||
$change_list = $this->getChangeList();
|
||||
if ($change_list) {
|
||||
// getChangeList() only disables the cache and does not invalidate.
|
||||
// self::getChangeList() only disables the cache and does not invalidate.
|
||||
// In case there are changes, explicitly invalidate caches.
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
}
|
||||
|
@ -107,18 +107,7 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
|
|||
// to revisionable and at the same time add revisionable fields to the
|
||||
// entity type.
|
||||
if (!empty($change_list['entity_type'])) {
|
||||
$entity_type = $this->entityManager->getDefinition($entity_type_id);
|
||||
|
||||
switch ($change_list['entity_type']) {
|
||||
case static::DEFINITION_CREATED:
|
||||
$this->entityManager->onEntityTypeCreate($entity_type);
|
||||
break;
|
||||
|
||||
case static::DEFINITION_UPDATED:
|
||||
$original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
|
||||
$this->entityManager->onEntityTypeUpdate($entity_type, $original);
|
||||
break;
|
||||
}
|
||||
$this->doEntityUpdate($change_list['entity_type'], $entity_type_id);
|
||||
}
|
||||
|
||||
// Process field storage definition changes.
|
||||
|
@ -127,24 +116,105 @@ class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInte
|
|||
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
|
||||
|
||||
foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
|
||||
switch ($change) {
|
||||
case static::DEFINITION_CREATED:
|
||||
$this->entityManager->onFieldStorageDefinitionCreate($storage_definitions[$field_name]);
|
||||
break;
|
||||
|
||||
case static::DEFINITION_UPDATED:
|
||||
$this->entityManager->onFieldStorageDefinitionUpdate($storage_definitions[$field_name], $original_storage_definitions[$field_name]);
|
||||
break;
|
||||
|
||||
case static::DEFINITION_DELETED:
|
||||
$this->entityManager->onFieldStorageDefinitionDelete($original_storage_definitions[$field_name]);
|
||||
break;
|
||||
}
|
||||
$storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
|
||||
$original_storage_definition = isset($original_storage_definitions[$field_name]) ? $original_storage_definitions[$field_name] : NULL;
|
||||
$this->doFieldUpdate($change, $storage_definition, $original_storage_definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyEntityUpdate($op, $entity_type_id, $reset_cached_definitions = TRUE) {
|
||||
$change_list = $this->getChangeList();
|
||||
if (!isset($change_list[$entity_type_id]) || $change_list[$entity_type_id]['entity_type'] !== $op) {
|
||||
return FALSE;
|
||||
}
|
||||
if ($reset_cached_definitions) {
|
||||
// self::getChangeList() only disables the cache and does not invalidate.
|
||||
// In case there are changes, explicitly invalidate caches.
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
}
|
||||
$this->doEntityUpdate($op, $entity_type_id);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyFieldUpdate($op, $entity_type_id, $field_name, $reset_cached_definitions = TRUE) {
|
||||
$change_list = $this->getChangeList();
|
||||
if (!isset($change_list[$entity_type_id]['field_storage_definitions']) || $change_list[$entity_type_id]['field_storage_definitions'][$field_name] !== $op) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ($reset_cached_definitions) {
|
||||
// self::getChangeList() only disables the cache and does not invalidate.
|
||||
// In case there are changes, explicitly invalidate caches.
|
||||
$this->entityManager->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
|
||||
$original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
|
||||
$storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
|
||||
$original_storage_definition = isset($original_storage_definitions[$field_name]) ? $original_storage_definitions[$field_name] : NULL;
|
||||
|
||||
$this->doFieldUpdate($op, $storage_definition, $original_storage_definition);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an entity type definition update.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation to perform, either static::DEFINITION_CREATED or
|
||||
* static::DEFINITION_UPDATED.
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
*/
|
||||
protected function doEntityUpdate($op, $entity_type_id) {
|
||||
$entity_type = $this->entityManager->getDefinition($entity_type_id);
|
||||
switch ($op) {
|
||||
case static::DEFINITION_CREATED:
|
||||
$this->entityManager->onEntityTypeCreate($entity_type);
|
||||
break;
|
||||
|
||||
case static::DEFINITION_UPDATED:
|
||||
$original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
|
||||
$this->entityManager->onEntityTypeUpdate($entity_type, $original);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a field storage definition update.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation to perform, possible values are static::DEFINITION_CREATED,
|
||||
* static::DEFINITION_UPDATED or static::DEFINITION_DELETED.
|
||||
* @param array|null $storage_definition
|
||||
* The new field storage definition.
|
||||
* @param array|null $original_storage_definition
|
||||
* The original field storage definition.
|
||||
*/
|
||||
protected function doFieldUpdate($op, $storage_definition = NULL, $original_storage_definition = NULL) {
|
||||
switch ($op) {
|
||||
case static::DEFINITION_CREATED:
|
||||
$this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
|
||||
break;
|
||||
|
||||
case static::DEFINITION_UPDATED:
|
||||
$this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original_storage_definition);
|
||||
break;
|
||||
|
||||
case static::DEFINITION_DELETED:
|
||||
$this->entityManager->onFieldStorageDefinitionDelete($original_storage_definition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of changes to entity type and field storage definitions.
|
||||
*
|
||||
|
|
|
@ -83,4 +83,68 @@ interface EntityDefinitionUpdateManagerInterface {
|
|||
*/
|
||||
public function applyUpdates();
|
||||
|
||||
/**
|
||||
* Performs a single entity definition update.
|
||||
*
|
||||
* This method should be used from hook_update_N() functions to process
|
||||
* entity definition updates as part of the update function. This is only
|
||||
* necessary if the hook_update_N() implementation relies on the entity
|
||||
* definition update. All remaining entity definition updates will be run
|
||||
* automatically after the hook_update_N() implementations.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation to perform, either static::DEFINITION_CREATED or
|
||||
* static::DEFINITION_UPDATED.
|
||||
* @param string $entity_type_id
|
||||
* The entity type to update.
|
||||
* @param bool $reset_cached_definitions
|
||||
* (optional). Determines whether to clear the Entity Manager's cached
|
||||
* definitions before applying the update. Defaults to TRUE. Can be used
|
||||
* to prevent unnecessary cache invalidation when a hook_update_N() makes
|
||||
* multiple calls to this method.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity update is processed, FALSE if not.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* This exception is thrown if a change cannot be applied without
|
||||
* unacceptable data loss. In such a case, the site administrator needs to
|
||||
* apply some other process, such as a custom update function or a
|
||||
* migration via the Migrate module.
|
||||
*/
|
||||
public function applyEntityUpdate($op, $entity_type_id, $reset_cached_definitions = TRUE);
|
||||
|
||||
/**
|
||||
* Performs a single field storage definition update.
|
||||
*
|
||||
* This method should be used from hook_update_N() functions to process field
|
||||
* storage definition updates as part of the update function. This is only
|
||||
* necessary if the hook_update_N() implementation relies on the field storage
|
||||
* definition update. All remaining field storage definition updates will be
|
||||
* run automatically after the hook_update_N() implementations.
|
||||
*
|
||||
* @param string $op
|
||||
* The operation to perform, possible values are static::DEFINITION_CREATED,
|
||||
* static::DEFINITION_UPDATED or static::DEFINITION_DELETED.
|
||||
* @param string $entity_type_id
|
||||
* The entity type to update.
|
||||
* @param string $field_name
|
||||
* The field name to update.
|
||||
* @param bool $reset_cached_definitions
|
||||
* (optional). Determines whether to clear the Entity Manager's cached
|
||||
* definitions before applying the update. Defaults to TRUE. Can be used
|
||||
* to prevent unnecessary cache invalidation when a hook_update_N() makes
|
||||
* multiple calls to this method.
|
||||
|
||||
* @return bool
|
||||
* TRUE if the entity update is processed, FALSE if not.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* This exception is thrown if a change cannot be applied without
|
||||
* unacceptable data loss. In such a case, the site administrator needs to
|
||||
* apply some other process, such as a custom update function or a
|
||||
* migration via the Migrate module.
|
||||
*/
|
||||
public function applyFieldUpdate($op, $entity_type_id, $field_name, $reset_cached_definitions = TRUE);
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
|
|||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Entity\Display\EntityDisplayInterface;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Provides a common base class for entity view and form displays.
|
||||
|
@ -262,7 +261,7 @@ abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDispl
|
|||
// If the target entity type uses entities to manage its bundles then
|
||||
// depend on the bundle entity.
|
||||
if (!$bundle_entity = $this->entityManager()->getStorage($bundle_entity_type_id)->load($this->bundle)) {
|
||||
throw new \LogicException(SafeMarkup::format('Missing bundle entity, entity type %type, entity id %bundle.', array('%type' => $bundle_entity_type_id, '%bundle' => $this->bundle)));
|
||||
throw new \LogicException("Missing bundle entity, entity type $bundle_entity_type_id, entity id {$this->bundle}.");
|
||||
}
|
||||
$this->addDependency('config', $bundle_entity->getConfigDependencyName());
|
||||
}
|
||||
|
|
|
@ -101,6 +101,12 @@ class EntityForm extends FormBase implements EntityFormInterface {
|
|||
$this->init($form_state);
|
||||
}
|
||||
|
||||
// Ensure that edit forms have the correct cacheability metadata so they can
|
||||
// be cached.
|
||||
if (!$this->entity->isNew()) {
|
||||
\Drupal::service('renderer')->addCacheableDependency($form, $this->entity);
|
||||
}
|
||||
|
||||
// Retrieve the form array using the possibly updated entity in form state.
|
||||
$form = $this->form($form, $form_state);
|
||||
|
||||
|
@ -219,7 +225,6 @@ class EntityForm extends FormBase implements EntityFormInterface {
|
|||
$actions['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#validate' => array('::validate'),
|
||||
'#submit' => array('::submitForm', '::save'),
|
||||
);
|
||||
|
||||
|
@ -244,16 +249,6 @@ class EntityForm extends FormBase implements EntityFormInterface {
|
|||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate(array $form, FormStateInterface $form_state) {
|
||||
// @todo Remove this.
|
||||
// Execute legacy global validation handlers.
|
||||
$form_state->setValidateHandlers([]);
|
||||
\Drupal::service('form_validator')->executeValidateHandlers($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
|
|
|
@ -9,12 +9,20 @@ namespace Drupal\Core\Entity;
|
|||
|
||||
/**
|
||||
* Builds entity forms.
|
||||
*
|
||||
* This is like \Drupal\Core\Form\FormBuilderInterface but instead of looking
|
||||
* up the form class by class name, it looks up the form class based on the
|
||||
* entity type and operation.
|
||||
*/
|
||||
interface EntityFormBuilderInterface {
|
||||
|
||||
/**
|
||||
* Gets the built and processed entity form for the given entity.
|
||||
*
|
||||
* The form may also be retrieved from the cache if the form was built in a
|
||||
* previous page load. The form is then passed on for processing, validation,
|
||||
* and submission if there is proper input.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be created or edited.
|
||||
* @param string $operation
|
||||
|
@ -23,7 +31,15 @@ interface EntityFormBuilderInterface {
|
|||
* @code
|
||||
* _entity_form: node.book_outline
|
||||
* @endcode
|
||||
* where "book_outline" is the value of $operation.
|
||||
* where "book_outline" is the value of $operation. The class name for the
|
||||
* form for each operation (edit, delete, etc.) can be found in the form
|
||||
* section of the handlers entity annotation. For example:
|
||||
* @code
|
||||
* handlers = {
|
||||
* "form" = {
|
||||
* "delete" = "Drupal\node\Form\NodeDeleteForm",
|
||||
* @endcode
|
||||
* Alternatively, the form class can be set from hook_entity_type_build().
|
||||
* @param array $form_state_additions
|
||||
* (optional) An associative array used to build the current state of the
|
||||
* form. Use this to pass additional information to the form, such as the
|
||||
|
@ -36,6 +52,11 @@ interface EntityFormBuilderInterface {
|
|||
*
|
||||
* @return array
|
||||
* The processed form for the given entity and operation.
|
||||
*
|
||||
* @see \Drupal\Core\Form\FormBuilderInterface::getForm()
|
||||
* @see \Drupal\Core\Entity\EntityTypeInterface::getFormClass()
|
||||
* @see \Drupal\Core\Entity\EntityTypeInterface::setFormClass()
|
||||
* @see system_entity_type_build()
|
||||
*/
|
||||
public function getForm(EntityInterface $entity, $operation = 'default', array $form_state_additions = array());
|
||||
|
||||
|
|
|
@ -94,19 +94,6 @@ interface EntityFormInterface extends BaseFormIdInterface {
|
|||
*/
|
||||
public function buildEntity(array $form, FormStateInterface $form_state);
|
||||
|
||||
/**
|
||||
* Validates the submitted form values of the entity form.
|
||||
*
|
||||
* @param array $form
|
||||
* A nested array form elements comprising the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityTypeInterface
|
||||
* The built entity.
|
||||
*/
|
||||
public function validate(array $form, FormStateInterface $form_state);
|
||||
|
||||
/**
|
||||
* Form submission handler for the 'save' action.
|
||||
*
|
||||
|
|
|
@ -9,13 +9,14 @@ namespace Drupal\Core\Entity;
|
|||
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for all entity objects.
|
||||
*
|
||||
* @ingroup entity_api
|
||||
*/
|
||||
interface EntityInterface extends AccessibleInterface, CacheableDependencyInterface {
|
||||
interface EntityInterface extends AccessibleInterface, CacheableDependencyInterface, RefinableCacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* Gets the entity UUID (Universally Unique Identifier).
|
||||
|
@ -348,6 +349,19 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
|
|||
*/
|
||||
public function getOriginalId();
|
||||
|
||||
/**
|
||||
* Returns the cache tags that should be used to invalidate caches.
|
||||
*
|
||||
* This will not return additional cache tags added through addCacheTags().
|
||||
*
|
||||
* @return string[]
|
||||
* Set of cache tags.
|
||||
*
|
||||
* @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface::addCacheTags()
|
||||
* @see \Drupal\Core\Cache\CacheableDependencyInterface::getCacheTags()
|
||||
*/
|
||||
public function getCacheTagsToInvalidate();
|
||||
|
||||
/**
|
||||
* Sets the original ID.
|
||||
*
|
||||
|
|
|
@ -41,8 +41,7 @@ interface EntityListBuilderInterface {
|
|||
* An associative array of operation link data for this list, keyed by
|
||||
* operation name, containing the following key-value pairs:
|
||||
* - title: The localized title of the operation.
|
||||
* - href: The path for the operation.
|
||||
* - options: An array of URL options for the path.
|
||||
* - url: An instance of \Drupal\Core\Url for the operation URL.
|
||||
* - weight: The weight of this operation.
|
||||
*/
|
||||
public function getOperations(EntityInterface $entity);
|
||||
|
|
|
@ -9,12 +9,12 @@ namespace Drupal\Core\Entity;
|
|||
|
||||
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityType;
|
||||
use Drupal\Core\DependencyInjection\ClassResolverInterface;
|
||||
use Drupal\Core\Entity\Exception\AmbiguousEntityClassException;
|
||||
use Drupal\Core\Entity\Exception\InvalidLinkTemplateException;
|
||||
use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
@ -226,6 +226,21 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
$this->handlers = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processDefinition(&$definition, $plugin_id) {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeInterface $definition */
|
||||
parent::processDefinition($definition, $plugin_id);
|
||||
|
||||
// All link templates must have a leading slash.
|
||||
foreach ((array) $definition->getLinkTemplates() as $link_relation_name => $link_template) {
|
||||
if ($link_template[0] != '/') {
|
||||
throw new InvalidLinkTemplateException("Link template '$link_relation_name' for entity type '$plugin_id' must start with a leading slash, the current link template is '$link_template'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -409,7 +424,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
|
||||
// Fail with an exception for non-fieldable entity types.
|
||||
if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
|
||||
throw new \LogicException(SafeMarkup::format('Getting the base fields is not supported for entity type @type.', array('@type' => $entity_type->getLabel())));
|
||||
throw new \LogicException("Getting the base fields is not supported for entity type {$entity_type->getLabel()}.");
|
||||
}
|
||||
|
||||
// Retrieve base field definitions.
|
||||
|
@ -477,28 +492,19 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
// translatable values.
|
||||
foreach (array_intersect_key($keys, array_flip(['id', 'revision', 'uuid', 'bundle'])) as $key => $field_name) {
|
||||
if (!isset($base_field_definitions[$field_name])) {
|
||||
throw new \LogicException(SafeMarkup::format('The @field field definition does not exist and it is used as @key entity key.', array(
|
||||
'@field' => $field_name,
|
||||
'@key' => $key,
|
||||
)));
|
||||
throw new \LogicException("The $field_name field definition does not exist and it is used as $key entity key.");
|
||||
}
|
||||
if ($base_field_definitions[$field_name]->isRevisionable()) {
|
||||
throw new \LogicException(SafeMarkup::format('The @field field cannot be revisionable as it is used as @key entity key.', array(
|
||||
'@field' => $base_field_definitions[$field_name]->getLabel(),
|
||||
'@key' => $key,
|
||||
)));
|
||||
throw new \LogicException("The {$base_field_definitions[$field_name]->getLabel()} field cannot be revisionable as it is used as $key entity key.");
|
||||
}
|
||||
if ($base_field_definitions[$field_name]->isTranslatable()) {
|
||||
throw new \LogicException(SafeMarkup::format('The @field field cannot be translatable as it is used as @key entity key.', array(
|
||||
'@field' => $base_field_definitions[$field_name]->getLabel(),
|
||||
'@key' => $key,
|
||||
)));
|
||||
throw new \LogicException("The {$base_field_definitions[$field_name]->getLabel()} field cannot be translatable as it is used as $key entity key.");
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure translatable entity types define the "langcode" field properly.
|
||||
if ($entity_type->isTranslatable() && (!isset($keys['langcode']) || !isset($base_field_definitions[$keys['langcode']]) || !$base_field_definitions[$keys['langcode']]->isTranslatable())) {
|
||||
throw new \LogicException(SafeMarkup::format('The @entity_type entity type cannot be translatable as it does not define a translatable "langcode" field.', array('@entity_type' => $entity_type->getLabel())));
|
||||
throw new \LogicException("The {$entity_type->getLabel()} entity type cannot be translatable as it does not define a translatable \"langcode\" field.");
|
||||
}
|
||||
|
||||
return $base_field_definitions;
|
||||
|
@ -969,6 +975,7 @@ class EntityManager extends DefaultPluginManager implements EntityManagerInterfa
|
|||
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
|
||||
if (empty($langcode)) {
|
||||
$langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
|
||||
$entity->addCacheContexts(['languages:' . LanguageInterface::TYPE_CONTENT]);
|
||||
}
|
||||
|
||||
// Retrieve language fallback candidates to perform the entity language
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
|
||||
/**
|
||||
|
@ -382,6 +381,34 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(EntityInterface $entity) {
|
||||
// Track if this entity is new.
|
||||
$is_new = $entity->isNew();
|
||||
|
||||
// Execute presave logic and invoke the related hooks.
|
||||
$id = $this->doPreSave($entity);
|
||||
|
||||
// Perform the save and reset the static cache for the changed entity.
|
||||
$return = $this->doSave($id, $entity);
|
||||
|
||||
// Execute post save logic and invoke the related hooks.
|
||||
$this->doPostSave($entity, !$is_new);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs presave entity processing.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The saved entity.
|
||||
*
|
||||
* @return int|string
|
||||
* The processed entity identifier.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
* If the entity identifier is invalid.
|
||||
*/
|
||||
protected function doPreSave(EntityInterface $entity) {
|
||||
$id = $entity->id();
|
||||
|
||||
// Track the original ID.
|
||||
|
@ -389,14 +416,12 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
$id = $entity->getOriginalId();
|
||||
}
|
||||
|
||||
// Track if this entity is new.
|
||||
$is_new = $entity->isNew();
|
||||
// Track if this entity exists already.
|
||||
$id_exists = $this->has($id, $entity);
|
||||
|
||||
// A new entity should not already exist.
|
||||
if ($id_exists && $is_new) {
|
||||
throw new EntityStorageException(SafeMarkup::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id)));
|
||||
if ($id_exists && $entity->isNew()) {
|
||||
throw new EntityStorageException("'{$this->entityTypeId}' entity with ID '$id' already exists.");
|
||||
}
|
||||
|
||||
// Load the original entity, if any.
|
||||
|
@ -408,25 +433,7 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
$entity->preSave($this);
|
||||
$this->invokeHook('presave', $entity);
|
||||
|
||||
// Perform the save and reset the static cache for the changed entity.
|
||||
$return = $this->doSave($id, $entity);
|
||||
$this->resetCache(array($id));
|
||||
|
||||
// The entity is no longer new.
|
||||
$entity->enforceIsNew(FALSE);
|
||||
|
||||
// Allow code to run after saving.
|
||||
$entity->postSave($this, !$is_new);
|
||||
$this->invokeHook($is_new ? 'insert' : 'update', $entity);
|
||||
|
||||
// After saving, this is now the "original entity", and subsequent saves
|
||||
// will be updates instead of inserts, and updates must always be able to
|
||||
// correctly identify the original entity.
|
||||
$entity->setOriginalId($entity->id());
|
||||
|
||||
unset($entity->original);
|
||||
|
||||
return $return;
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,6 +450,32 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor
|
|||
*/
|
||||
abstract protected function doSave($id, EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Performs post save entity processing.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The saved entity.
|
||||
* @param bool $update
|
||||
* Specifies whether the entity is being updated or created.
|
||||
*/
|
||||
protected function doPostSave(EntityInterface $entity, $update) {
|
||||
$this->resetCache(array($entity->id()));
|
||||
|
||||
// The entity is no longer new.
|
||||
$entity->enforceIsNew(FALSE);
|
||||
|
||||
// Allow code to run after saving.
|
||||
$entity->postSave($this, $update);
|
||||
$this->invokeHook($update ? 'update' : 'insert', $entity);
|
||||
|
||||
// After saving, this is now the "original entity", and subsequent saves
|
||||
// will be updates instead of inserts, and updates must always be able to
|
||||
// correctly identify the original entity.
|
||||
$entity->setOriginalId($entity->id());
|
||||
|
||||
unset($entity->original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an entity query.
|
||||
*
|
||||
|
|
|
@ -79,7 +79,7 @@ interface EntityStorageInterface {
|
|||
/**
|
||||
* Load a specific entity revision.
|
||||
*
|
||||
* @param int $revision_id
|
||||
* @param int|string $revision_id
|
||||
* The revision id.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\Exception\EntityTypeIdLengthException;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
@ -245,12 +244,7 @@ class EntityType implements EntityTypeInterface {
|
|||
public function __construct($definition) {
|
||||
// Throw an exception if the entity type ID is longer than 32 characters.
|
||||
if (Unicode::strlen($definition['id']) > static::ID_MAX_LENGTH) {
|
||||
throw new EntityTypeIdLengthException(SafeMarkup::format(
|
||||
'Attempt to create an entity type with an ID longer than @max characters: @id.', array(
|
||||
'@max' => static::ID_MAX_LENGTH,
|
||||
'@id' => $definition['id'],
|
||||
)
|
||||
));
|
||||
throw new EntityTypeIdLengthException('Attempt to create an entity type with an ID longer than ' . static::ID_MAX_LENGTH . " characters: {$definition['id']}.");
|
||||
}
|
||||
|
||||
foreach ($definition as $property => $value) {
|
||||
|
|
|
@ -117,14 +117,10 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
|
||||
if (!isset($langcode)) {
|
||||
$langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
|
||||
}
|
||||
|
||||
$build_list = array(
|
||||
'#sorted' => TRUE,
|
||||
'#pre_render' => array(array($this, 'buildMultiple')),
|
||||
'#langcode' => $langcode,
|
||||
'#langcode' => $langcode ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(),
|
||||
);
|
||||
$weight = 0;
|
||||
foreach ($entities as $key => $entity) {
|
||||
|
@ -133,9 +129,10 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
|
|||
$entity = $this->entityManager->getTranslationFromContext($entity, $langcode);
|
||||
|
||||
// Set build defaults.
|
||||
$build_list[$key] = $this->getBuildDefaults($entity, $view_mode, $langcode);
|
||||
$entity_langcode = $entity->language()->getId();
|
||||
$build_list[$key] = $this->getBuildDefaults($entity, $view_mode, $entity_langcode);
|
||||
$entityType = $this->entityTypeId;
|
||||
$this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode, $langcode);
|
||||
$this->moduleHandler()->alter(array($entityType . '_build_defaults', 'entity_build_defaults'), $build_list[$key], $entity, $view_mode, $entity_langcode);
|
||||
|
||||
$build_list[$key]['#weight'] = $weight++;
|
||||
}
|
||||
|
@ -368,7 +365,8 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
|
|||
if (isset($entities)) {
|
||||
$tags = [];
|
||||
foreach ($entities as $entity) {
|
||||
$tags = Cache::mergeTags($tags, $entity->getCacheTags(), $entity->getEntityType()->getListCacheTags());
|
||||
$tags = Cache::mergeTags($tags, $entity->getCacheTags());
|
||||
$tags = Cache::mergeTags($tags, $entity->getEntityType()->getListCacheTags());
|
||||
}
|
||||
Cache::invalidateTags($tags);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\Exception\InvalidLinkTemplateException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Exception;
|
||||
|
||||
/**
|
||||
* Indicates that a link template does not follow the required pattern.
|
||||
*/
|
||||
class InvalidLinkTemplateException extends \Exception {
|
||||
|
||||
}
|
|
@ -175,7 +175,7 @@ interface FieldableEntityInterface extends EntityInterface {
|
|||
public function set($field_name, $value, $notify = TRUE);
|
||||
|
||||
/**
|
||||
* Gets an array of field item lists.
|
||||
* Gets an array of all field item lists.
|
||||
*
|
||||
* @param bool $include_computed
|
||||
* If set to TRUE, computed fields are included. Defaults to TRUE.
|
||||
|
@ -185,6 +185,17 @@ interface FieldableEntityInterface extends EntityInterface {
|
|||
*/
|
||||
public function getFields($include_computed = TRUE);
|
||||
|
||||
/**
|
||||
* Gets an array of field item lists for translatable fields.
|
||||
*
|
||||
* @param bool $include_computed
|
||||
* If set to TRUE, computed fields are included. Defaults to TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Field\FieldItemListInterface[]
|
||||
* An array of field item lists implementing, keyed by field name.
|
||||
*/
|
||||
public function getTranslatableFields($include_computed = TRUE);
|
||||
|
||||
/**
|
||||
* Reacts to changes to a field.
|
||||
*
|
||||
|
@ -212,4 +223,22 @@ interface FieldableEntityInterface extends EntityInterface {
|
|||
*/
|
||||
public function validate();
|
||||
|
||||
/**
|
||||
* Checks whether entity validation is required before saving the entity.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if validation is required, FALSE if not.
|
||||
*/
|
||||
public function isValidationRequired();
|
||||
|
||||
/**
|
||||
* Sets whether entity validation is required before saving the entity.
|
||||
*
|
||||
* @param bool $required
|
||||
* TRUE if validation is required, FALSE otherwise.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setValidationRequired($required);
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity\KeyValueStore;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
|
@ -167,10 +166,7 @@ class KeyValueEntityStorage extends EntityStorageBase {
|
|||
// @todo This is not config-specific, but serial IDs will likely never hit
|
||||
// this limit. Consider renaming the exception class.
|
||||
if (strlen($entity->id()) > static::MAX_ID_LENGTH) {
|
||||
throw new ConfigEntityIdLengthException(SafeMarkup::format('Entity ID @id exceeds maximum allowed length of @length characters.', array(
|
||||
'@id' => $entity->id(),
|
||||
'@length' => static::MAX_ID_LENGTH,
|
||||
)));
|
||||
throw new ConfigEntityIdLengthException("Entity ID {$entity->id()} exceeds maximum allowed length of " . static::MAX_ID_LENGTH . ' characters.');
|
||||
}
|
||||
return parent::save($entity);
|
||||
}
|
||||
|
|
|
@ -81,12 +81,12 @@ class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexData
|
|||
*/
|
||||
public function get($property_name) {
|
||||
if (!isset($this->entity)) {
|
||||
throw new MissingDataException(SafeMarkup::format('Unable to get property @name as no entity has been provided.', array('@name' => $property_name)));
|
||||
throw new MissingDataException("Unable to get property $property_name as no entity has been provided.");
|
||||
}
|
||||
if (!$this->entity instanceof FieldableEntityInterface) {
|
||||
// @todo: Add support for config entities in
|
||||
// https://www.drupal.org/node/1818574.
|
||||
throw new \InvalidArgumentException(SafeMarkup::format('Unable to get unknown property @name.', array('@name' => $property_name)));
|
||||
throw new \InvalidArgumentException("Unable to get unknown property $property_name.");
|
||||
}
|
||||
// This will throw an exception for unknown fields.
|
||||
return $this->entity->get($property_name);
|
||||
|
@ -97,12 +97,12 @@ class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexData
|
|||
*/
|
||||
public function set($property_name, $value, $notify = TRUE) {
|
||||
if (!isset($this->entity)) {
|
||||
throw new MissingDataException(SafeMarkup::format('Unable to set property @name as no entity has been provided.', array('@name' => $property_name)));
|
||||
throw new MissingDataException("Unable to set property $property_name as no entity has been provided.");
|
||||
}
|
||||
if (!$this->entity instanceof FieldableEntityInterface) {
|
||||
// @todo: Add support for config entities in
|
||||
// https://www.drupal.org/node/1818574.
|
||||
throw new \InvalidArgumentException(SafeMarkup::format('Unable to set unknown property @name.', array('@name' => $property_name)));
|
||||
throw new \InvalidArgumentException("Unable to set unknown property $property_name.");
|
||||
}
|
||||
// This will throw an exception for unknown fields.
|
||||
$this->entity->set($property_name, $value, $notify);
|
||||
|
@ -129,7 +129,7 @@ class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexData
|
|||
*/
|
||||
public function toArray() {
|
||||
if (!isset($this->entity)) {
|
||||
throw new MissingDataException(SafeMarkup::format('Unable to get property values as no entity has been provided.'));
|
||||
throw new MissingDataException('Unable to get property values as no entity has been provided.');
|
||||
}
|
||||
return $this->entity->toArray();
|
||||
}
|
||||
|
@ -151,13 +151,6 @@ class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexData
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDataDefinition() {
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -36,15 +36,27 @@ abstract class ConditionFundamentals {
|
|||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* List of potential namespaces of the classes belonging to this condition.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $namespaces = array();
|
||||
|
||||
/**
|
||||
* Constructs a Condition object.
|
||||
*
|
||||
* @param string $conjunction
|
||||
* The operator to use to combine conditions: 'AND' or 'OR'.
|
||||
* @param QueryInterface $query
|
||||
* The entity query this condition belongs to.
|
||||
* @param array $namespaces
|
||||
* List of potential namespaces of the classes belonging to this condition.
|
||||
*/
|
||||
public function __construct($conjunction, QueryInterface $query) {
|
||||
public function __construct($conjunction, QueryInterface $query, $namespaces = []) {
|
||||
$this->conjunction = $conjunction;
|
||||
$this->query = $query;
|
||||
$this->namespaces = $namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Query\Sql;
|
||||
|
||||
use Drupal\Core\Entity\Query\ConditionBase;
|
||||
use Drupal\Core\Entity\Query\ConditionInterface;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Database\Query\Condition as SqlCondition;
|
||||
use Drupal\Core\Entity\Query\ConditionBase;
|
||||
use Drupal\Core\Entity\Query\ConditionInterface;
|
||||
|
||||
/**
|
||||
* Implements entity query conditions for SQL databases.
|
||||
|
@ -28,6 +28,7 @@ class Condition extends ConditionBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function compile($conditionContainer) {
|
||||
|
||||
// If this is not the top level condition group then the sql query is
|
||||
// added to the $conditionContainer object by this function itself. The
|
||||
// SQL query object is only necessary to pass to Query::addField() so it
|
||||
|
@ -41,13 +42,21 @@ class Condition extends ConditionBase {
|
|||
// Add the SQL query to the object before calling this method again.
|
||||
$sql_condition->sqlQuery = $sql_query;
|
||||
$condition['field']->compile($sql_condition);
|
||||
$sql_query->condition($sql_condition);
|
||||
$conditionContainer->condition($sql_condition);
|
||||
}
|
||||
else {
|
||||
$type = strtoupper($this->conjunction) == 'OR' || $condition['operator'] == 'IS NULL' ? 'LEFT' : 'INNER';
|
||||
$field = $tables->addField($condition['field'], $type, $condition['langcode']);
|
||||
$condition['real_field'] = $field;
|
||||
static::translateCondition($condition, $sql_query, $tables->isFieldCaseSensitive($condition['field']));
|
||||
$conditionContainer->condition($field, $condition['value'], $condition['operator']);
|
||||
|
||||
// Add the translated conditions back to the condition container.
|
||||
if (isset($condition['where']) && isset($condition['where_args'])) {
|
||||
$conditionContainer->where($condition['where'], $condition['where_args']);
|
||||
}
|
||||
else {
|
||||
$conditionContainer->condition($field, $condition['value'], $condition['operator']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,10 +89,11 @@ class Condition extends ConditionBase {
|
|||
* @see \Drupal\Core\Database\Query\ConditionInterface::condition()
|
||||
*/
|
||||
public static function translateCondition(&$condition, SelectInterface $sql_query, $case_sensitive) {
|
||||
// There is nothing we can do for IN ().
|
||||
// // There is nothing we can do for IN ().
|
||||
if (is_array($condition['value'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the default operator is set to simplify the cases below.
|
||||
if (empty($condition['operator'])) {
|
||||
$condition['operator'] = '=';
|
||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\Core\Database\Query\SelectInterface;
|
|||
use Drupal\Core\Entity\Query\ConditionAggregateBase;
|
||||
use Drupal\Core\Entity\Query\ConditionAggregateInterface;
|
||||
use Drupal\Core\Database\Query\Condition as SqlCondition;
|
||||
use Drupal\Core\Entity\Query\QueryBase;
|
||||
|
||||
/**
|
||||
* Defines the aggregate condition for sql based storage.
|
||||
|
@ -39,7 +40,8 @@ class ConditionAggregate extends ConditionAggregateBase {
|
|||
else {
|
||||
$type = ((strtoupper($this->conjunction) == 'OR') || ($condition['operator'] == 'IS NULL')) ? 'LEFT' : 'INNER';
|
||||
$field = $tables->addField($condition['field'], $type, $condition['langcode']);
|
||||
Condition::translateCondition($condition, $sql_query, $tables->isFieldCaseSensitive($condition['field']));
|
||||
$condition_class = QueryBase::getClass($this->namespaces, 'Condition');
|
||||
$condition_class::translateCondition($condition, $sql_query, $tables->isFieldCaseSensitive($condition['field']));
|
||||
$function = $condition['function'];
|
||||
$placeholder = ':db_placeholder_' . $conditionContainer->nextPlaceholder();
|
||||
$conditionContainer->having("$function($field) {$condition['operator']} $placeholder", array($placeholder => $condition['value']));
|
||||
|
|
|
@ -52,7 +52,8 @@ class QueryAggregate extends Query implements QueryAggregateInterface {
|
|||
* Implements \Drupal\Core\Entity\Query\QueryAggregateInterface::conditionAggregateGroupFactory().
|
||||
*/
|
||||
public function conditionAggregateGroupFactory($conjunction = 'AND') {
|
||||
return new ConditionAggregate($conjunction, $this);
|
||||
$class = static::getClass($this->namespaces, 'ConditionAggregate');
|
||||
return new $class($conjunction, $this, $this->namespaces);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -215,7 +215,7 @@ class Tables implements TablesInterface {
|
|||
$index_prefix .= "$next_index_prefix.";
|
||||
}
|
||||
else {
|
||||
throw new QueryException(format_string('Invalid specifier @next.', array('@next' => $relationship_specifier)));
|
||||
throw new QueryException("Invalid specifier '$relationship_specifier'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ class Tables implements TablesInterface {
|
|||
return $this->entityTables[$index_prefix . $table];
|
||||
}
|
||||
}
|
||||
throw new QueryException(format_string('@property not found', array('@property' => $property)));
|
||||
throw new QueryException("'$property' not found");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
41
core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
Normal file
41
core/lib/Drupal/Core/Entity/Query/Sql/pgsql/Condition.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\Query\Sql\pgsql\Condition.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\Query\Sql\pgsql;
|
||||
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Entity\Query\Sql\Condition as BaseCondition;
|
||||
|
||||
/**
|
||||
* Implements entity query conditions for PostgreSQL databases.
|
||||
*/
|
||||
class Condition extends BaseCondition {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function translateCondition(&$condition, SelectInterface $sql_query, $case_sensitive) {
|
||||
if (is_array($condition['value']) && $case_sensitive === FALSE) {
|
||||
$condition['where'] = 'LOWER(' . $sql_query->escapeField($condition['real_field']) . ') ' . $condition['operator'] . ' (';
|
||||
$condition['where_args'] = [];
|
||||
|
||||
$n = 1;
|
||||
// Only use the array values in case an associative array is passed as an
|
||||
// argument following similar pattern in
|
||||
// \Drupal\Core\Database\Connection::expandArguments().
|
||||
foreach ($condition['value'] as $value) {
|
||||
$condition['where'] .= 'LOWER(:value' . $n . '),';
|
||||
$condition['where_args'][':value' . $n] = $value;
|
||||
$n++;
|
||||
}
|
||||
$condition['where'] = trim($condition['where'], ',');
|
||||
$condition['where'] .= ')';
|
||||
return;
|
||||
}
|
||||
parent::translateCondition($condition, $sql_query, $case_sensitive);
|
||||
}
|
||||
}
|
30
core/lib/Drupal/Core/Entity/Query/Sql/pgsql/QueryFactory.php
Normal file
30
core/lib/Drupal/Core/Entity/Query/Sql/pgsql/QueryFactory.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\Query\Sql\pgsql\QueryFactory.
|
||||
*/
|
||||
|
||||
|
||||
namespace Drupal\Core\Entity\Query\Sql\pgsql;
|
||||
|
||||
use Drupal\Core\Entity\Query\Sql\QueryFactory as BaseQueryFactory;
|
||||
|
||||
/**
|
||||
* PostgreSQL specific entity query implementation.
|
||||
*
|
||||
* To add a new query implementation extending the default SQL one, add
|
||||
* a service definition like pgsql.entity.query.sql and a factory class like
|
||||
* this. The system will automatically find the relevant Query, QueryAggregate,
|
||||
* Condition, ConditionAggregate, Tables classes in this namespace, in the
|
||||
* namespace of the parent class and so on. So after creating an empty query
|
||||
* factory class like this, it is possible to just drop in a class extending
|
||||
* the base class in this namespace and it will be used automatically but it
|
||||
* is optional: if a class is not extended the relevant default is used.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\Query\QueryBase::getNamespaces()
|
||||
* @see \Drupal\Core\Entity\Query\QueryBase::getClass()
|
||||
*/
|
||||
class QueryFactory extends BaseQueryFactory {
|
||||
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Sql;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
|
@ -178,7 +177,7 @@ class DefaultTableMapping implements TableMappingInterface {
|
|||
}
|
||||
|
||||
if (!isset($result)) {
|
||||
throw new SqlContentEntityStorageException(SafeMarkup::format('Table information not available for the "@field_name" field.', array('@field_name' => $field_name)));
|
||||
throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field.");
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@ -211,7 +210,7 @@ class DefaultTableMapping implements TableMappingInterface {
|
|||
$column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name;
|
||||
}
|
||||
else {
|
||||
throw new SqlContentEntityStorageException(SafeMarkup::format('Column information not available for the "@field_name" field.', array('@field_name' => $field_name)));
|
||||
throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field.");
|
||||
}
|
||||
|
||||
return $column_name;
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Sql;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\Database;
|
||||
|
@ -22,7 +20,6 @@ use Drupal\Core\Entity\EntityTypeInterface;
|
|||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
|
@ -109,13 +106,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The entity type's storage schema object.
|
||||
*
|
||||
|
@ -123,13 +113,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
*/
|
||||
protected $storageSchema;
|
||||
|
||||
/**
|
||||
* Cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cacheBackend;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
|
@ -176,10 +159,8 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
* The language manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
|
||||
parent::__construct($entity_type);
|
||||
parent::__construct($entity_type, $entity_manager, $cache);
|
||||
$this->database = $database;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->cacheBackend = $cache;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->initTableLayout();
|
||||
}
|
||||
|
@ -284,7 +265,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
$this->initTableLayout();
|
||||
}
|
||||
else {
|
||||
throw new EntityStorageException(SafeMarkup::format('Unsupported entity type @id', array('@id' => $entity_type->id())));
|
||||
throw new EntityStorageException("Unsupported entity type {$entity_type->id()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,8 +395,10 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
$entities_from_cache = $this->getFromPersistentCache($ids);
|
||||
|
||||
// Load any remaining entities from the database.
|
||||
$entities_from_storage = $this->getFromStorage($ids);
|
||||
$this->setPersistentCache($entities_from_storage);
|
||||
if ($entities_from_storage = $this->getFromStorage($ids)) {
|
||||
$this->invokeStorageLoadHook($entities_from_storage);
|
||||
$this->setPersistentCache($entities_from_storage);
|
||||
}
|
||||
|
||||
return $entities_from_cache + $entities_from_storage;
|
||||
}
|
||||
|
@ -447,157 +430,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
// Map the loaded records into entity objects and according fields.
|
||||
if ($records) {
|
||||
$entities = $this->mapFromStorageRecords($records);
|
||||
|
||||
// Call hook_entity_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations('entity_storage_load') as $module) {
|
||||
$function = $module . '_entity_storage_load';
|
||||
$function($entities, $this->entityTypeId);
|
||||
}
|
||||
// Call hook_TYPE_storage_load().
|
||||
foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_storage_load') as $module) {
|
||||
$function = $module . '_' . $this->entityTypeId . '_storage_load';
|
||||
$function($entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures integer entity IDs are valid.
|
||||
*
|
||||
* The identifier sanitization provided by this method has been introduced
|
||||
* as Drupal used to rely on the database to facilitate this, which worked
|
||||
* correctly with MySQL but led to errors with other DBMS such as PostgreSQL.
|
||||
*
|
||||
* @param array $ids
|
||||
* The entity IDs to verify.
|
||||
* @return array
|
||||
* The sanitized list of entity IDs.
|
||||
*/
|
||||
protected function cleanIds(array $ids) {
|
||||
$definitions = $this->entityManager->getBaseFieldDefinitions($this->entityTypeId);
|
||||
$id_definition = $definitions[$this->entityType->getKey('id')];
|
||||
if ($id_definition->getType() == 'integer') {
|
||||
$ids = array_filter($ids, function ($id) {
|
||||
return is_numeric($id) && $id == (int) $id;
|
||||
});
|
||||
$ids = array_map('intval', $ids);
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the persistent cache backend.
|
||||
*
|
||||
* @param array|null &$ids
|
||||
* If not empty, return entities that match these IDs. IDs that were found
|
||||
* will be removed from the list.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\ContentEntityInterface[]
|
||||
* Array of entities from the persistent cache.
|
||||
*/
|
||||
protected function getFromPersistentCache(array &$ids = NULL) {
|
||||
if (!$this->entityType->isPersistentlyCacheable() || empty($ids)) {
|
||||
return array();
|
||||
}
|
||||
$entities = array();
|
||||
// Build the list of cache entries to retrieve.
|
||||
$cid_map = array();
|
||||
foreach ($ids as $id) {
|
||||
$cid_map[$id] = $this->buildCacheId($id);
|
||||
}
|
||||
$cids = array_values($cid_map);
|
||||
if ($cache = $this->cacheBackend->getMultiple($cids)) {
|
||||
// Get the entities that were found in the cache.
|
||||
foreach ($ids as $index => $id) {
|
||||
$cid = $cid_map[$id];
|
||||
if (isset($cache[$cid])) {
|
||||
$entities[$id] = $cache[$cid]->data;
|
||||
unset($ids[$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the persistent cache backend.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* Entities to store in the cache.
|
||||
*/
|
||||
protected function setPersistentCache($entities) {
|
||||
if (!$this->entityType->isPersistentlyCacheable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_tags = array(
|
||||
$this->entityTypeId . '_values',
|
||||
'entity_field_info',
|
||||
);
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes hook_entity_load_uncached().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
|
||||
* List of entities, keyed on the entity ID.
|
||||
*/
|
||||
protected function invokeLoadUncachedHook(array &$entities) {
|
||||
if (!empty($entities)) {
|
||||
// Call hook_entity_load_uncached().
|
||||
foreach ($this->moduleHandler()->getImplementations('entity_load_uncached') as $module) {
|
||||
$function = $module . '_entity_load_uncached';
|
||||
$function($entities, $this->entityTypeId);
|
||||
}
|
||||
// Call hook_TYPE_load_uncached().
|
||||
foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_load_uncached') as $module) {
|
||||
$function = $module . '_' . $this->entityTypeId . '_load_uncached';
|
||||
$function($entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
if ($ids) {
|
||||
$cids = array();
|
||||
foreach ($ids as $id) {
|
||||
unset($this->entities[$id]);
|
||||
$cids[] = $this->buildCacheId($id);
|
||||
}
|
||||
if ($this->entityType->isPersistentlyCacheable()) {
|
||||
$this->cacheBackend->deleteMultiple($cids);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->entities = array();
|
||||
if ($this->entityType->isPersistentlyCacheable()) {
|
||||
Cache::invalidateTags(array($this->entityTypeId . '_values'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the cache ID for the passed in entity ID.
|
||||
*
|
||||
* @param int $id
|
||||
* Entity ID for which the cache ID should be built.
|
||||
*
|
||||
* @return string
|
||||
* Cache ID that can be passed to the cache backend.
|
||||
*/
|
||||
protected function buildCacheId($id) {
|
||||
return "values:{$this->entityTypeId}:$id";
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps from storage records to entity objects, and attaches fields.
|
||||
*
|
||||
|
@ -727,7 +565,9 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadRevision($revision_id) {
|
||||
protected function doLoadRevisionFieldItems($revision_id) {
|
||||
$revision = NULL;
|
||||
|
||||
// Build and execute the query.
|
||||
$query_result = $this->buildQuery(array(), $revision_id)->execute();
|
||||
$records = $query_result->fetchAllAssoc($this->idKey);
|
||||
|
@ -735,31 +575,20 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
if (!empty($records)) {
|
||||
// Convert the raw records to entity objects.
|
||||
$entities = $this->mapFromStorageRecords($records, TRUE);
|
||||
$this->postLoad($entities);
|
||||
$entity = reset($entities);
|
||||
if ($entity) {
|
||||
return $entity;
|
||||
}
|
||||
$revision = reset($entities) ?: NULL;
|
||||
}
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Entity\EntityStorageInterface::deleteRevision().
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteRevision($revision_id) {
|
||||
if ($revision = $this->loadRevision($revision_id)) {
|
||||
// Prevent deletion if this is the default revision.
|
||||
if ($revision->isDefaultRevision()) {
|
||||
throw new EntityStorageException('Default revision can not be deleted');
|
||||
}
|
||||
|
||||
$this->database->delete($this->revisionTable)
|
||||
->condition($this->revisionKey, $revision->getRevisionId())
|
||||
->execute();
|
||||
$this->invokeFieldMethod('deleteRevision', $revision);
|
||||
$this->deleteRevisionFromDedicatedTables($revision);
|
||||
$this->invokeHook('revision_delete', $revision);
|
||||
}
|
||||
protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
|
||||
$this->database->delete($this->revisionTable)
|
||||
->condition($this->revisionKey, $revision->getRevisionId())
|
||||
->execute();
|
||||
$this->deleteRevisionFromDedicatedTables($revision);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -878,7 +707,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete($entities) {
|
||||
protected function doDeleteFieldItems($entities) {
|
||||
$ids = array_keys($entities);
|
||||
|
||||
$this->database->delete($this->entityType->getBaseTable())
|
||||
|
@ -904,7 +733,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
}
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$this->invokeFieldMethod('delete', $entity);
|
||||
$this->deleteFromDedicatedTables($entity);
|
||||
}
|
||||
}
|
||||
|
@ -915,9 +743,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
public function save(EntityInterface $entity) {
|
||||
$transaction = $this->database->startTransaction();
|
||||
try {
|
||||
// Sync the changes made in the fields array to the internal values array.
|
||||
$entity->updateOriginalValues();
|
||||
|
||||
$return = parent::save($entity);
|
||||
|
||||
// Ignore replica server temporarily.
|
||||
|
@ -934,75 +759,97 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave($id, EntityInterface $entity) {
|
||||
// Create the storage record to be saved.
|
||||
$record = $this->mapToStorageRecord($entity);
|
||||
protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
|
||||
$full_save = empty($names);
|
||||
$update = !$full_save || !$entity->isNew();
|
||||
|
||||
$is_new = $entity->isNew();
|
||||
if (!$is_new) {
|
||||
if ($entity->isDefaultRevision()) {
|
||||
$this->database
|
||||
->update($this->baseTable)
|
||||
->fields((array) $record)
|
||||
->condition($this->idKey, $record->{$this->idKey})
|
||||
->execute();
|
||||
$return = SAVED_UPDATED;
|
||||
}
|
||||
else {
|
||||
// @todo, should a different value be returned when saving an entity
|
||||
// with $isDefaultRevision = FALSE?
|
||||
$return = FALSE;
|
||||
}
|
||||
if ($this->revisionTable) {
|
||||
$entity->{$this->revisionKey}->value = $this->saveRevision($entity);
|
||||
}
|
||||
if ($this->dataTable) {
|
||||
$this->populateAffectedRevisionTranslations($entity);
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable);
|
||||
}
|
||||
if ($full_save) {
|
||||
$shared_table_fields = TRUE;
|
||||
$dedicated_table_fields = TRUE;
|
||||
}
|
||||
else {
|
||||
// Ensure the entity is still seen as new after assigning it an id,
|
||||
// while storing its data.
|
||||
$entity->enforceIsNew();
|
||||
$insert_id = $this->database
|
||||
->insert($this->baseTable, array('return' => Database::RETURN_INSERT_ID))
|
||||
->fields((array) $record)
|
||||
->execute();
|
||||
// Even if this is a new entity the ID key might have been set, in which
|
||||
// case we should not override the provided ID. An ID key that is not set
|
||||
// to any value is interpreted as NULL (or DEFAULT) and thus overridden.
|
||||
if (!isset($record->{$this->idKey})) {
|
||||
$record->{$this->idKey} = $insert_id;
|
||||
}
|
||||
$return = SAVED_NEW;
|
||||
$entity->{$this->idKey}->value = (string) $record->{$this->idKey};
|
||||
if ($this->revisionTable) {
|
||||
$entity->setNewRevision();
|
||||
$record->{$this->revisionKey} = $this->saveRevision($entity);
|
||||
}
|
||||
if ($this->dataTable) {
|
||||
$this->populateAffectedRevisionTranslations($entity);
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable);
|
||||
}
|
||||
}
|
||||
$this->invokeFieldMethod($is_new ? 'insert' : 'update', $entity);
|
||||
$this->saveToDedicatedTables($entity, !$is_new);
|
||||
$table_mapping = $this->getTableMapping();
|
||||
$storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
|
||||
$shared_table_fields = FALSE;
|
||||
$dedicated_table_fields = [];
|
||||
|
||||
if (!$is_new && $this->dataTable) {
|
||||
$this->invokeTranslationHooks($entity);
|
||||
// Collect the name of fields to be written in dedicated tables and check
|
||||
// whether shared table records need to be updated.
|
||||
foreach ($names as $name) {
|
||||
$storage_definition = $storage_definitions[$name];
|
||||
if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
|
||||
$shared_table_fields = TRUE;
|
||||
}
|
||||
elseif ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
|
||||
$dedicated_table_fields[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
$entity->enforceIsNew(FALSE);
|
||||
if ($this->revisionTable) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
|
||||
// Update shared table records if necessary.
|
||||
if ($shared_table_fields) {
|
||||
$record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable);
|
||||
// Create the storage record to be saved.
|
||||
if ($update) {
|
||||
$default_revision = $entity->isDefaultRevision();
|
||||
if ($default_revision) {
|
||||
$this->database
|
||||
->update($this->baseTable)
|
||||
->fields((array) $record)
|
||||
->condition($this->idKey, $record->{$this->idKey})
|
||||
->execute();
|
||||
}
|
||||
if ($this->revisionTable) {
|
||||
if ($full_save) {
|
||||
$entity->{$this->revisionKey} = $this->saveRevision($entity);
|
||||
}
|
||||
else {
|
||||
$record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
|
||||
$entity->preSaveRevision($this, $record);
|
||||
$this->database
|
||||
->update($this->revisionTable)
|
||||
->fields((array) $record)
|
||||
->condition($this->revisionKey, $record->{$this->revisionKey})
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
if ($default_revision && $this->dataTable) {
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$new_revision = $full_save && $entity->isNewRevision();
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable, $new_revision);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$insert_id = $this->database
|
||||
->insert($this->baseTable, array('return' => Database::RETURN_INSERT_ID))
|
||||
->fields((array) $record)
|
||||
->execute();
|
||||
// Even if this is a new entity the ID key might have been set, in which
|
||||
// case we should not override the provided ID. An ID key that is not set
|
||||
// to any value is interpreted as NULL (or DEFAULT) and thus overridden.
|
||||
if (!isset($record->{$this->idKey})) {
|
||||
$record->{$this->idKey} = $insert_id;
|
||||
}
|
||||
$entity->{$this->idKey} = (string) $record->{$this->idKey};
|
||||
if ($this->revisionTable) {
|
||||
$record->{$this->revisionKey} = $this->saveRevision($entity);
|
||||
}
|
||||
if ($this->dataTable) {
|
||||
$this->saveToSharedTables($entity);
|
||||
}
|
||||
if ($this->revisionDataTable) {
|
||||
$this->saveToSharedTables($entity, $this->revisionDataTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update dedicated table records if necessary.
|
||||
if ($dedicated_table_fields) {
|
||||
$names = is_array($dedicated_table_fields) ? $dedicated_table_fields : [];
|
||||
$this->saveToDedicatedTables($entity, $update, $names);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1019,14 +866,20 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
* The entity object.
|
||||
* @param string $table_name
|
||||
* (optional) The table name to save to. Defaults to the data table.
|
||||
* @param bool $new_revision
|
||||
* (optional) Whether we are dealing with a new revision. By default fetches
|
||||
* the information from the entity object.
|
||||
*/
|
||||
protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL) {
|
||||
protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) {
|
||||
if (!isset($table_name)) {
|
||||
$table_name = $this->dataTable;
|
||||
}
|
||||
if (!isset($new_revision)) {
|
||||
$new_revision = $entity->isNewRevision();
|
||||
}
|
||||
$revision = $table_name != $this->dataTable;
|
||||
|
||||
if (!$revision || !$entity->isNewRevision()) {
|
||||
if (!$revision || !$new_revision) {
|
||||
$key = $revision ? $this->revisionKey : $this->idKey;
|
||||
$value = $revision ? $entity->getRevisionId() : $entity->id();
|
||||
// Delete and insert to handle removed values.
|
||||
|
@ -1070,7 +923,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
|
||||
|
||||
if (empty($this->getFieldStorageDefinitions()[$field_name])) {
|
||||
throw new EntityStorageException(SafeMarkup::format('Table mapping contains invalid field %field.', array('%field' => $field_name)));
|
||||
throw new EntityStorageException("Table mapping contains invalid field $field_name.");
|
||||
}
|
||||
$definition = $this->getFieldStorageDefinitions()[$field_name];
|
||||
$columns = $table_mapping->getColumnNames($field_name);
|
||||
|
@ -1303,8 +1156,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
* The entity.
|
||||
* @param bool $update
|
||||
* TRUE if the entity is being updated, FALSE if it is being inserted.
|
||||
* @param string[] $names
|
||||
* (optional) The names of the fields to be stored. Defaults to all the
|
||||
* available fields.
|
||||
*/
|
||||
protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE) {
|
||||
protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = array()) {
|
||||
$vid = $entity->getRevisionId();
|
||||
$id = $entity->id();
|
||||
$bundle = $entity->bundle();
|
||||
|
@ -1319,7 +1175,13 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
|
||||
$original = !empty($entity->original) ? $entity->original: NULL;
|
||||
|
||||
foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $field_definition) {
|
||||
// Determine which fields should be actually stored.
|
||||
$definitions = $this->entityManager->getFieldDefinitions($entity_type, $bundle);
|
||||
if ($names) {
|
||||
$definitions = array_intersect_key($definitions, array_flip($names));
|
||||
}
|
||||
|
||||
foreach ($definitions as $field_name => $field_definition) {
|
||||
$storage_definition = $field_definition->getFieldStorageDefinition();
|
||||
if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
|
||||
continue;
|
||||
|
@ -1760,9 +1622,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
$or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $property_name));
|
||||
}
|
||||
$query->condition($or);
|
||||
$query
|
||||
->fields('t', array($this->idKey))
|
||||
->distinct(TRUE);
|
||||
if (!$as_bool) {
|
||||
$query
|
||||
->fields('t', array($this->idKey))
|
||||
->distinct(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Find a way to count field data also for fields having custom
|
||||
|
@ -1772,7 +1636,9 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
|
|||
// If we are performing the query just to check if the field has data
|
||||
// limit the number of rows.
|
||||
if ($as_bool) {
|
||||
$query->range(0, 1);
|
||||
$query
|
||||
->range(0, 1)
|
||||
->addExpression('1');
|
||||
}
|
||||
else {
|
||||
// Otherwise count the number of rows.
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Sql;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\DatabaseException;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
|
@ -286,7 +285,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
|
||||
// If a migration is required, we can't proceed.
|
||||
if ($this->requiresEntityDataMigration($entity_type, $original)) {
|
||||
throw new EntityStorageException(SafeMarkup::format('The SQL storage cannot change the schema for an existing entity type with data.'));
|
||||
throw new EntityStorageException('The SQL storage cannot change the schema for an existing entity type with data.');
|
||||
}
|
||||
|
||||
// If we have no data just recreate the entity schema from scratch.
|
||||
|
@ -467,7 +466,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
*/
|
||||
protected function checkEntityType(EntityTypeInterface $entity_type) {
|
||||
if ($entity_type->id() != $this->entityType->id()) {
|
||||
throw new EntityStorageException(SafeMarkup::format('Unsupported entity type @id', array('@id' => $entity_type->id())));
|
||||
throw new EntityStorageException("Unsupported entity type {$entity_type->id()}");
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -530,7 +529,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
}
|
||||
foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
|
||||
if (!isset($storage_definitions[$field_name])) {
|
||||
throw new FieldException(SafeMarkup::format('Field storage definition for "@field_name" could not be found.', array('@field_name' => $field_name)));
|
||||
throw new FieldException("Field storage definition for '$field_name' could not be found.");
|
||||
}
|
||||
// Add the schema for base field definitions.
|
||||
elseif ($table_mapping->allowsSharedTableStorage($storage_definitions[$field_name])) {
|
||||
|
@ -906,7 +905,9 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
$schema = array(
|
||||
'description' => "The data table for $entity_type_id entities.",
|
||||
'primary key' => array($id_key, $entity_type->getKey('langcode')),
|
||||
'indexes' => array(),
|
||||
'indexes' => array(
|
||||
$entity_type_id . '__id__default_langcode__langcode' => array($id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
$entity_type_id => array(
|
||||
'table' => $this->storage->getBaseTable(),
|
||||
|
@ -942,7 +943,9 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
$schema = array(
|
||||
'description' => "The revision data table for $entity_type_id entities.",
|
||||
'primary key' => array($revision_key, $entity_type->getKey('langcode')),
|
||||
'indexes' => array(),
|
||||
'indexes' => array(
|
||||
$entity_type_id . '__id__default_langcode__langcode' => array($id_key, $entity_type->getKey('default_langcode'), $entity_type->getKey('langcode')),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
$entity_type_id => array(
|
||||
'table' => $this->storage->getBaseTable(),
|
||||
|
@ -1017,6 +1020,9 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
* A partial schema array for the base table.
|
||||
*/
|
||||
protected function processDataTable(ContentEntityTypeInterface $entity_type, array &$schema) {
|
||||
// Marking the respective fields as NOT NULL makes the indexes more
|
||||
// performant.
|
||||
$schema['fields'][$entity_type->getKey('default_langcode')]['not null'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1031,6 +1037,9 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
* A partial schema array for the base table.
|
||||
*/
|
||||
protected function processRevisionDataTable(ContentEntityTypeInterface $entity_type, array &$schema) {
|
||||
// Marking the respective fields as NOT NULL makes the indexes more
|
||||
// performant.
|
||||
$schema['fields'][$entity_type->getKey('default_langcode')]['not null'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1420,7 +1429,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
|
||||
// Check that the schema does not include forbidden column names.
|
||||
if (array_intersect(array_keys($field_schema['columns']), $this->storage->getTableMapping()->getReservedColumns())) {
|
||||
throw new FieldException(format_string('Illegal field column names on @field_name', array('@field_name' => $storage_definition->getName())));
|
||||
throw new FieldException("Illegal field column names on {$storage_definition->getName()}");
|
||||
}
|
||||
|
||||
$field_name = $storage_definition->getName();
|
||||
|
@ -1642,7 +1651,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage
|
|||
$properties = $storage_definition->getPropertyDefinitions();
|
||||
$table_mapping = $this->storage->getTableMapping();
|
||||
if (array_intersect(array_keys($schema['columns']), $table_mapping->getReservedColumns())) {
|
||||
throw new FieldException(format_string('Illegal field column names on @field_name', array('@field_name' => $storage_definition->getName())));
|
||||
throw new FieldException("Illegal field column names on {$storage_definition->getName()}");
|
||||
}
|
||||
|
||||
// Add field columns.
|
||||
|
|
|
@ -1808,13 +1808,15 @@ function hook_entity_field_storage_info_alter(&$fields, \Drupal\Core\Entity\Enti
|
|||
*
|
||||
* @return array
|
||||
* An operations array as returned by
|
||||
* \Drupal\Core\Entity\EntityListBuilderInterface::getOperations().
|
||||
* EntityListBuilderInterface::getOperations().
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityListBuilderInterface::getOperations()
|
||||
*/
|
||||
function hook_entity_operation(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
$operations = array();
|
||||
$operations['translate'] = array(
|
||||
'title' => t('Translate'),
|
||||
'route_name' => 'foo_module.entity.translate',
|
||||
'url' => \Drupal\Core\Url::fromRoute('foo_module.entity.translate'),
|
||||
'weight' => 50,
|
||||
);
|
||||
|
||||
|
|
Reference in a new issue