Issue #2985297 by amateescu, hchonov: Generalize the concept of entity synchronization

8.7.x
Lee Rowlands 2018-08-24 14:32:10 +10:00
parent 36ecfc620a
commit cb5774fe4b
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
8 changed files with 99 additions and 73 deletions

View File

@ -10,6 +10,7 @@ use Drupal\Core\Config\ConfigDuplicateUUIDException;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\Core\Entity\SynchronizableEntityTrait;
use Drupal\Core\Plugin\PluginDependencyTrait;
/**
@ -22,6 +23,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
use PluginDependencyTrait {
addDependency as addDependencyTrait;
}
use SynchronizableEntityTrait;
/**
* The original ID of the configuration entity.
@ -48,14 +50,6 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
*/
protected $uuid;
/**
* Whether the config is being created, updated or deleted through the
* import process.
*
* @var bool
*/
private $isSyncing = FALSE;
/**
* Whether the config is being deleted by the uninstall process.
*
@ -204,22 +198,6 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
return !empty($this->status);
}
/**
* {@inheritdoc}
*/
public function setSyncing($syncing) {
$this->isSyncing = $syncing;
return $this;
}
/**
* {@inheritdoc}
*/
public function isSyncing() {
return $this->isSyncing;
}
/**
* {@inheritdoc}
*/

View File

@ -3,6 +3,7 @@
namespace Drupal\Core\Config\Entity;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\SynchronizableInterface;
/**
* Defines a common interface for configuration entities.
@ -10,7 +11,7 @@ use Drupal\Core\Entity\EntityInterface;
* @ingroup config_api
* @ingroup entity_api
*/
interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInterface {
interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInterface, SynchronizableInterface {
/**
* Enables the configuration entity.
@ -36,16 +37,6 @@ interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInter
*/
public function setStatus($status);
/**
* Sets the status of the isSyncing flag.
*
* @param bool $status
* The status of the sync flag.
*
* @return $this
*/
public function setSyncing($status);
/**
* Returns whether the configuration entity is enabled.
*
@ -63,36 +54,6 @@ interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInter
*/
public function status();
/**
* Returns whether this entity is being changed as part of an import process.
*
* If you are writing code that responds to a change in this entity (insert,
* update, delete, presave, etc.), and your code would result in a
* configuration change (whether related to this configuration entity, another
* configuration entity, or non-entity configuration) or your code would
* result in a change to this entity itself, you need to check and see if this
* entity change is part of an import process, and skip executing your code if
* that is the case.
*
* For example, \Drupal\node\Entity\NodeType::postSave() adds the default body
* field to newly created node type configuration entities, which is a
* configuration change. You would not want this code to run during an import,
* because imported entities were already given the body field when they were
* originally created, and the imported configuration includes all of their
* currently-configured fields. On the other hand,
* \Drupal\field\Entity\FieldStorageConfig::preSave() and the methods it calls
* make sure that the storage tables are created or updated for the field
* storage configuration entity, which is not a configuration change, and it
* must be done whether due to an import or not. So, the first method should
* check $entity->isSyncing() and skip executing if it returns TRUE, and the
* second should not perform this check.
*
* @return bool
* TRUE if the configuration entity is being created, updated, or deleted
* through the import process.
*/
public function isSyncing();
/**
* Returns whether this entity is being changed during the uninstall process.
*

View File

@ -22,6 +22,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
use EntityChangesDetectionTrait {
getFieldsToSkipFromTranslationChangesCheck as traitGetFieldsToSkipFromTranslationChangesCheck;
}
use SynchronizableEntityTrait;
/**
* The plain data values of the contained fields.
@ -921,6 +922,7 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$translation->loadedRevisionId = &$this->loadedRevisionId;
$translation->isDefaultRevision = &$this->isDefaultRevision;
$translation->enforceRevisionTranslationAffected = &$this->enforceRevisionTranslationAffected;
$translation->isSyncing = &$this->isSyncing;
return $translation;
}
@ -1219,6 +1221,9 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
$is_revision_translation_affected_enforced = $this->enforceRevisionTranslationAffected;
$this->enforceRevisionTranslationAffected = &$is_revision_translation_affected_enforced;
$is_syncing = $this->isSyncing;
$this->isSyncing = &$is_syncing;
foreach ($this->fields as $name => $fields_by_langcode) {
$this->fields[$name] = [];
// Untranslatable fields may have multiple references for the same field

View File

@ -21,6 +21,6 @@ namespace Drupal\Core\Entity;
*
* @ingroup entity_api
*/
interface ContentEntityInterface extends \Traversable, FieldableEntityInterface, TranslatableRevisionableInterface {
interface ContentEntityInterface extends \Traversable, FieldableEntityInterface, TranslatableRevisionableInterface, SynchronizableInterface {
}

View File

@ -0,0 +1,36 @@
<?php
namespace Drupal\Core\Entity;
/**
* Provides a trait for accessing synchronization information.
*
* @ingroup entity_api
*/
trait SynchronizableEntityTrait {
/**
* Whether this entity is being created, updated or deleted through a
* synchronization process.
*
* @var bool
*/
protected $isSyncing = FALSE;
/**
* {@inheritdoc}
*/
public function setSyncing($syncing) {
$this->isSyncing = $syncing;
return $this;
}
/**
* {@inheritdoc}
*/
public function isSyncing() {
return $this->isSyncing;
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Drupal\Core\Entity;
/**
* Defines methods for an entity that supports synchronization.
*/
interface SynchronizableInterface {
/**
* Sets the status of the synchronization flag.
*
* @param bool $status
* The status of the synchronization flag.
*
* @return $this
*/
public function setSyncing($status);
/**
* Returns whether this entity is being changed as part of a synchronization.
*
* If you are writing code that responds to a change in this entity (insert,
* update, delete, presave, etc.), and your code would result in a change to
* this entity itself, a configuration change (whether related to this entity,
* another entity, or non-entity configuration), you need to check and see if
* this entity change is part of a synchronization process, and skip executing
* your code if that is the case.
*
* For example, \Drupal\node\Entity\NodeType::postSave() adds the default body
* field to newly created node type configuration entities, which is a
* configuration change. You would not want this code to run during an import,
* because imported entities were already given the body field when they were
* originally created, and the imported configuration includes all of their
* currently-configured fields. On the other hand,
* \Drupal\field\Entity\FieldStorageConfig::preSave() and the methods it calls
* make sure that the storage tables are created or updated for the field
* storage configuration entity, which is not a configuration change, and it
* must be done whether due to an import or not. So, the first method should
* check $entity->isSyncing() and skip executing if it returns TRUE, and the
* second should not perform this check.
*
* @return bool
* TRUE if the configuration entity is being created, updated, or deleted
* through a synchronization process.
*/
public function isSyncing();
}

View File

@ -133,8 +133,8 @@ class EntityOperations implements ContainerInjectionInterface {
throw new \RuntimeException('This entity can only be saved in the default workspace.');
}
/** @var \Drupal\Core\Entity\RevisionableInterface|\Drupal\Core\Entity\EntityPublishedInterface $entity */
if (!$entity->isNew() && !isset($entity->_isReplicating)) {
/** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\EntityPublishedInterface $entity */
if (!$entity->isNew() && !$entity->isSyncing()) {
// Force a new revision if the entity is not replicating.
$entity->setNewRevision(TRUE);

View File

@ -78,14 +78,11 @@ class WorkspacePublisher implements WorkspacePublisherInterface {
foreach ($this->getDifferringRevisionIdsOnSource() as $entity_type_id => $revision_difference) {
$entity_revisions = $this->entityTypeManager->getStorage($entity_type_id)
->loadMultipleRevisions(array_keys($revision_difference));
/** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $entity */
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
foreach ($entity_revisions as $entity) {
// When pushing workspace-specific revisions to the default workspace
// (Live), we simply need to mark them as default revisions.
// @todo Remove this dynamic property once we have an API for
// associating temporary data with an entity:
// https://www.drupal.org/node/2896474.
$entity->_isReplicating = TRUE;
$entity->setSyncing(TRUE);
$entity->isDefaultRevision(TRUE);
$entity->save();
}