class EntityReferenceRevisionsOrphanPurger in Entity Reference Revisions 8
Manages orphan composite revision deletion.
Hierarchy
- class \Drupal\entity_reference_revisions\EntityReferenceRevisionsOrphanPurger uses StringTranslationTrait
Expanded class hierarchy of EntityReferenceRevisionsOrphanPurger
3 files declare their use of EntityReferenceRevisionsOrphanPurger
- EntityReferenceRevisionsCommands.php in src/
Commands/ EntityReferenceRevisionsCommands.php - OrphanedCompositeEntitiesDeleteForm.php in src/
Form/ OrphanedCompositeEntitiesDeleteForm.php - OrphanPurger.php in src/
Plugin/ QueueWorker/ OrphanPurger.php
1 string reference to 'EntityReferenceRevisionsOrphanPurger'
1 service uses EntityReferenceRevisionsOrphanPurger
File
- src/
EntityReferenceRevisionsOrphanPurger.php, line 18
Namespace
Drupal\entity_reference_revisionsView source
class EntityReferenceRevisionsOrphanPurger {
use StringTranslationTrait;
/**
* Parent is valid.
*/
const PARENT_VALID = 0;
/**
* Parent is invalid and usage can not be verified.
*/
const PARENT_INVALID_SKIP = 1;
/**
* Parent is invalid and paragraph is safe to delete.
*/
const PARENT_INVALID_DELETE = 2;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The date formatter service.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* The database service.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* List of already checked parents.
*
* @var bool[][]
*/
protected $validParents = [];
/**
* Constructs a EntityReferenceRevisionsOrphanManager object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
* The date formatter service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\Core\Database\Connection $database
* The database service.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, DateFormatterInterface $date_formatter, TimeInterface $time, Connection $database, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->dateFormatter = $date_formatter;
$this->time = $time;
$this->database = $database;
$this->messenger = $messenger;
}
/**
* Deletes unused revision or an entity if there are no revisions remaining.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $composite_revision
* The composite revision.
*
* @return bool
* TRUE if an entity revision was deleted. Otherwise, FALSE.
*/
public function deleteUnusedRevision(ContentEntityInterface $composite_revision) {
// If this is the default revision of the composite entity, check if there
// are other revisions. If there are not, delete the composite entity.
$composite_storage = $this->entityTypeManager
->getStorage($composite_revision
->getEntityTypeId());
if ($composite_revision
->isDefaultRevision()) {
$count = $composite_storage
->getQuery()
->accessCheck(FALSE)
->allRevisions()
->condition($composite_storage
->getEntityType()
->getKey('id'), $composite_revision
->id())
->count()
->execute();
if ($count <= 1) {
$composite_revision
->delete();
return TRUE;
}
}
else {
// Delete the revision if this is not the default one.
$composite_storage
->deleteRevision($composite_revision
->getRevisionId());
return TRUE;
}
return FALSE;
}
/**
* Batch operation for checking orphans for a given entity type.
*
* @param string $entity_type_id
* The entity type id, for example 'paragraph'.
* @param Iterable|array $context
* The context array.
*/
public function deleteOrphansBatchOperation($entity_type_id, &$context) {
$composite_type = $this->entityTypeManager
->getDefinition($entity_type_id);
$composite_revision_key = $composite_type
->getKey('revision');
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $composite_storage */
$composite_storage = $this->entityTypeManager
->getStorage($entity_type_id);
$batch_size = Settings::get('entity_update_batch_size', 50);
if (empty($context['sandbox']['total'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_revision_id'] = -1;
$context['sandbox']['total'] = (int) $composite_storage
->getQuery()
->allRevisions()
->accessCheck(FALSE)
->count()
->execute();
}
if (!isset($context['results'][$entity_type_id])) {
$context['results'][$entity_type_id]['entity_count'] = 0;
$context['results'][$entity_type_id]['revision_count'] = 0;
$context['results'][$entity_type_id]['start'] = $this->time
->getRequestTime();
}
// Get the next batch of revision ids from the selected entity type.
// @todo Replace with an entity query on all revisions with a revision ID
// condition after https://www.drupal.org/project/drupal/issues/2766135.
$revision_table = $composite_type
->getRevisionTable();
$entity_revision_ids = $this->database
->select($revision_table, 'r')
->fields('r', [
$composite_revision_key,
])
->range(0, $batch_size)
->orderBy($composite_revision_key)
->condition($composite_revision_key, $context['sandbox']['current_revision_id'], '>')
->execute()
->fetchCol();
/** @var \Drupal\Core\Entity\ContentEntityInterface $composite_revision */
foreach ($composite_storage
->loadMultipleRevisions($entity_revision_ids) as $composite_revision) {
$context['sandbox']['progress']++;
$context['sandbox']['current_revision_id'] = $composite_revision
->getRevisionId();
if ($this
->isUsed($composite_revision)) {
continue;
}
if ($this
->deleteUnusedRevision($composite_revision)) {
$context['results'][$entity_type_id]['revision_count']++;
if ($composite_revision
->isDefaultRevision()) {
$context['results'][$entity_type_id]['entity_count']++;
}
}
}
// This entity type is completed if no new revision ids were found or the
// total is reached.
if ($entity_revision_ids && $context['sandbox']['progress'] < $context['sandbox']['total']) {
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['total'];
}
else {
$context['finished'] = 1;
$context['results'][$entity_type_id]['end'] = $this->time
->getRequestTime();
}
$interval = $this->dateFormatter
->formatInterval($this->time
->getRequestTime() - $context['results'][$entity_type_id]['start']);
$context['message'] = t('Checked @entity_type revisions for orphans: @current of @total in @interval (@deletions deleted)', [
'@entity_type' => $composite_type
->getLabel(),
'@current' => $context['sandbox']['progress'],
'@total' => $context['sandbox']['total'],
'@interval' => $interval,
'@deletions' => $context['results'][$entity_type_id]['revision_count'],
]);
}
/**
* Batch dispatch submission finished callback.
*/
public static function batchSubmitFinished($success, $results, $operations) {
return \Drupal::service('entity_reference_revisions.orphan_purger')
->doBatchSubmitFinished($success, $results, $operations);
}
/**
* Sets a batch for executing deletion of the orphaned composite entities.
*
* @param array $composite_entity_type_ids
* An array of composite entity type IDs to remove orphaned items for.
*/
public function setBatch(array $composite_entity_type_ids) {
if (empty($composite_entity_type_ids)) {
return;
}
$operations = [];
foreach ($composite_entity_type_ids as $entity_type_id) {
$operations[] = [
'_entity_reference_revisions_orphan_purger_batch_dispatcher',
[
'entity_reference_revisions.orphan_purger:deleteOrphansBatchOperation',
$entity_type_id,
],
];
}
$batch = [
'operations' => $operations,
'finished' => [
EntityReferenceRevisionsOrphanPurger::class,
'batchSubmitFinished',
],
'title' => $this
->t('Removing orphaned entities.'),
'progress_message' => $this
->t('Processed @current of @total entity types.'),
'error_message' => $this
->t('This batch encountered an error.'),
];
batch_set($batch);
}
/**
* Finished callback for the batch process.
*
* @param bool $success
* Whether the batch completed successfully.
* @param array $results
* The results array.
* @param array $operations
* The operations array.
*/
public function doBatchSubmitFinished($success, $results, $operations) {
if ($success) {
foreach ($results as $entity_type_id => $result) {
if ($this->entityTypeManager
->hasDefinition($entity_type_id)) {
$entity_type = $this->entityTypeManager
->getDefinition($entity_type_id);
$interval = $this->dateFormatter
->formatInterval($result['end'] - $result['start']);
$this->messenger
->addMessage($this
->t('@label: Deleted @revision_count revisions (@entity_count entities) in @interval.', [
'@label' => $entity_type
->getLabel(),
'@revision_count' => $result['revision_count'],
'@entity_count' => $result['entity_count'],
'@interval' => $interval,
]));
}
}
}
else {
// $operations contains the operations that remained unprocessed.
$error_operation = reset($operations);
$this->messenger
->addError($this
->t('An error occurred while processing @operation with arguments : @args', [
'@operation' => $error_operation[0],
'@args' => print_r($error_operation[0], TRUE),
]));
}
}
/**
* Checks if the composite entity is used.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $composite_revision
* The composite revision.
*
* @return bool
* Whether the composite entity is used, FALSE if it is safe to delete.
*/
public function isUsed(ContentEntityInterface $composite_revision) {
$composite_type = $this->entityTypeManager
->getDefinition($composite_revision
->getEntityTypeId());
$parent_type_field = $composite_type
->get('entity_revision_parent_type_field');
$parent_type = $composite_revision
->get($parent_type_field)->value;
$parent_field_name_field = $composite_type
->get('entity_revision_parent_field_name_field');
$parent_field_name = $composite_revision
->get($parent_field_name_field)->value;
$status = $this
->isValidParent($parent_type, $parent_field_name);
if ($status !== static::PARENT_VALID) {
return $status == static::PARENT_INVALID_SKIP ? TRUE : FALSE;
}
// Check if the revision is used in any revision of the parent, if that
// entity type supports revisions.
$query = $this->entityTypeManager
->getStorage($parent_type)
->getQuery()
->condition("{$parent_field_name}.target_revision_id", $composite_revision
->getRevisionId())
->range(0, 1)
->accessCheck(FALSE);
if ($this->entityTypeManager
->getDefinition($parent_type)
->isRevisionable()) {
$query = $query
->allRevisions();
}
$revisions = $query
->execute();
// If there are parent revisions where this revision is used, skip it.
return !empty($revisions);
}
/**
* Checks if the parent type/field is a valid combination that can be queried.
*
* @param string $parent_type
* Parent entity type ID.
* @param string $parent_field_name
* Parent field name.
*
* @return int
* static::PARENT_VALID, static::PARENT_INVALID_SKIP or
* static::PARENT_INVALID_DELETE.
*/
protected function isValidParent($parent_type, $parent_field_name) {
// There is not certainty that this revision is not used because we do not
// know what to query for if the parent fields are empty.
if ($parent_type == NULL) {
return static::PARENT_INVALID_SKIP;
}
if (isset($this->validParents[$parent_type][$parent_field_name])) {
return $this->validParents[$parent_type][$parent_field_name];
}
$status = static::PARENT_VALID;
// If the parent type does not exist anymore, the composite is not used.
if (!$this->entityTypeManager
->hasDefinition($parent_type)) {
$status = static::PARENT_INVALID_DELETE;
}
else {
$parent_field_definitions = $this->entityFieldManager
->getFieldStorageDefinitions($parent_type);
if (!isset($parent_field_definitions[$parent_field_name])) {
$status = static::PARENT_INVALID_DELETE;
}
elseif (empty($parent_field_definitions[$parent_field_name]
->getSchema()['columns']['target_revision_id'])) {
$status = static::PARENT_INVALID_SKIP;
}
}
$this->validParents[$parent_type][$parent_field_name] = $status;
return $status;
}
/**
* Returns a list of composite entity types.
*
* @return \Drupal\Core\Entity\EntityTypeInterface[]
* An array of composite entity types.
*/
public function getCompositeEntityTypes() {
$composite_entity_types = [];
$entity_types = $this->entityTypeManager
->getDefinitions();
foreach ($entity_types as $entity_type) {
$has_parent_type_field = $entity_type
->get('entity_revision_parent_type_field');
$has_parent_id_field = $entity_type
->get('entity_revision_parent_id_field');
$has_parent_field_name_field = $entity_type
->get('entity_revision_parent_field_name_field');
if ($has_parent_type_field && $has_parent_id_field && $has_parent_field_name_field) {
$composite_entity_types[] = $entity_type;
}
}
return $composite_entity_types;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
EntityReferenceRevisionsOrphanPurger:: |
protected | property | The database service. | |
EntityReferenceRevisionsOrphanPurger:: |
protected | property | The date formatter service. | |
EntityReferenceRevisionsOrphanPurger:: |
protected | property | The entity field manager. | |
EntityReferenceRevisionsOrphanPurger:: |
protected | property | The entity type manager. | |
EntityReferenceRevisionsOrphanPurger:: |
protected | property | The messenger. | |
EntityReferenceRevisionsOrphanPurger:: |
protected | property | The time service. | |
EntityReferenceRevisionsOrphanPurger:: |
protected | property | List of already checked parents. | |
EntityReferenceRevisionsOrphanPurger:: |
public static | function | Batch dispatch submission finished callback. | |
EntityReferenceRevisionsOrphanPurger:: |
public | function | Batch operation for checking orphans for a given entity type. | |
EntityReferenceRevisionsOrphanPurger:: |
public | function | Deletes unused revision or an entity if there are no revisions remaining. | |
EntityReferenceRevisionsOrphanPurger:: |
public | function | Finished callback for the batch process. | |
EntityReferenceRevisionsOrphanPurger:: |
public | function | Returns a list of composite entity types. | |
EntityReferenceRevisionsOrphanPurger:: |
public | function | Checks if the composite entity is used. | |
EntityReferenceRevisionsOrphanPurger:: |
protected | function | Checks if the parent type/field is a valid combination that can be queried. | |
EntityReferenceRevisionsOrphanPurger:: |
constant | Parent is invalid and paragraph is safe to delete. | ||
EntityReferenceRevisionsOrphanPurger:: |
constant | Parent is invalid and usage can not be verified. | ||
EntityReferenceRevisionsOrphanPurger:: |
constant | Parent is valid. | ||
EntityReferenceRevisionsOrphanPurger:: |
public | function | Sets a batch for executing deletion of the orphaned composite entities. | |
EntityReferenceRevisionsOrphanPurger:: |
public | function | Constructs a EntityReferenceRevisionsOrphanManager object. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |