To support POST/PATCH with a format, the Normalizers must implement DenormalizerInterface.

Discussion about whether or not we should support POST/PATCH in hal+json should go in #1929632: Decide how to handle POST/PATCH when using HAL. This issue is simply for implementation in case we do decide to support it.

Proposed resolution

Add denormalizers for the Typed Data interfaces we are supporting.

Because the language is identified in the field item data, we must be able to translate in the FieldItemNormalizer. However, because the language can only be managed from the entity object, the entity object must be passed in. To do this, create the 'target_instance' for both the field and field item in the calling denormalizer (e.g. entity gets the next field object and passes it into FieldNormalzier using $context['target_instance']). Then, the FieldItemNormalizer can traverse the property path back up to the entity and create a new path to a translated field item.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Anonymous’s picture

Status: Active » Needs review
FileSize
44.25 KB
9.44 KB

This patch just gets a start, using the TypeLinkManager to figure out which bundle the type URI corresponds to and creating the corresponding entity. This is similar to the RDF Mapping Manager we were using in the JSON-LD case.

Anonymous’s picture

Added support for marking fields for deletion.

Anonymous’s picture

Last patch had a minor logic error in the test. Interdiff is from #1.

Anonymous’s picture

This patch adds support for deserializing regular fields. Because a field item is not in control of its own language, there is some really janky code.

This patch is not complete. I comment out some of the previous HAL tests and have debug messages instead of assertions for the test that isn't commented out. I just wanted to post for comparison, because I'm going to try something else in the next patch.

Anonymous’s picture

This moves the translation logic to the FieldItemNormalizer, since field items are what contain the language information. It also adds tests for text field denormalization.

Status: Needs review » Needs work

The last submitted patch, 1931976-05-hal-deserialize.patch, failed testing.

Anonymous’s picture

Status: Needs work » Needs review
FileSize
44.84 KB
Anonymous’s picture

This patch adds full deserialization support by adding the decoder and fixes a problem caused by entity_create automatically adding values for fields like UUID.

Anonymous’s picture

Reroll since info.yml got in.

Anonymous’s picture

Updated with a few changes from #1924220: Support serialization in hal+json and some comments.

Anonymous’s picture

Reroll now that HAL serialization is in. There are no outstanding dependencies for this patch.

klausi’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
@@ -0,0 +1,169 @@
+    try {
+      $this->container->get('serializer')->denormalize($data_with_invalid_type,   $this->entityClass, $this->format);

superfluous white space.

I did some manual testing with REST module and creating entity_test entities, which works. So I guess we can move forward here, RTBC.

Anonymous’s picture

I would like to see more review of the actual approach before this gets committed, especially since Larry raised concerns about it on the call, but I guess this can move forward if everyone else agrees.

webchick’s picture

Assigned: Unassigned » Crell

Crell, what say you?

Crell’s picture

Only the first comment is commit-blocking, probably. This all makes sense to the extent that I understand what all has been written. :-) I'm still behind in my understanding here.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
@@ -62,6 +63,63 @@ public function normalize($entity, $format = NULL, array $context = array()) {
+    $type_uri = $data['_links']['type']['href'];

I think we need to account for type being an array. There's no reason it should ever be multi-value, but from discussion on the HAL list it looks like we should account for *any* link being an array at any time.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php
@@ -53,6 +56,34 @@ public function normalize($field, $format = NULL, array $context = array()) {
+      $count = $field->isEmpty() ? 0 : $field->count();

If this is using the Countable interface in PHP, we should be able to do count($field), and defaulting to 0 should be handled internally there.

If that's not the case, then this patch can continue but then we need to go change Fields to use Countable properly. :-)

+++ b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
@@ -0,0 +1,169 @@
+    $denormalized = $this->container->get('serializer')->denormalize($data_with_valid_type, $this->entityClass, $this->format);
+    $this->assertEqual(get_class($denormalized),   $this->entityClass, 'Request with valid type results in creation of correct bundle.');

I'm probably late to this game and should have caught it earlier, but... Containers have no business in unit tests. Wire things up manually. Is this code not actually unit testable?

If there's some dependency that prevents it from being unit testable, we need @todos to mark that so we know how much work we have to do when we finally clean up those parts of core that prevent real unit tests.

+++ b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
@@ -0,0 +1,169 @@
+      $this->fail('Exception thrown when type is invalid.');

Shouldn't this have a negative message for WHY it failed, not what it would have said if it passed? I thought that was the convention.

webchick’s picture

Status: Reviewed & tested by the community » Needs work
effulgentsia’s picture

I might be out of date, but last I remember (from a discussion a month or so ago) was that we wanted to structure our regular json to be the same as our hal+json, just without the _links and _embedded sections. And for us to have full deserialization (including entity references, and lang on field items) working in regular json. Is that still the case? If so, how much overlap would there be between that code and the code here, and does it make sense to do the other one first, and have the code here extend that with just the part specific to _links (and possibly _embedded, though not sure if there's anything relevant to deserialization in _embedded, since it's a duplicate of what already exists in the "normal" part of the json)?

Anonymous’s picture

@Crell

I think we need to account for type being an array.

Good point, although we'll only be able to use one of them. I will just write it so that it chooses the first one it finds a match for.

If this is using the Countable interface in PHP, we should be able to do count($field), and defaulting to 0 should be handled internally there.

I could have sworn this failed one of the tests before, but I tried it again and it passes now at least.

Containers have no business in unit tests.

I believe other DrupalUnitTestBase tests use the container that the base class adds, I think that's how I found out about it. I'm not opposed to making this change, the only downside I see is that then the constructed Serializer can get out of sync with the Normalizers/Encoders defined in the Bundle (which happened once already in serialization.module).

Shouldn't this have a negative message for WHY it failed, not what it would have said if it passed? I thought that was the convention.

There's no mention of exceptions in the SimpleTest coding standard and there doesn't seem to be one convention. For example:

Same message for pass and fail:

  • FileTransferTest
  • TermFieldTest

Different message:

  • ContextPluginTest
  • TypedDataTest

However, I'm not opposed to changing it.

@effulgentsia

The divergence between the structuring of our regular JSON and hal+json was touched upon in the serialization issue. I would rather tackle hal+json first and consider whether to handle deserialization for regular JSON as a separate issue.

Anonymous’s picture

Status: Needs work » Needs review
FileSize
5.99 KB
26.17 KB

This patch fixes the first two of Crell's issues and a small code style issue. I still need to fix the second two.

Anonymous’s picture

This fixes the rest of the issues Crell pointed out.

effulgentsia’s picture

Status: Needs review » Needs work

I would rather tackle hal+json first and consider whether to handle deserialization for regular JSON as a separate issue.

Ok. That means a fair amount of the code this patch is adding within HAL module will eventually move somewhere more reusable, but that's fine.

Other than that, after spending some time with this patch, and Lin patiently explaining some HAL concepts to me, I think this looks great other than some nits. I don't consider any of these to be commit blockers (they can be cleaned up in a follow up), but CNW to see if Lin is willing to do one more reroll to address some of them.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
@@ -62,6 +63,58 @@ public function normalize($entity, $format = NULL, array $context = array()) {
+    // Get language. language_default() is used because otherwise the entity
+    // system cannot understand the langcode when working in the DrupalUnitTest
+    // environment and importing data without an explicit langcode.
+    $langcode = isset($data['langcode']) ? $data['langcode'][0]['value'] : language_default();

I think we want to use LANGUAGE_NOT_SPECIFIED here rather than language_default(). language_default() is the language the site should output things into by default, but here we're receiving input, and if the input hasn't specified the language, then that's what LANGUAGE_NOT_SPECIFIED is for. I think we can remove the comment about DrupalUnitTest, since proper language handling isn't just for unit testing.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
@@ -62,6 +63,58 @@ public function normalize($entity, $format = NULL, array $context = array()) {
+    $typed_data_ids = $this->getTypedDataIds($data['_links']['type']);

I don't like the term "typed data ids" for referring to things like 'entity_type' and 'bundle', but looks like we already have code in HEAD (RDF module) that does this, so we can punt that discussion to a follow up.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
@@ -62,6 +63,58 @@ public function normalize($entity, $format = NULL, array $context = array()) {
+    // Get links and remove from data array.
+    $links = $data['_links'];
+    unset($data['_links']);
+    // Get embedded resources and remove from data array.
+    $embedded = array();
+    if (isset($data['_embedded'])) {
+      $embedded = $data['_embedded'];
+      unset($data['_embedded']);
+    }

Can we expand the comments as to why we're removing them from $data? Especially for the _embedded one, let's add a @todo pointing to #1880424: Handle entity references on import.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
@@ -62,6 +63,58 @@ public function normalize($entity, $format = NULL, array $context = array()) {
+      $class = get_class($field);

Setting $class here overrides the function parameter of the same name. Not a functional problem, since we no longer use the original parameter, but confusing. Perhaps calling it $field_class would be clearer?

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
@@ -62,6 +63,58 @@ public function normalize($entity, $format = NULL, array $context = array()) {
+      // Pass in the empty field object as a target instance. Since the context
+      // is already prepared for the field, any data added to it is
+      // automatically added to the entity.
+      $context['target_instance'] = $field;
+      $this->serializer->denormalize($field_data, $class, $format, $context);

The name 'target_instance' tripped me up, because:
1) Seeing it next to $field made me think of what $instance is within Field API, which is *the configuration* of a field specific to a particular bundle. That's not what 'instance' is referring to here though: here it just means an object we want the denormalizer we're about to call to populate rather than instantiating a new one.
2) I'm not clear what 'target' is intending to convey. Entityreference module uses the term 'target' to refer to the referenced entity, but this code has nothing to do with entity references.

Therefore, how about simply $context['object']?

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php
@@ -38,4 +40,72 @@ public function normalize($field_item, $format = NULL, array $context = array())
+      if ($field_definition['translatable'] == TRUE) {

"== TRUE" is redundant and can be removed.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php
@@ -38,4 +40,72 @@ public function normalize($field_item, $format = NULL, array $context = array())
+        $field_item = $this->createTranslatedInstance($field_item, $langcode);

Can we rename this method to getTranslatedFieldItem()?

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php
@@ -38,4 +40,72 @@ public function normalize($field_item, $format = NULL, array $context = array())
+    $parent = $field_item->getParent();

Lets call this $field instead of $parent.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php
@@ -38,4 +40,72 @@ public function normalize($field_item, $format = NULL, array $context = array())
+    // Get the property path.
+    while (!method_exists($parent, 'getTranslation')) {
+      array_unshift($ancestors, $parent);
+      $parent = $parent->getParent();
+    }

I think it's ok for us to know Drupal's data model of entity/field/item, and replace this with simply $entity = $field->getParent(). However, if there's some reason we want flexibility to accommodate other data models here, then at least let's replace the method_exists() with instanceof. The interface is Drupal\Core\TypedData\TranslatableInterface.

+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php
@@ -36,6 +37,22 @@ public function supportsNormalization($data, $format = NULL) {
+      $target = new \ReflectionClass($type);
+      $supported = new \ReflectionClass($this->supportedInterfaceOrClass);
+      if ($supported->isInterface()) {
+        return $target->implementsInterface($this->supportedInterfaceOrClass);
+      }
+      else {
+        return ($target->getName() == $this->supportedInterfaceOrClass || $target->isSubclassOf($this->supportedInterfaceOrClass));
+      }

Drupal now requires PHP 5.3.10, so this can all be replaced with is_a($type, $this->supportedInterfaceOrClass, TRUE).

+++ b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
@@ -0,0 +1,185 @@
+    $this->assertTrue(!empty($no_field_value) && empty($empty_field_value), 'A field set to an empty array in the data is structured differently than an empty field.');

Why are we only testing $no_field_value for not being empty? What do we actually expect it to be?

Anonymous’s picture

I've started processing these comments, but will be headed to an event soon, so just responding to a little bit now.

I think we want to use LANGUAGE_NOT_SPECIFIED here rather than language_default(). language_default() is the language the site should output things into by default, but here we're receiving input, and if the input hasn't specified the language, then that's what LANGUAGE_NOT_SPECIFIED is for. I think we can remove the comment about DrupalUnitTest, since proper language handling isn't just for unit testing.

Using LANGUAGE_NOT_SPECIFIED makes it so that the call to $denormalized->get('field_test_translatable_text')->getValue() fails to return the correct value when running tests. To be honest, the intricacies of the interactions between the entity system, the multilingual system, and DrupalUnitTests are difficult for me to debug, so I don't know what is causing the underlying issue.

Can we expand the comments as to why we're removing them from $data?

Added comment.

Perhaps calling it $field_class would be clearer?

Good point, changed.

Therefore, how about simply $context['object']?

I see your point, but 'object' feels to generic to me. I'll think about it some more.

"== TRUE" is redundant and can be removed.

Fixed.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
2.72 KB
28.23 KB

Using LANGUAGE_NOT_SPECIFIED makes it so that the call to $denormalized->get('field_test_translatable_text')->getValue() fails

Fixed that. The problem is just one of consistency. If langcode isn't specified, then 'en' values are translations, not default language values. So we can either change the test to identify the resource as 'en', or we can remove 'en' from the data items we want to assert as nontranslated ones. I suppose in principle we should test both variations, but here, I just arbitrarily picked the latter.

Can you post a patch for the fixes you made in #22, and then I think the rest can be a follow up.

webchick’s picture

Priority: Normal » Major

My understanding is we can't call the REST web services stuff complete unless this is done, so marking this as major.

effulgentsia’s picture

Status: Needs review » Reviewed & tested by the community

This was RTBC already in #12. It has only improved since then. I'm ok with my nits from #21 being addressed in a follow up patch. Getting this in will help resume progress on #1880424: Handle entity references on import. Therefore, I suggest committing #23 and then setting this issue back to needs work with normal priority.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

It seems like the major portions of eff's review (namely the language handling stuff) were addressed here. Looks like lin meant to upload a patch in #22, but it didn't quite make it. In the interests of moving things along with web services, though, I think it makes sense to commit this and then push the rest (ha, get it?) of the more minor things to a follow-up issue.

Committed and pushed to 8.x. Thanks!

Anonymous’s picture

If langcode isn't specified, then 'en' values are translations, not default language values. So we can either change the test to identify the resource as 'en', or we can remove 'en' from the data items we want to assert as nontranslated ones. I suppose in principle we should test both variations, but here, I just arbitrarily picked the latter.

Doesn't this mean that we don't actually test that we can ingest the data that we export?

effulgentsia’s picture

Title: Support deserialization for hal+json » Support deserialization for hal+json (minor nit fixes)
Priority: Major » Minor
Status: Fixed » Reviewed & tested by the community
FileSize
1.96 KB

Here's @linclark's fixes from #22 extracted out of #1935538-17: Switch REST to default to HAL.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Well that's easy enough.

Committed and pushed to 8.x. Thanks!

effulgentsia’s picture

Title: Support deserialization for hal+json (minor nit fixes) » Support deserialization for hal+json (needs more language handling tests)
Assigned: Crell » effulgentsia
Priority: Minor » Normal
Status: Fixed » Needs work
Issue tags: +Needs tests

Thanks! Back to needs work for #27. That shouldn't be too hard: I'll try to post a patch here soon.

effulgentsia’s picture

Issue summary: View changes

proposed resolution.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

  • webchick committed 4b455af on 8.3.x
    Issue #1931976 by linclark, effulgentsia: Support deserialization for...
  • webchick committed f277d1d on 8.3.x
    Issue #1931976 follow-up by linclark, effulgentsia: Support...

  • webchick committed 4b455af on 8.3.x
    Issue #1931976 by linclark, effulgentsia: Support deserialization for...
  • webchick committed f277d1d on 8.3.x
    Issue #1931976 follow-up by linclark, effulgentsia: Support...

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

  • webchick committed 4b455af on 8.4.x
    Issue #1931976 by linclark, effulgentsia: Support deserialization for...
  • webchick committed f277d1d on 8.4.x
    Issue #1931976 follow-up by linclark, effulgentsia: Support...

  • webchick committed 4b455af on 8.4.x
    Issue #1931976 by linclark, effulgentsia: Support deserialization for...
  • webchick committed f277d1d on 8.4.x
    Issue #1931976 follow-up by linclark, effulgentsia: Support...

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Wim Leers’s picture

Sigh. This was filed against the wrong component. This was supposed to be a mandatory follow-up for #1924220: Support serialization in hal+json, but obviously it was never completed.

Because, yes, this is true:

Doesn't this mean that we don't actually test that we can ingest the data that we export?

It's very much broken. We're dealing with the fallout of this over at #2135829: [PP-1] EntityResource: translations support and #2904423: Translated field denormalization creates duplicate values.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev
Spokje’s picture

Project: Drupal core » Hypermedia Application Language (HAL)
Version: 9.3.x-dev » 1.0.x-dev
Component: hal.module » Code

The hal module has moved out of Drupal Core and into a Contrib Module.
Moving this issue to the Contrib Module queue.