From ae5ae96e9ef3fad506e833cd820b9f5ecec58339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Mon, 17 Mar 2025 20:59:58 -0400 Subject: [PATCH 01/14] Initial implementation --- .../Plugin/ConfigAction/SetNestedProperty.php | 53 +++++++++++++++++++ .../ConfigAction/SimpleConfigUpdate.php | 25 ++++----- 2 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php new file mode 100644 index 000000000000..526fe414a32f --- /dev/null +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php @@ -0,0 +1,53 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Config\Action\Plugin\ConfigAction; + +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Config\Action\Attribute\ConfigAction; +use Drupal\Core\Config\Action\ConfigActionException; +use Drupal\Core\Config\Action\ConfigActionPluginInterface; +use Drupal\Core\Config\ConfigManagerInterface; +use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; + +#[ConfigAction( + id: 'setNested', + admin_label: new TranslatableMarkup('Set nested property'), + entity_types: ['*'], +)] +final class SetNestedProperty implements ConfigActionPluginInterface, ContainerFactoryPluginInterface { + + public function __construct( + private readonly ConfigManagerInterface $configManager, + ) {} + + /** + * {@inheritdoc} + */ + public function apply(string $configName, mixed $value): void { + $entity = $this->configManager->loadConfigEntityByName($configName); + assert($entity instanceof ConfigEntityInterface); + + assert(is_array($value)); + $values = array_is_list($value) ? $value : [$value]; + + foreach ($values as $value) { + $property_name = $value['property_name']; + assert(is_string($property_name)); + + // The property name has to be a nested property path. + if (!str_contains($property_name, '.')) { + throw new ConfigActionException("The setNested config action requires a nested property path."); + } + $parts = explode('.', $property_name); + $property_value = $entity->get($parts[0]) ?? []; + NestedArray::setValue($property_value, array_slice($parts, 1), $value['value']); + $entity->set($parts[0], $property_value); + } + $entity->save(); + } + +} diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php index 2e9064f3a3fc..a9e4afa40174 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php @@ -8,6 +8,7 @@ use Drupal\Core\Config\Action\ConfigActionException; use Drupal\Core\Config\Action\ConfigActionPluginInterface; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ConfigManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -22,31 +23,31 @@ )] final class SimpleConfigUpdate implements ConfigActionPluginInterface, ContainerFactoryPluginInterface { - /** - * Constructs a SimpleConfigUpdate object. - * - * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory - * The config factory. - */ public function __construct( - protected readonly ConfigFactoryInterface $configFactory, - ) { - } + private readonly ConfigFactoryInterface $configFactory, + private readonly ConfigManagerInterface $configManager, + ) {} /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { - return new static($container->get('config.factory')); + return new static( + $container->get(ConfigFactoryInterface::class), + $container->get(ConfigManagerInterface::class), + ); } /** * {@inheritdoc} */ public function apply(string $configName, mixed $value): void { + if ($this->configManager->getEntityTypeIdByName($configName)) { + // @todo Throw an exception. + @trigger_error('Using the simpleConfigUpdate config action is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://drupal.org/node/3439713.', E_USER_DEPRECATED); + } + $config = $this->configFactory->getEditable($configName); - // @todo https://www.drupal.org/i/3439713 Should we error if this is a - // config entity? if ($config->isNew()) { throw new ConfigActionException(sprintf('Config %s does not exist so can not be updated', $configName)); } -- GitLab From dada7532c51fd55367f2676efeb27238ef47b155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Mon, 17 Mar 2025 21:04:39 -0400 Subject: [PATCH 02/14] Throw if value is not an array --- .../Config/Action/Plugin/ConfigAction/SetNestedProperty.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php index 526fe414a32f..16b71d5ba45e 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php @@ -43,7 +43,12 @@ public function apply(string $configName, mixed $value): void { throw new ConfigActionException("The setNested config action requires a nested property path."); } $parts = explode('.', $property_name); + $property_value = $entity->get($parts[0]) ?? []; + if (!is_array($property_value)) { + throw new ConfigActionException('The setNested config action can only work on array values.'); + } + NestedArray::setValue($property_value, array_slice($parts, 1), $value['value']); $entity->set($parts[0], $property_value); } -- GitLab From c5d038846bca7ab28d354e2f23c1ea1fcbc6b9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Mon, 17 Mar 2025 21:34:43 -0400 Subject: [PATCH 03/14] Coding standards and coding STANdards, see what I did there? --- .../Action/Plugin/ConfigAction/SetNestedProperty.php | 10 ++++++++++ .../Action/Plugin/ConfigAction/SimpleConfigUpdate.php | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php index 16b71d5ba45e..00a694508a51 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php @@ -12,6 +12,7 @@ use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Symfony\Component\DependencyInjection\ContainerInterface; #[ConfigAction( id: 'setNested', @@ -24,6 +25,15 @@ public function __construct( private readonly ConfigManagerInterface $configManager, ) {} + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $container->get(ConfigManagerInterface::class), + ); + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php index a9e4afa40174..e216281079b0 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php @@ -44,7 +44,7 @@ public static function create(ContainerInterface $container, array $configuratio public function apply(string $configName, mixed $value): void { if ($this->configManager->getEntityTypeIdByName($configName)) { // @todo Throw an exception. - @trigger_error('Using the simpleConfigUpdate config action is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://drupal.org/node/3439713.', E_USER_DEPRECATED); + @trigger_error('Using the simpleConfigUpdate config action is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://www.drupal.org/node/3439713', E_USER_DEPRECATED); } $config = $this->configFactory->getEditable($configName); -- GitLab From f4ce51481dc6e0c2c856e35c495add89469a4acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Mar 2025 14:28:25 -0400 Subject: [PATCH 04/14] Rename the plugin and make it work more like simpleConfigUpdate --- .../Plugin/ConfigAction/SetNestedProperty.php | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php index 00a694508a51..8f169936323f 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php @@ -15,11 +15,11 @@ use Symfony\Component\DependencyInjection\ContainerInterface; #[ConfigAction( - id: 'setNested', - admin_label: new TranslatableMarkup('Set nested property'), + id: 'setProperties', + admin_label: new TranslatableMarkup('Set property of a config entity'), entity_types: ['*'], )] -final class SetNestedProperty implements ConfigActionPluginInterface, ContainerFactoryPluginInterface { +final class SetProperties implements ConfigActionPluginInterface, ContainerFactoryPluginInterface { public function __construct( private readonly ConfigManagerInterface $configManager, @@ -42,24 +42,21 @@ public function apply(string $configName, mixed $value): void { assert($entity instanceof ConfigEntityInterface); assert(is_array($value)); - $values = array_is_list($value) ? $value : [$value]; + assert(!array_is_list($value)); - foreach ($values as $value) { - $property_name = $value['property_name']; - assert(is_string($property_name)); - - // The property name has to be a nested property path. - if (!str_contains($property_name, '.')) { - throw new ConfigActionException("The setNested config action requires a nested property path."); - } + foreach ($values as $property_name => $value) { $parts = explode('.', $property_name); - $property_value = $entity->get($parts[0]) ?? []; - if (!is_array($property_value)) { - throw new ConfigActionException('The setNested config action can only work on array values.'); + $property_value = $entity->get($parts[0]); + if (count($parts) > 1) { + if (!is_array($property_value)) { + throw new ConfigActionException('This config action can only set nested values on arrays.'); + } + NestedArray::setValue($property_value, array_slice($parts, 1), $value['value']); + } + else { + $property_value = $value; } - - NestedArray::setValue($property_value, array_slice($parts, 1), $value['value']); $entity->set($parts[0], $property_value); } $entity->save(); -- GitLab From 03fe3b703c76deb35953e114f0fe80613fef1449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Mar 2025 16:21:38 -0400 Subject: [PATCH 05/14] Write a test, fix bugs --- ...etNestedProperty.php => SetProperties.php} | 11 ++-- .../Recipe/EntityMethodConfigActionsTest.php | 53 ++++++++++++++++++- 2 files changed, 58 insertions(+), 6 deletions(-) rename core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/{SetNestedProperty.php => SetProperties.php} (87%) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php similarity index 87% rename from core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php rename to core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php index 8f169936323f..9bef949576c3 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetNestedProperty.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php @@ -37,22 +37,23 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function apply(string $configName, mixed $value): void { + public function apply(string $configName, mixed $values): void { $entity = $this->configManager->loadConfigEntityByName($configName); assert($entity instanceof ConfigEntityInterface); - assert(is_array($value)); - assert(!array_is_list($value)); + assert(is_array($values)); + assert(!array_is_list($values)); foreach ($values as $property_name => $value) { $parts = explode('.', $property_name); $property_value = $entity->get($parts[0]); if (count($parts) > 1) { - if (!is_array($property_value)) { + if (isset($property_value) && !is_array($property_value)) { throw new ConfigActionException('This config action can only set nested values on arrays.'); } - NestedArray::setValue($property_value, array_slice($parts, 1), $value['value']); + $property_value ??= []; + NestedArray::setValue($property_value, array_slice($parts, 1), $value); } else { $property_value = $value; diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php index 8c08c3b8c848..60b9ebdbc848 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php @@ -4,21 +4,27 @@ namespace Drupal\KernelTests\Core\Recipe; +use Drupal\block\Entity\Block; +use Drupal\Core\Config\Action\ConfigActionException; use Drupal\Core\Config\Action\ConfigActionManager; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Extension\ThemeInstallerInterface; use Drupal\entity_test\Entity\EntityTestBundle; use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\block\Traits\BlockCreationTrait; /** * @group Recipe */ class EntityMethodConfigActionsTest extends KernelTestBase { + use BlockCreationTrait; + /** * {@inheritdoc} */ - protected static $modules = ['config_test', 'entity_test', 'system']; + protected static $modules = ['block', 'config_test', 'entity_test', 'system']; /** * The configuration action manager. @@ -172,4 +178,49 @@ public function testRemoveComponentFromDisplay(string $action_name): void { $this->assertFalse($this->configActionManager->hasDefinition($plugin_id)); } + /** + * Test setting a nested property on a config entity. + */ + public function testSetNestedProperty(): void { + $this->container->get(ThemeInstallerInterface::class) + ->install(['claro']); + $block = $this->placeBlock('local_tasks_block', ['theme' => 'claro']); + + $this->configActionManager->applyAction( + 'setProperties', + $block->getConfigDependencyName(), + ['settings.label' => 'Magic!'], + ); + $settings = Block::load($block->id())->get('settings'); + $this->assertSame('Magic!', $settings['label']); + + // If the property is not nested, it should still work. + $settings['label'] = 'Mundane'; + $this->configActionManager->applyAction( + 'setProperties', + $block->getConfigDependencyName(), + ['settings' => $settings], + ); + $settings = Block::load($block->id())->get('settings'); + $this->assertSame('Mundane', $settings['label']); + + // We can use this to set a scalar property normally. + $this->configActionManager->applyAction( + 'setProperties', + $block->getConfigDependencyName(), + ['region' => 'highlighted'], + ); + $this->assertSame('highlighted', Block::load($block->id())->getRegion()); + + // We should get an exception if we try to set a nested value on a property + // that isn't array. + $this->expectException(ConfigActionException::class); + $this->expectExceptionMessage('This config action can only set nested values on arrays.'); + $this->configActionManager->applyAction( + 'setProperties', + $block->getConfigDependencyName(), + ['theme.name' => 'stark'], + ); + } + } -- GitLab From 30acd99585ae272108a1d46551079090a95ff774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Mar 2025 16:26:34 -0400 Subject: [PATCH 06/14] Add a deprecation test --- .../Plugin/ConfigAction/SimpleConfigUpdate.php | 2 +- .../Recipe/EntityMethodConfigActionsTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php index e216281079b0..0d27ada654d7 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php @@ -44,7 +44,7 @@ public static function create(ContainerInterface $container, array $configuratio public function apply(string $configName, mixed $value): void { if ($this->configManager->getEntityTypeIdByName($configName)) { // @todo Throw an exception. - @trigger_error('Using the simpleConfigUpdate config action is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://www.drupal.org/node/3439713', E_USER_DEPRECATED); + @trigger_error('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://www.drupal.org/node/3439713', E_USER_DEPRECATED); } $config = $this->configFactory->getEditable($configName); diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php index 60b9ebdbc848..a7d140d345e7 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php @@ -223,4 +223,22 @@ public function testSetNestedProperty(): void { ); } + /** + * Tests that the simpleConfigUpdate action cannot be used on entities. + * + * @group legacy + */ + public function testSimpleConfigUpdateFailsOnEntities(): void { + $view_display = $this->container->get(EntityDisplayRepositoryInterface::class) + ->getViewDisplay('entity_test_with_bundle', 'test'); + $view_display->save(); + + $this->expectDeprecation('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://www.drupal.org/node/3439713'); + $this->configActionManager->applyAction( + 'simpleConfigUpdate', + $view_display->getConfigDependencyName(), + ['hidden.uid' => TRUE], + ); + } + } -- GitLab From f6cd98df859a80196c389ca6250deaec8eeb4bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Tue, 25 Mar 2025 16:31:55 -0400 Subject: [PATCH 07/14] Class doc comment. --- .../Core/Config/Action/Plugin/ConfigAction/SetProperties.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php index 9bef949576c3..d09e0437eec7 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php @@ -14,6 +14,10 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; +/** + * @internal + * This API is experimental. + */ #[ConfigAction( id: 'setProperties', admin_label: new TranslatableMarkup('Set property of a config entity'), -- GitLab From aca7e9fb4f596e18fc7c28e91faae54f4e1eeccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Mar 2025 11:03:58 -0400 Subject: [PATCH 08/14] Fix deprecations --- .../KernelTests/Core/Recipe/ConfigActionValidationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/ConfigActionValidationTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/ConfigActionValidationTest.php index e5df2b824b19..871e07757770 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/ConfigActionValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/ConfigActionValidationTest.php @@ -77,7 +77,7 @@ public function testConfigActionsAreValidated(string $entity_type_id): void { config: actions: $config_name: - simpleConfigUpdate: + setProperties: $label_key: '' YAML; -- GitLab From 887c9cc9e7cab1e260c02190ac3ce844ba13a103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Mar 2025 11:25:32 -0400 Subject: [PATCH 09/14] Update links --- .../Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php index 0d27ada654d7..209e435003d9 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php @@ -43,8 +43,8 @@ public static function create(ContainerInterface $container, array $configuratio */ public function apply(string $configName, mixed $value): void { if ($this->configManager->getEntityTypeIdByName($configName)) { - // @todo Throw an exception. - @trigger_error('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://www.drupal.org/node/3439713', E_USER_DEPRECATED); + // @todo Make this an exception in https://www.drupal.org/node/3515544. + @trigger_error('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setProperties action instead. See https://www.drupal.org/node/3515543', E_USER_DEPRECATED); } $config = $this->configFactory->getEditable($configName); -- GitLab From 3e5259d156ee11a7a0865d2296bfea2d1d8775de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Wed, 26 Mar 2025 11:40:10 -0400 Subject: [PATCH 10/14] Fix bad deprecation expectation --- .../KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php index a7d140d345e7..edb9c8584537 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php @@ -233,7 +233,7 @@ public function testSimpleConfigUpdateFailsOnEntities(): void { ->getViewDisplay('entity_test_with_bundle', 'test'); $view_display->save(); - $this->expectDeprecation('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setNested action instead. See https://www.drupal.org/node/3439713'); + $this->expectDeprecation('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setProperties action instead. See https://www.drupal.org/node/3515543'); $this->configActionManager->applyAction( 'simpleConfigUpdate', $view_display->getConfigDependencyName(), -- GitLab From f0c80e64fe3f12388257df956f15ee9a0df1beef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Thu, 27 Mar 2025 12:37:27 -0400 Subject: [PATCH 11/14] Fail on entity keys --- .../Config/Action/Plugin/ConfigAction/SetProperties.php | 4 ++++ .../Core/Recipe/EntityMethodConfigActionsTest.php | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php index d09e0437eec7..0c2c4f563eec 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php @@ -48,7 +48,11 @@ public function apply(string $configName, mixed $values): void { assert(is_array($values)); assert(!array_is_list($values)); + $entity_keys = $entity->getEntityType()->getKeys(); foreach ($values as $property_name => $value) { + if (in_array($property_value, $entity_keys, TRUE)) { + throw new ConfigActionException("Entity key '$property_name' cannot be changed."); + } $parts = explode('.', $property_name); $property_value = $entity->get($parts[0]); diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php index edb9c8584537..5c043c6ee2bb 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php @@ -223,6 +223,12 @@ public function testSetNestedProperty(): void { ); } + /** + * Tests that the setProperties action refuses to modify entity keys. + */ + public function testSetPropertiesWillNotChangeEntityKeys(): void { + } + /** * Tests that the simpleConfigUpdate action cannot be used on entities. * -- GitLab From a8e2620c2172c607dcc986246e4ca61e1284bf9c Mon Sep 17 00:00:00 2001 From: Eric Volkernick <eric@devcollaborative.com> Date: Sun, 30 Mar 2025 13:58:58 -0400 Subject: [PATCH 12/14] Prevent setting 'uuid' or 'id' with setProperties. --- .../Plugin/ConfigAction/SetProperties.php | 6 +++++- .../Recipe/EntityMethodConfigActionsTest.php | 20 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php index d09e0437eec7..c802fdd2b8d0 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php @@ -51,10 +51,14 @@ public function apply(string $configName, mixed $values): void { foreach ($values as $property_name => $value) { $parts = explode('.', $property_name); + if (in_array($parts[0], ['uuid', 'id'])) { + throw new ConfigActionException('The setProperties config action cannot set the UUID or ID of a config entity.'); + } + $property_value = $entity->get($parts[0]); if (count($parts) > 1) { if (isset($property_value) && !is_array($property_value)) { - throw new ConfigActionException('This config action can only set nested values on arrays.'); + throw new ConfigActionException('The setProperties config action can only set nested values on arrays.'); } $property_value ??= []; NestedArray::setValue($property_value, array_slice($parts, 1), $value); diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php index edb9c8584537..2834ecdb4ec9 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php @@ -213,14 +213,30 @@ public function testSetNestedProperty(): void { $this->assertSame('highlighted', Block::load($block->id())->getRegion()); // We should get an exception if we try to set a nested value on a property - // that isn't array. + // that isn't an array. $this->expectException(ConfigActionException::class); - $this->expectExceptionMessage('This config action can only set nested values on arrays.'); + $this->expectExceptionMessage('The setProperties config action can only set nested values on arrays.'); $this->configActionManager->applyAction( 'setProperties', $block->getConfigDependencyName(), ['theme.name' => 'stark'], ); + + // We should not be able to set uuid or id. + $this->expectException(ConfigActionException::class); + $this->expectExceptionMessage('The setProperties config action cannot set the UUID or ID of a config entity.'); + $this->configActionManager->applyAction( + 'setProperties', + $block->getConfigDependencyName(), + ['uuid' => '12345'], + ); + $this->expectException(ConfigActionException::class); + $this->expectExceptionMessage('The setProperties config action cannot set the UUID or ID of a config entity.'); + $this->configActionManager->applyAction( + 'setProperties', + $block->getConfigDependencyName(), + ['id' => '123'], + ); } /** -- GitLab From 82f2ad82d2f0b125b90f9020ffd8a11027bbc6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= <adam@phenaproxima.net> Date: Sun, 30 Mar 2025 17:48:28 -0400 Subject: [PATCH 13/14] Explicitly disallow ID and UUID generically --- .../Plugin/ConfigAction/SetProperties.php | 14 ++++---- .../Recipe/EntityMethodConfigActionsTest.php | 35 ++++++++++--------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php index 5073ff96f3f3..0ec3c7fc3253 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SetProperties.php @@ -48,17 +48,19 @@ public function apply(string $configName, mixed $values): void { assert(is_array($values)); assert(!array_is_list($values)); + // Don't allow the ID or UUID to be changed. $entity_keys = $entity->getEntityType()->getKeys(); + $forbidden_keys = array_filter([ + $entity_keys['id'], + $entity_keys['uuid'], + ]); + foreach ($values as $property_name => $value) { - if (in_array($property_value, $entity_keys, TRUE)) { - throw new ConfigActionException("Entity key '$property_name' cannot be changed."); + if (in_array($property_name, $forbidden_keys, TRUE)) { + throw new ConfigActionException("Entity key '$property_name' cannot be changed by the setProperties config action."); } $parts = explode('.', $property_name); - if (in_array($parts[0], ['uuid', 'id'])) { - throw new ConfigActionException('The setProperties config action cannot set the UUID or ID of a config entity.'); - } - $property_value = $entity->get($parts[0]); if (count($parts) > 1) { if (isset($property_value) && !is_array($property_value)) { diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php index 63e40b1b78e4..322cab854341 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php @@ -221,30 +221,31 @@ public function testSetNestedProperty(): void { $block->getConfigDependencyName(), ['theme.name' => 'stark'], ); + } + + /** + * Tests that the setProperties action refuses to modify entity IDs or UUIDs. + * + * @testWith ["id"] + * ["uuid"] + */ + public function testSetPropertiesWillNotChangeEntityKeys(string $key): void { + $view_display = $this->container->get(EntityDisplayRepositoryInterface::class) + ->getViewDisplay('entity_test_with_bundle', 'test'); + $this->assertFalse($view_display->isNew()); + + $property_name = $view_display->getEntityType()->getKey($key); + $this->assertNotEmpty($property_name); - // We should not be able to set uuid or id. - $this->expectException(ConfigActionException::class); - $this->expectExceptionMessage('The setProperties config action cannot set the UUID or ID of a config entity.'); - $this->configActionManager->applyAction( - 'setProperties', - $block->getConfigDependencyName(), - ['uuid' => '12345'], - ); $this->expectException(ConfigActionException::class); - $this->expectExceptionMessage('The setProperties config action cannot set the UUID or ID of a config entity.'); + $this->expectExceptionMessage("Entity key '$property_name' cannot be changed by the setProperties config action."); $this->configActionManager->applyAction( 'setProperties', - $block->getConfigDependencyName(), - ['id' => '123'], + $view_display->getConfigDependencyName(), + [$property_name => '12345'], ); } - /** - * Tests that the setProperties action refuses to modify entity keys. - */ - public function testSetPropertiesWillNotChangeEntityKeys(): void { - } - /** * Tests that the simpleConfigUpdate action cannot be used on entities. * -- GitLab From bc3650490de4f068cc30bd867a361357c4774328 Mon Sep 17 00:00:00 2001 From: Alex Pott <1732-alexpott@users.noreply.drupalcode.org> Date: Mon, 31 Mar 2025 08:33:24 +0000 Subject: [PATCH 14/14] Apply 2 suggestion(s) to 2 file(s) --- .../Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php | 2 +- .../KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php index 209e435003d9..8df05ae91c4d 100644 --- a/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php +++ b/core/lib/Drupal/Core/Config/Action/Plugin/ConfigAction/SimpleConfigUpdate.php @@ -44,7 +44,7 @@ public static function create(ContainerInterface $container, array $configuratio public function apply(string $configName, mixed $value): void { if ($this->configManager->getEntityTypeIdByName($configName)) { // @todo Make this an exception in https://www.drupal.org/node/3515544. - @trigger_error('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setProperties action instead. See https://www.drupal.org/node/3515543', E_USER_DEPRECATED); + @trigger_error('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and throws an exception in drupal:12.0.0. Use the setProperties action instead. See https://www.drupal.org/node/3515543', E_USER_DEPRECATED); } $config = $this->configFactory->getEditable($configName); diff --git a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php index 322cab854341..f1ae746ddd02 100644 --- a/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Recipe/EntityMethodConfigActionsTest.php @@ -256,7 +256,7 @@ public function testSimpleConfigUpdateFailsOnEntities(): void { ->getViewDisplay('entity_test_with_bundle', 'test'); $view_display->save(); - $this->expectDeprecation('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. Use the setProperties action instead. See https://www.drupal.org/node/3515543'); + $this->expectDeprecation('Using the simpleConfigUpdate config action on config entities is deprecated in drupal:11.2.0 and throws an exception in drupal:12.0.0. Use the setProperties action instead. See https://www.drupal.org/node/3515543'); $this->configActionManager->applyAction( 'simpleConfigUpdate', $view_display->getConfigDependencyName(), -- GitLab