-
Notifications
You must be signed in to change notification settings - Fork 120
Add "Save entity" derivative #419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 8.x-3.x
Are you sure you want to change the base?
Changes from 3 commits
ed2a37f
95e0f49
9b52169
1714d7c
c2c4c01
ebc189d
6fb10ed
a46cfcc
ba09b7e
4b10458
5bcd4a1
47244f8
889098a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * @file | ||
| * Contains \Drupal\rules\Plugin\RulesAction\EntitySaveDeriver. | ||
| */ | ||
|
|
||
| namespace Drupal\rules\Plugin\RulesAction; | ||
|
|
||
| use Drupal\Component\Plugin\Derivative\DeriverBase; | ||
| use Drupal\Core\Entity\ContentEntityTypeInterface; | ||
| use Drupal\Core\Entity\EntityTypeManagerInterface; | ||
| use Drupal\Core\Entity\EntityFieldManagerInterface; | ||
| use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; | ||
| use Drupal\Core\StringTranslation\StringTranslationTrait; | ||
| use Drupal\Core\StringTranslation\TranslationInterface; | ||
| use Drupal\rules\Context\ContextDefinition; | ||
| use Symfony\Component\DependencyInjection\ContainerInterface; | ||
|
|
||
| /** | ||
| * Derives entity save plugin definitions based on content entity types. | ||
| * | ||
| * @see \Drupal\rules\Plugin\RulesAction\EntitySave | ||
| */ | ||
| class EntitySaveDeriver extends DeriverBase implements ContainerDeriverInterface { | ||
| use StringTranslationTrait; | ||
|
|
||
| /** | ||
| * The entity type manager. | ||
| * | ||
| * @var \Drupal\Core\Entity\EntityTypeManagerInterface | ||
| */ | ||
| protected $entityTypeManager; | ||
| /** | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing NL before this line. |
||
| * The entity field manager. | ||
| * | ||
| * @var \Drupal\Core\Entity\EntityFieldManagerInterface; | ||
| */ | ||
| protected $entityFieldManager; | ||
| /** | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing NL before this line. |
||
| * Saves a new EntitySaveDeriver object. | ||
| * | ||
| * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager | ||
| * The entity type manager. | ||
| * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation | ||
| * The string translation service. | ||
| */ | ||
| public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, TranslationInterface $string_translation) { | ||
| $this->entityTypeManager = $entity_type_manager; | ||
| $this->entityFieldManager = $entity_field_manager; | ||
| $this->stringTranslation = $string_translation; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public static function create(ContainerInterface $container, $base_plugin_id) { | ||
| return new static($container->get('entity_type.manager'), $container->get('entity_field.manager'), $container->get('string_translation')); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function getDerivativeDefinitions($base_plugin_definition) { | ||
| foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { | ||
| // Only allow content entities and ignore configuration entities. | ||
| if (!$entity_type instanceof ContentEntityTypeInterface) { | ||
| continue; | ||
| } | ||
|
|
||
| $this->derivatives[$entity_type_id] = [ | ||
| 'label' => $this->t('Save @entity_type', ['@entity_type' => $entity_type->getLowercaseLabel()]), | ||
| 'category' => $entity_type->getLabel(), | ||
| 'entity_type_id' => $entity_type_id, | ||
| 'context' => [], | ||
|
||
| 'provides' => [ | ||
| 'entity' => ContextDefinition::create("entity:$entity_type_id") | ||
| ->setLabel($entity_type->getLabel()) | ||
| ->setRequired(TRUE), | ||
| ], | ||
| ] + $base_plugin_definition; | ||
| // Add a required context for the bundle key, and optional contexts for | ||
| // other required base fields. This matches the storage create() behavior, | ||
| // where only the bundle requirement is enforced. | ||
| $bundle_key = $entity_type->getKey('bundle'); | ||
| $base_field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id); | ||
| foreach ($base_field_definitions as $field_name => $definition) { | ||
| if ($field_name != $bundle_key && !$definition->isRequired()) { | ||
| continue; | ||
| } | ||
|
|
||
| $is_bundle = ($field_name == $bundle_key); | ||
| $multiple = ($definition->getCardinality() === 1) ? FALSE : TRUE; | ||
| $context_definition = ContextDefinition::create($definition->getType()) | ||
| ->setLabel($definition->getLabel()) | ||
| ->setRequired($is_bundle) | ||
| ->setMultiple($multiple) | ||
| ->setDescription($definition->getDescription()); | ||
|
|
||
| if ($is_bundle) { | ||
| $context_definition->setAssignmentRestriction(ContextDefinition::ASSIGNMENT_RESTRICTION_INPUT); | ||
| } | ||
|
|
||
| $this->derivatives[$entity_type_id]['context'][$field_name] = $context_definition; | ||
| } | ||
| } | ||
|
|
||
| return $this->derivatives; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,31 +47,31 @@ public function setUp() { | |
| // and the mocked field definition is instantiated without the necessary | ||
| // information. | ||
| $bundle_field_definition->getCardinality()->willReturn(1) | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition->getType()->willReturn('string') | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition->getLabel()->willReturn('Bundle') | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition->getDescription() | ||
| ->willReturn('Bundle mock description') | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
|
|
||
| $bundle_field_definition_required->getCardinality()->willReturn(1) | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->getType()->willReturn('string') | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->getLabel()->willReturn('Required field') | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->getDescription() | ||
| ->willReturn('Required field mock description') | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is that used?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. The test does not work without these lines. All the comments in EntityCreateTest.php are unrelated. I only changed shouldBeCalledTimes(1) to shouldBeCalled() as you suggested during discussion. |
||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->isRequired() | ||
| ->willReturn(TRUE) | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if those are called 0 times, remove them.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. those are called 1 time |
||
|
|
||
| $bundle_field_definition_optional->isRequired() | ||
| ->willReturn(FALSE) | ||
| ->shouldBeCalledTimes(1); | ||
| ->shouldBeCalled(); | ||
|
|
||
| // Prepare mocked entity storage. | ||
| $entity_type_storage = $this->prophesize(EntityStorageBase::class); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,9 @@ | |
| namespace Drupal\Tests\rules\Integration\Action; | ||
|
|
||
| use Drupal\Core\Entity\EntityInterface; | ||
| use Drupal\Core\Entity\TypedData\EntityDataDefinition; | ||
| use Drupal\Core\Entity\EntityStorageBase; | ||
| use Drupal\Core\Field\BaseFieldDefinition; | ||
| use Drupal\Tests\rules\Integration\RulesEntityIntegrationTestBase; | ||
|
|
||
| /** | ||
|
|
@@ -30,6 +33,11 @@ class EntitySaveTest extends RulesEntityIntegrationTestBase { | |
| */ | ||
| protected $entity; | ||
|
|
||
| /** | ||
| * A constant that will be used instead of an entity. | ||
| */ | ||
| const ENTITY_REPLACEMENT = 'This is a fake entity'; | ||
|
||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
|
|
@@ -38,7 +46,63 @@ public function setUp() { | |
|
|
||
| $this->entity = $this->prophesizeEntity(EntityInterface::class); | ||
|
|
||
| $this->action = $this->actionManager->createInstance('rules_entity_save'); | ||
| // Prepare some mocked bundle field definitions. This is needed because | ||
| // EntityCreateDeriver adds required contexts for required fields, and | ||
| // assumes that the bundle field is required. | ||
| $entity_definition = $this->prophesize(EntityDataDefinition::class); | ||
| $bundle_field_definition = $this->prophesize(BaseFieldDefinition::class); | ||
| $bundle_field_definition_optional = $this->prophesize(BaseFieldDefinition::class); | ||
| $bundle_field_definition_required = $this->prophesize(BaseFieldDefinition::class); | ||
|
|
||
| // The next methods are mocked because EntitySaveDeriver executes them, | ||
| // and the mocked field definition is instantiated without the necessary | ||
| // information. | ||
| $bundle_field_definition->getCardinality()->willReturn(1) | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition->getType()->willReturn('string') | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition->getLabel()->willReturn('Bundle') | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition->getDescription() | ||
| ->willReturn('Bundle mock description') | ||
| ->shouldBeCalled(); | ||
|
|
||
| $bundle_field_definition_required->getCardinality()->willReturn(1) | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->getType()->willReturn('string') | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->getLabel()->willReturn('Required field') | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->getDescription() | ||
| ->willReturn('Required field mock description') | ||
| ->shouldBeCalled(); | ||
| $bundle_field_definition_required->isRequired() | ||
| ->willReturn(TRUE) | ||
| ->shouldBeCalled(); | ||
|
|
||
| $bundle_field_definition_optional->isRequired() | ||
| ->willReturn(FALSE) | ||
| ->shouldBeCalled(); | ||
|
|
||
| // Prepare mocked entity storage. | ||
| $entity_type_storage = $this->prophesize(EntityStorageBase::class); | ||
| $entity_type_storage->save(['entity' => $this->entity, 'bundle' => 'test', 'field_required' => NULL]) | ||
| ->willReturn(self::ENTITY_REPLACEMENT); | ||
|
|
||
| // Return the mocked storage controller. | ||
| $this->entityTypeManager->getStorage('test') | ||
| ->willReturn($entity_type_storage->reveal()); | ||
|
|
||
| // Return a mocked list of base fields definitions. | ||
| $this->entityFieldManager->getBaseFieldDefinitions('test') | ||
| ->willReturn([ | ||
| 'bundle' => $bundle_field_definition->reveal(), | ||
| 'field_required' => $bundle_field_definition_required->reveal(), | ||
| 'field_optional' => $bundle_field_definition_optional->reveal(), | ||
| ]); | ||
|
|
||
| // Instantiate the action we are testing. | ||
| $this->action = $this->actionManager->createInstance('rules_entity_save:test'); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -50,6 +114,23 @@ public function testSummary() { | |
| $this->assertEquals('Save entity', $this->action->summary()); | ||
| } | ||
|
|
||
| /** | ||
| * Tests the action execution. | ||
| * | ||
| * @covers ::execute | ||
| */ | ||
| public function testActionExecution() { | ||
| $this->entity->save()->shouldBeCalledTimes(1); | ||
|
|
||
| // @todo Exception: The entity context is not a valid context. | ||
| $this->action->setContextValue('entity', $this->entity->reveal()) | ||
|
||
| ->setContextValue('immediate', TRUE); | ||
|
|
||
| $this->action->execute(); | ||
| $entity = $this->action->getProvidedContext('entity')->getContextValue(); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why would entity save provide an entity? it doesn't afaik |
||
| $this->assertEquals(self::ENTITY_REPLACEMENT, $entity); | ||
|
||
| } | ||
|
|
||
| /** | ||
| * Tests the action execution when saving immediately. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure about this __construct()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems ok?