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