Problem/Motivation

For both #2869792: [meta] Add constraints to all config entity types and #2300677: JSON:API POST/PATCH support for fully validatable config entities it would be helpful to get a typed config respresentation for a given entity.
Currently you'd need to call $typedConfigManager->get($name) , which is tricky as:

  • Getting the name from is different from config and config entity case
  • It doesn't actually work for config entities, which haven't been saved yet

Possible solutions:

  • Add a ->typedData() method to both ConfigBase and ConfigEntityBase
  • Add methods to TypedConfigManager to return what is needed

Proposed resolution

For now I went with the second solution as I'd like to separate the idea of typed data from runtime representations of configuration.

Remaining tasks

User interface changes

None

API changes

Add \Drupal\Core\Config\TypedConfigManagerInterface::createFromNameAndData()

Data model changes

None

Comments

dawehner created an issue. See original summary.

dawehner’s picture

Status: Active » Needs review
StatusFileSize
new4.54 KB
wim leers’s picture

--- a/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
@@ -72,4 +73,25 @@ public function hasConfigSchema($name);
+  public function getTypedDataForConfig(ConfigBase $config);
...
+  public function getTypedDataForConfigEntity(ConfigEntityInterface $config_entity);

Is it okay to expand this interface? Is that not a BC break?

It's not marked @internal. Should it be?

dawehner’s picture

I can NOT imagine anyone in the entire universe to extend the typed config manager ... :)

dawehner’s picture

Technically I guess these two new methods are something like a TypedConfigFactory

+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -387,4 +388,22 @@ protected function alterDefinitions(&$definitions) {
+  /**
+   * {@inheritdoc}
+   */
+  public function getTypedDataForConfig(ConfigBase $config) {
+    $definition = $this->getDefinition($config->getName());
+    $data_definition = $this->buildDataDefinition($definition, $config->get());
+    return $this->create($data_definition, $config->get());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTypedDataForConfigEntity(ConfigEntityInterface $config_entity) {
+    $definition = $this->getDefinition($config_entity->getConfigDependencyName());
+    $data_definition = $this->buildDataDefinition($definition, $config_entity->toArray());
+    return $this->create($data_definition, $config_entity->toArray());
+  }

We could always introduce new static methods and pull $this from the container.

tim.plunkett’s picture

StatusFileSize
new5.57 KB
new2.98 KB

I went through the actual code base looking for places this could be used, and came up wanting.
Most callers of this pattern already have the config name and data split out.
Here's an example of my thinking.

Also I think the two new getFor* methods should be createFrom*, since the final call is a create.

dawehner’s picture

StatusFileSize
new5.8 KB
new4.88 KB

I think its a good idea to not expose the concept of simple config and config entities on this level, but also its in general a smaller API surface. Let's just use this single new method. I found another place where we could use it.

tim.plunkett’s picture

+1

I think \Drupal\locale\LocaleConfigManager::getTranslatableDefaultConfig() and \Drupal\Core\Config\TypedConfigManager::get() itself should also be changed, and then this is done.

dawehner’s picture

StatusFileSize
new7.23 KB
new1.69 KB

I think \Drupal\locale\LocaleConfigManager::getTranslatableDefaultConfig() and \Drupal\Core\Config\TypedConfigManager::get() itself should also be changed, and then this is done.

Nice finds ...

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

This looks great!
Thanks @dawehner

tstoeckler’s picture

Status: Reviewed & tested by the community » Needs review
  1. +++ b/core/modules/locale/src/LocaleConfigManager.php
    @@ -140,12 +140,8 @@ public function getTranslatableDefaultConfig($name) {
    -      $typed_config = $this->typedConfigManager->create($data_definition, $data);
    -      if ($typed_config instanceof TraversableTypedDataInterface) {
    -        return $this->getTranslatableData($typed_config);
    ...
    +      $typed_config = $this->typedConfigManager->createFromNameAndData($name, $data);
    +      return $this->getTranslatableData($typed_config);
    

    This seems like a behavior change. As this is only ever called with (top-level) config objects, this seems safe, but also kind of unnecessary to do here?

  2. So we already have the following in Drupal\Core\Entity\Entity:
      public function getTypedData() {
        if (!isset($this->typedData)) {
          $class = \Drupal::typedDataManager()->getDefinition('entity')['class'];
          $this->typedData = $class::createFromEntity($this);
        }
        return $this->typedData;
      }
    

    I like the method being introduced here, but shouldn't ConfigEntityBase somehow override this method? Or instead should we add a ConfigEntityAdapter or something like that? Or is that left for a followup?

dawehner’s picture

StatusFileSize
new7.14 KB
new771 bytes

Fabulous review tobias!

This seems like a behavior change. As this is only ever called with (top-level) config objects, this seems safe, but also kind of unnecessary to do here?

100% fair point. Let's keep the if around.

So we already have the following in Drupal\Core\Entity\Entity:

I personally consider it as a major flaw in the entity API that its typed data is exposed as a major primary API instead of rather an API in parallel to the main API.
I hope for config entities we can avoid casting them to typed data directly ... but well this is my person opinion of course.

Do you think we should keep this discussion for a followup?

tstoeckler’s picture

I'm fine with a follow-up for that. I don't have a strong opinion in either direction, but I think we should either deprecate EntityInterface::getTypedData() wholesale or fix it for ConfigEntityBase in that follow-up. Opening that follow-up should block this issue, though, IMO.

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

Thank you! Let's get this in, then.

dawehner’s picture

We are getting somewhere, nice!

@tstoeckler
Do you have any thoughts what we should do with validations on config save time?

tstoeckler’s picture

Hmm... that's a good question. I think in an ideal world my answer would be a different one, but since content entities also don't validate on save, I think we are well advised to follow suit for config entities. On the other hand, if I read the current code correctly we already pass $has_trusted_data = FALSE to Config::save() when saving configuration entities. So if we do constraint validation if $has_trusted_data is FALSE we would automatically trigger it for config entities by default. On the third hand I'm not sure if that wouldn't be considered a BC-break. On the fourth hand I guess currently no one will actually have constraints in their schema at the moment, so maybe we can get away with it? And also maybe we even if it's a slight BC break it's acceptable because the only thing that breaks is if people are saving invalid configuration, so it's good that we prevent it?

As you can see, not really sure...

wim leers’s picture

Agreed with #17. @tstoeckler++

I wonder what @alexpott's thoughts about this are.

dawehner’s picture

I 100% agree with tobias. There might be issues with migrations which historically have been really lazy with migrating 100% valid data but beside from that, I think adding more validation is just exactly what we want.

kristiaanvandeneynde’s picture

But since content entities also don't validate on save, I think we are well advised to follow suit for config entities

Just chiming in to point out that I'm actually trying to change that behavior to some extent: #2847319: Always enforce basefield and entity-level constraints

Let's not use a mistake we made in one place as an excuse to repeat it in another :) For all we know, we might fix the fact that content entities don't validate on save and then have a broken config entity save because we "just went along with it".

dawehner’s picture

Issue tags: +Baltimore2017
wim leers’s picture

Title: Make it easy to get typed config respresentations of entities » Make it easy to get typed config representations of entities
Priority: Normal » Major
wim leers’s picture

Issue tags: +API-First Initiative
effulgentsia’s picture

This patch looks pretty great to me. I still want to read the comments in this issue before committing to make sure I'm not missing anything. In the meantime, I noticed the following unused use statements. Just pointing them out in case they reflect something that should be in the patch but isn't.

+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -4,6 +4,7 @@
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
...
+++ b/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
@@ -2,6 +2,7 @@
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
...
+++ b/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
@@ -2,7 +2,9 @@
+use Drupal\config_test\Entity\ConfigTest;
alexpott’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

I think we should consider the TypedConfigManager largely as an internal object and certainly not an extension point. We're adding a method to its interface here. I think we should have a CR to tell people about the nee method.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -387,4 +386,13 @@ protected function alterDefinitions(&$definitions) {
+  /**
+   * {@inheritdoc}
+   */
+  public function createFromNameAndData($config_name, array $config_data) {
+    $definition = $this->getDefinition($config_name);
+    $data_definition = $this->buildDataDefinition($definition, $config_data);
+    return $this->create($data_definition, $config_data);
+  }

Theoretically we could avoid the new method by having a new static method which gets the typed config manager in as first method parameter. Funny enough you can actually call to protected/private functions in that static method, so it could be used even beyond the case of just public method calls (which is the case here). I do agree with @alexpott that the typed config manager is not meant to be an extension point in Drupal, but here is at least an alternative we could follow, even if its super ugly.

effulgentsia’s picture

Theoretically we could avoid the new method by having a new static method which gets the typed config manager in as first method parameter.

I don't think doing so is necessary, since adding methods to interfaces in minor releases is explicitly allowed for in our BC policy. However, a static method might actually make sense in this case, since it doesn't seam to me to have an implementation that would ever need swapping. In other words, if it truly is merely a utility wrapper around three other interface methods, then a static method kind of makes sense IMO, and is not "super ugly".

I'm ok with either approach though, so I leave it to those who've worked on this patch as to whether to update the patch to use a static method instead of an interface method, or whether to write a CR to inform people about the interface addition.

effulgentsia’s picture

I read through the issue comments and looks to me like all points are addressed (except per #28, doing one of either #26 or #27).

#17 hasn't been addressed, but that discussion is also taking place in #2869792: [meta] Add constraints to all config entity types, so if we need to open a new issue for it, I think it makes sense to do so as a related issue of that one, not this one.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new7.84 KB
new7.62 KB

Good point @effulgentsia. Given that, I would kinda prefer #12

Nevertheless, here is some experiment about my train of thoughts in #27. It works, but well, I'm not 100% convinced of it.

effulgentsia’s picture

+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -387,4 +386,23 @@ protected function alterDefinitions(&$definitions) {
+   * @return \Drupal\Core\TypedData\TraversableTypedDataInterface
+  public static function createFromNameAndData($config_name, array $config_data, TypedConfigManagerInterface $typed_config_manager = NULL) {

I think what I like least about #30 is that it's a static function with a name that starts with "create", but returns an instance of something entirely different than a typed config manager. I think all our other static "create" functions in core return an instance of the class they're on (or at least an instance of a very similar class).

If someone thinks of a more natural home for this static method, great, let's explore that.

Otherwise, I think I prefer #12 as well, despite it needing a CR.

effulgentsia’s picture

If someone thinks of a more natural home for this static method, great, let's explore that.

What about Drupal\Core\Config\Schema\Mapping::createFromNameAndData()? Or is there a case where createFromNameAndData() creates something other than a Mapping instance?

effulgentsia’s picture

Or maybe Drupal\Core\Config\Schema\ArrayElement instead of presuming it's a Mapping?

dawehner’s picture

Issue tags: -Needs change record

Conceptually this method would be usable anyway, but the practical usecase would be always a mapping. On the other hand the entire unification of typed config and typed schema might resort all those keys again.

Given that I really think we should go with #12. Note: I created a basic change record describing how it is useful and how you can implement this on your own implementation.

wim leers’s picture

#12 was RTBC'd by @tstoeckler in #15. Do we want to re-upload #12 and re-RTBC it?

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new7.14 KB

Here it is ...

wim leers’s picture

Ok, so we do. Great!

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 36: 2869809-36.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new6.58 KB
new2.12 KB
effulgentsia’s picture

Ticking credit box for @tstoeckler for his reviews.

dawehner’s picture

Thank you @effulgentsia!

@Wim Leers you have the honour, I guess :)

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

:)

alexpott’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: +8.4.0 release notes

Committed a9fe99c and pushed to 8.4.x. Thanks!

  • alexpott committed a9fe99c on 8.4.x
    Issue #2869809 by dawehner, effulgentsia, tim.plunkett, tstoeckler: Make...
wim leers’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.