Commit 33c2b205 authored by mnico's avatar mnico Committed by Berdir

Issue #3158156 by mnico, Berdir: Purger service removes entities that are...

Issue #3158156 by mnico, Berdir: Purger service removes entities that are referenced from parent fields created with BaseFieldDefinition
parent eec92af3
services:
entity_reference_revisions.orphan_purger:
class: Drupal\entity_reference_revisions\EntityReferenceRevisionsOrphanPurger
arguments: ['@entity_type.manager', '@date.formatter', '@datetime.time', '@database', '@messenger']
arguments: ['@entity_type.manager', '@entity_field.manager', '@date.formatter', '@datetime.time', '@database', '@messenger']
......@@ -6,6 +6,7 @@ use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Site\Settings;
......@@ -40,6 +41,13 @@ class EntityReferenceRevisionsOrphanPurger {
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The date formatter service.
*
......@@ -80,6 +88,8 @@ class EntityReferenceRevisionsOrphanPurger {
*
* @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
......@@ -89,8 +99,9 @@ class EntityReferenceRevisionsOrphanPurger {
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, DateFormatterInterface $date_formatter, TimeInterface $time, Connection $database, MessengerInterface $messenger) {
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;
......@@ -349,18 +360,21 @@ class EntityReferenceRevisionsOrphanPurger {
}
$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;
}
// Check if the parent field is valid.
elseif (!($parent_field_config = $this->entityTypeManager->getStorage('field_storage_config')->load("$parent_type.$parent_field_name"))) {
$status = static::PARENT_INVALID_DELETE;
}
// In case the parent field has no target revision ID key we can not be sure
// that this revision is not used anymore.
elseif (empty($parent_field_config->getSchema()['columns']['target_revision_id'])) {
$status = static::PARENT_INVALID_SKIP;
else {
$parent_field_definitions = $this->entityFieldManager->getFieldStorageDefinitions($parent_type);
if (!isset($parent_field_definitions[$parent_field_name])) {
$status = static::PARENT_INVALID_DELETE;
}
// In case the parent field has no target revision ID key we can not be
// sure that this revision is not used anymore.
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;
......
name: 'Entity Host Base Field ERR'
type: module
description: 'Entity host with base field definition using entity_reference_revisions.'
package: Testing
core_version_requirement: ^8.7.7 || ^9
dependencies:
- entity_composite_relationship_test:entity_composite_relationship_test
administer entity_test host:
title: administer entity_test host
<?php
namespace Drupal\entity_host_relationship_test\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\entity_test\Entity\EntityTestMulRev;
/**
* Defines the test entity class.
*
* @ContentEntityType(
* id = "entity_host_relationship_test",
* label = @Translation("Test entity host"),
* base_table = "entity_test_host",
* revision_table = "entity_test_host_revision",
* data_table = "entity_test_host_field_data",
* revision_data_table = "entity_test_host_field_revision",
* content_translation_ui_skip = TRUE,
* translatable = TRUE,
* admin_permission = "administer entity_test host",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "revision" = "revision_id",
* "bundle" = "type",
* "label" = "name",
* "langcode" = "langcode",
* }
* )
*/
class EntityTestHostRelationship extends EntityTestMulRev implements RevisionableInterface {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['entity'] = BaseFieldDefinition::create('entity_reference_revisions')
->setLabel(t('Entity test composite'))
->setRevisionable(TRUE)
->setSetting('target_type', 'entity_test_composite')
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
return $fields;
}
}
<?php
namespace Drupal\Tests\entity_reference_revisions\Functional;
use Drupal\entity_composite_relationship_test\Entity\EntityTestCompositeRelationship;
use Drupal\entity_host_relationship_test\Entity\EntityTestHostRelationship;
/**
* Tests orphan composite revisions are properly removed.
*
* @group entity_reference_revisions
*/
class EntityReferenceRevisionsOrphanRemovalForBaseFieldDefinitionTest extends EntityReferenceRevisionsOrphanRemovalTest {
/**
* A user with administration access.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'node',
'field',
'entity_reference_revisions',
'entity_composite_relationship_test',
'entity_host_relationship_test',
];
/**
* {@inheritdoc}
*/
public function insertRevisionableData() {
/** @var \Drupal\node\NodeStorageInterface $entity_host_storage */
$entity_host_storage = \Drupal::entityTypeManager()->getStorage('entity_host_relationship_test');
// Scenario 1: A composite with a default revision that is referenced and an
// old revision that is not. Result: Only the old revision is deleted.
$composite_entity_first = EntityTestCompositeRelationship::create([
'name' => 'first not used, second used',
'parent_id' => 1000,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_first->save();
$composite_entity_first = EntityTestCompositeRelationship::load($composite_entity_first->id());
$composite_entity_first->setNewRevision(TRUE);
$composite_entity_first->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'First composite',
'entity' => $composite_entity_first,
]);
$entity_host->save();
// Scenario 2: A composite with an old revision that is used and a default
// revision that is not. Result: Nothing should be deleted.
$composite_entity_second = EntityTestCompositeRelationship::create([
'name' => 'first used, second not used',
]);
$composite_entity_second->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Second composite',
'entity' => $composite_entity_second,
]);
$entity_host->save();
$entity_host = $this->getEntityHostByName('Second composite');
$entity_host = $entity_host_storage->createRevision($entity_host);
$entity_host->set('entity', NULL);
$entity_host->save();
$composite_entity_second = EntityTestCompositeRelationship::load($composite_entity_second->id());
$composite_entity_second->setNewRevision(TRUE);
$composite_entity_second->save();
// Scenario 3: A composite with an old revision and a default revision both
// that are not used with empty parent fields. Result: Nothing should be
// deleted since we do not know if it is still used.
$composite_entity_third = EntityTestCompositeRelationship::create([
'name' => 'first not used, second not used',
]);
$composite_entity_third->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
// Scenario 4: A composite with an old revision and a default revision both
// that are not used with filled parent fields. Result: Should first delete
// the old revision and then the default revision. Delete the entity too.
$composite_entity_fourth = EntityTestCompositeRelationship::create([
'name' => '1st filled not, 2nd filled not',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_fourth->save();
$composite_entity_fourth = EntityTestCompositeRelationship::load($composite_entity_fourth->id());
$composite_entity_fourth->setNewRevision(TRUE);
$composite_entity_fourth->set('parent_id', 1001);
$composite_entity_fourth->save();
// Scenario 5: A composite with many revisions and 2 at least used. Result:
// Delete all unused revisions.
$composite_entity_fifth = EntityTestCompositeRelationship::create([
'name' => '1st not, 2nd used, 3rd not, 4th',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_fifth->save();
$composite_entity_fifth = EntityTestCompositeRelationship::load($composite_entity_fifth->id());
$composite_entity_fifth->setNewRevision(TRUE);
$composite_entity_fifth->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Third composite',
'entity' => $composite_entity_fifth,
]);
$entity_host->save();
$entity_host = $this->getEntityHostByName('Second composite');
$entity_host = $entity_host_storage->createRevision($entity_host);
$entity_host->set('entity', NULL);
$entity_host->save();
$composite_entity_fifth = EntityTestCompositeRelationship::load($composite_entity_fifth->id());
$composite_entity_fifth->setNewRevision(TRUE);
$composite_entity_fifth->save();
$entity_host = $this->getEntityHostByName('Third composite');
$entity_host = $entity_host_storage->createRevision($entity_host);
$entity_host->set('entity', $composite_entity_fifth);
$entity_host->save();
// Scenario 6: A composite with wrong parent fields filled pointing to a non
// existent parent (Parent 1). However, Parent 2 references it. Result: Must
// not be deleted.
$entity_host = EntityTestHostRelationship::create([
'name' => 'DELETED composite',
]);
$entity_host->save();
$composite_entity_sixth = EntityTestCompositeRelationship::create([
'name' => 'wrong parent fields',
'parent_id' => $entity_host->id(),
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_sixth->save();
$entity_host->delete();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Fourth composite',
'entity' => $composite_entity_sixth,
]);
$entity_host->save();
}
/**
* {@inheritdoc}
*/
public function insertNonRevisionableData() {
// Scenario 1: A composite with a default revision that is referenced and an
// old revision that is not. Result: Only the old revision is deleted.
$composite_entity_first = EntityTestCompositeRelationship::create([
'name' => 'NR first not used, second used',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_first->save();
$composite_entity_first = EntityTestCompositeRelationship::load($composite_entity_first->id());
$composite_entity_first->setNewRevision(TRUE);
$composite_entity_first->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'First NR composite',
'entity' => $composite_entity_first,
]);
$entity_host->save();
// Scenario 2: A composite with an old revision that is used and a default
// revision that is not. Result: Nothing should be deleted.
$composite_entity_second = EntityTestCompositeRelationship::create([
'name' => 'NR first used, second not used',
]);
$composite_entity_second->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Second NR composite',
'entity' => $composite_entity_second,
]);
$entity_host->save();
$composite_entity_second = EntityTestCompositeRelationship::load($composite_entity_second->id());
$composite_entity_second->setNewRevision(TRUE);
$composite_entity_second->save();
// Scenario 3: A composite with many revisions and 2 at least used. Result:
// Delete all unused revisions.
$composite_entity_third = EntityTestCompositeRelationship::create([
'name' => 'NR 1st not, 2nd, 3rd not, 4th',
'parent_id' => 1001,
'parent_type' => 'entity_host_relationship_test',
'parent_field_name' => 'entity',
]);
$composite_entity_third->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
$entity_host = EntityTestHostRelationship::create([
'name' => 'Third NR composite',
'entity' => $composite_entity_third,
]);
$entity_host->save();
$entity_host = $this->getEntityHostByName('Third NR composite');
$entity_host->set('entity', NULL);
$entity_host->save();
$composite_entity_third = EntityTestCompositeRelationship::load($composite_entity_third->id());
$composite_entity_third->setNewRevision(TRUE);
$composite_entity_third->save();
$entity_host = $this->getEntityHostByName('Third NR composite');
$entity_host->set('entity', $composite_entity_third);
$entity_host->save();
}
/**
* Get an entity host from the database based on its name.
*
* @param string $name
* A entity name.
* @param bool $reset
* (optional) Whether to reset the entity cache.
*
* @return \Drupal\Core\Entity\RevisionableInterface
* A revisionable entity matching $name.
*/
protected function getEntityHostByName($name, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()->getStorage('entity_host_relationship_test')->resetCache();
}
$name = (string) $name;
/** @var \Drupal\Core\Entity\RevisionableInterface[] $entities */
$entities = \Drupal::entityTypeManager()
->getStorage('entity_host_relationship_test')
->loadByProperties(['name' => $name]);
// Load the first entity returned from the database.
return reset($entities);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment