If you serialize a node and then try to de-serialize it, you get an error.

  // Test code.
  $options = array(
    'type' => 'node',
    'content_id' => 1,
  );
  // Get the serializer service.
  $serializer = \Drupal::service('serializer');
  $entity = entity_load($options['type'], $options['content_id']);
  $json = $serializer->serialize($entity, $format);
  $class = \Drupal::entityManager()->getDefinition($options['type'])->getClass();

    // Fail!
    $new_entity = $serializer->deserialize($json, $class, $format, array('entity_type'=>$options['type']));

Drupal\Core\Entity\EntityStorageException: Missing bundle for entity type node in Drupal\Core\Entity\ContentEntityStorageBase->doCreate() (line 63 of ../core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php).

CommentFileSizeAuthor
#56 interdiff-2451397-56.txt563 bytesdamiankloip
#56 2451397-56.patch17.18 KBdamiankloip
#53 interdiff-2451397-53.txt4.71 KBdamiankloip
#53 2451397-53.patch17.18 KBdamiankloip
#51 2451397-51.patch17.04 KBJaesin
#51 interdiff-2451397_47-51.txt2.46 KBJaesin
#48 2451397-47.patch17.11 KBJaesin
#48 interdiff_2451397_46-47.txt1.24 KBJaesin
#31 interdiff-2451397-22-31.txt2.82 KBJaesin
#31 2451397.31.patch4.86 KBJaesin
#22 2451397.22.patch4.54 KBJaesin
#22 interdiff-2451397-19-22.txt929 bytesJaesin
#1 drupal-use_target_id-2451397-1.patch714 bytesJaesin
#3 drupal-use_target_id-2451397-3.patch1.1 KBJaesin
#6 drupal-use_target_id-2451397-6.patch899 bytesJaesin
#7 2451397.7.patch3.21 KBalexpott
#11 interdiff-2451397-7-11.txt3.52 KBJaesin
#11 2451397.11.patch4.02 KBJaesin
#14 interdiff-2451397-11-14.txt1.53 KBJaesin
#14 2451397.14.patch4.47 KBJaesin
#16 interdiff-2451397-11-16.txt2.6 KBJaesin
#16 2451397.16.patch4.27 KBJaesin
#17 interdiff-2451397-16-17.txt583 bytesJaesin
#17 2451397-17-test-only.patch1.42 KBJaesin
#17 2451397.17.patch4.24 KBJaesin
#19 2451397.19.patch4.63 KBJaesin
#19 2451397-19-test-only.patch1.8 KBJaesin
#19 interdiff-2451397-17-19.txt1.89 KBJaesin
#33 31-33-interdiff.txt871 bytesalexpott
#33 2506533.33.patch4.79 KBalexpott
#35 2451397-35.patch11.59 KBdamiankloip
#35 interdiff-2451397-35.txt9.19 KBdamiankloip
#38 2451397-38.patch11.7 KBdamiankloip
#38 interdiff-2451397-38.txt1.29 KBdamiankloip
#46 2451397-46.patch16.86 KBJaesin
#46 test_only_2451397-46.patch13.11 KBJaesin
#46 interdiff_2451397_38-46.txt9.83 KBJaesin
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Jaesin’s picture

Status: Active » Needs review
FileSize
714 bytes

Get the bundle by target_id.

Status: Needs review » Needs work

The last submitted patch, 1: drupal-use_target_id-2451397-1.patch, failed testing.

Jaesin’s picture

FileSize
1.1 KB

Bundles are normalized as entity references unless they 'bundle_entity_type' is undefined or "bundle".

Jaesin’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 3: drupal-use_target_id-2451397-3.patch, failed testing.

Jaesin’s picture

Status: Needs work » Needs review
FileSize
899 bytes

Just get the bundle by "target_id" if it is set.

alexpott’s picture

FileSize
3.21 KB

After investigating a bit this appears to only be occurring with 'json' serialisation and not 'hal_json'. Patch with a test and a bit more tidy up.

Status: Needs review » Needs work

The last submitted patch, 7: 2451397.7.patch, failed testing.

Status: Needs work » Needs review

Jaesin queued 7: 2451397.7.patch for re-testing.

Status: Needs review » Needs work

The last submitted patch, 7: 2451397.7.patch, failed testing.

Jaesin’s picture

Status: Needs work » Needs review
FileSize
3.52 KB
4.02 KB

Clean up test and use context over class for denormalization.

Jaesin’s picture

Status: Needs review » Needs work

The last submitted patch, 11: 2451397.11.patch, failed testing.

Jaesin’s picture

Status: Needs work » Needs review
FileSize
1.53 KB
4.47 KB

Check for EntityTypeInterface in \Drupal\serialization\Normalizer\EntityNormalizer::denormalize

Status: Needs review » Needs work

The last submitted patch, 14: 2451397.14.patch, failed testing.

Jaesin’s picture

Status: Needs work » Needs review
FileSize
2.6 KB
4.27 KB

Throw an UnexpectedValueException when a valid entity type is unavailable for denormalization.

Jaesin’s picture

FileSize
583 bytes
1.42 KB
4.24 KB

Remove comment and add test only patch.

The last submitted patch, 17: 2451397-17-test-only.patch, failed testing.

Jaesin’s picture

FileSize
1.89 KB
1.8 KB
4.63 KB

My test changes were stripped, Re-add title assertion.

The last submitted patch, 19: 2451397-19-test-only.patch, failed testing.

alexpott’s picture

Status: Needs review » Needs work

The test looks good and just one more minor nit with the code...

+++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
@@ -43,22 +44,34 @@ public function __construct(EntityManagerInterface $entity_manager) {
+    if ($entity_type instanceof EntityTypeInterface && $entity_type->hasKey('bundle')) {

The instanceof check here is superfluous because we've just checked it above.

Jaesin’s picture

Status: Needs work » Needs review
FileSize
929 bytes
4.54 KB

It seems so obvious not that you mention it. ;)

Status: Needs review » Needs work

The last submitted patch, 22: 2451397.22.patch, failed testing.

Status: Needs work » Needs review

Jaesin queued 22: 2451397.22.patch for re-testing.

damiankloip’s picture

The only problem now is the _restSubmittedFields property.

Jaesin’s picture

alexpott’s picture

I think this is good to go and we have the followup #2456257: [PP-1] Normalizers must set $entity->_restSubmittedFields for entity POSTing/PATCHing to work to try to remove _restSubmittedFields which is not the fault of this patch. I can't rtbc since I contributed to the patch.

damiankloip’s picture

  1. +++ b/core/modules/rest/src/Tests/NodeTest.php
    @@ -90,4 +90,40 @@ public function testNodes() {
    +    $this->assertEqual($node->title->value, $title);
    

    We should check the type here too.

  2. +++ b/core/modules/rest/src/Tests/NodeTest.php
    @@ -90,4 +90,40 @@ public function testNodes() {
    +    $this->assertHeader('Location', $node->url('canonical', ['absolute' => TRUE]));
    

    This seems like odd behaviour - not a problem with this patch though.

  3. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -43,22 +44,34 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +    if (!$entity_type instanceof EntityTypeInterface) {
    

    I don't see a need to check the interface here, EntityManager::getDefinition won't return random objects. So this could just check emptiness? However, we currently won't get here anyway as EntityManager::getDefinition() will throw a PluginNotFoundException if the type is not found before here. Unless we add FALSE for the second ($exception_on_invalid) parameter.

  4. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -43,22 +44,34 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +      $data[$bundle_key] = !empty($data[$bundle_key][0]['target_id']) ? $data[$bundle_key][0]['target_id'] : $data[$bundle_key][0]['value'];
    

    This still seems kind of fragile to me, we should be able to have a solution using FieldItemInterface::mainPropertyName()?

  5. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -43,22 +44,34 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +    $entity->_restSubmittedFields = array_keys($data);
    

    If we are keeping this as part of this issue (sigh) then we at least need a @todo linking to the other issue :)

We also need to add to/amend the test coverage in EntityNormalizerTest probably?

damiankloip’s picture

Just took a look at the newer approach in #2456257: [PP-1] Normalizers must set $entity->_restSubmittedFields for entity POSTing/PATCHing to work, no more _restSubmittedFields - I love it!

damiankloip’s picture

Status: Needs review » Needs work
Jaesin’s picture

FileSize
2.82 KB
4.86 KB

This approach uses:

<?php
$this->entityManager
->getBaseFieldDefinitions($entity_type_id)[$bundle_key]
  ->getFieldStorageDefinition()
    ->getMainPropertyName()"
?>
Jaesin’s picture

Status: Needs work » Needs review
alexpott’s picture

+++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
@@ -43,22 +43,45 @@ public function __construct(EntityManagerInterface $entity_manager) {
+      if (!is_string($data[$bundle_key])) {
+        throw new UnexpectedValueException('A valid bundle is required for denormalization.');
+      }

Is this testable?

Patch fixes @todo.

Jaesin’s picture

This is to allow an entity to be normalized if the bundle it passed like.

{
  "title": "Foo", 
  "type": "article"
}

Instead of:

{
  "title": "Foo", 
  "type": ["article"]
}

Which would otherwise be required and doesn't make much sense considering only one bundle is acceptable.

damiankloip’s picture

We can test that in the unit tests, which need to be amended and extended anyway I think.

damiankloip’s picture

  1. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -61,17 +60,23 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
    -      $data[$bundle_key] = $data[$bundle_key][0][$key_id] ?: $data[$bundle_key];
    

    That would throw a notice.

  2. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -61,17 +60,23 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
    +      if (!is_string($data[$bundle_key]) || !in_array($data[$bundle_key], $bundle_types)) {
    

    Added checking against available bundles too. That also makes the exception message make a bit more sense.

Status: Needs review » Needs work

The last submitted patch, 35: 2451397-35.patch, failed testing.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
11.7 KB
1.29 KB
damiankloip’s picture

+++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
@@ -72,10 +72,11 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
+      $bundle_types = ($bundle_entity_type !== 'bundle') ? $this->entityManager->getStorage($bundle_entity_type)->getQuery()->execute() : [];

Core is doing that in other places too. Having the bundle entity type defaulting to 'bundle' seems....silly.

damiankloip’s picture

Title: denormalize fails to retrieve bundle » Entity denormalize fails to retrieve bundle
damiankloip’s picture

Title: Entity denormalize fails to retrieve bundle » Entity denormalization fails to retrieve bundle
Berdir’s picture

damiankloip’s picture

YES! That is very good, thanks Berdir!

damiankloip’s picture

Priority: Normal » Major

We can wait on that one now I think and tidy that part I don't like up a bit. Also,not being able to deserialze entities is bigger than 'normal'.

Jaesin’s picture

+++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
@@ -43,22 +43,51 @@ public function __construct(EntityManagerInterface $entity_manager) {
+      $bundle_types = ($bundle_entity_type !== 'bundle') ? $this->entityManager->getStorage($bundle_entity_type)->getQuery()->execute() : [];
+
+      // Make sure the bundle is a simple string.
+      if (!is_string($data[$bundle_key]) || ($bundle_types && !in_array($data[$bundle_key], $bundle_types))) {
+        throw new UnexpectedValueException(sprintf('"%s" is not a valid bundle type for denormalization.', $data[$bundle_key]));
+      }

I was going to let the entity storage handle validating the bundle but this is probably better. ContentEntityStorageBase::doCreate() only checks that a bundle is passed leaving the bundle validation to the field validator.

Jaesin’s picture

@damiankloip Thanks for the UnitTestCoverage.

A little more cleanup and additional simpletest coverage and a testonly patch.

The last submitted patch, 46: test_only_2451397-46.patch, failed testing.

Jaesin’s picture

damiankloip’s picture

  1. +++ b/core/modules/rest/src/Tests/NodeTest.php
    @@ -42,7 +42,13 @@ protected function enableNodeConfiguration($method, $operation) {
    +   * Serializes and post a node to create it.
    

    This line needs a tweak.

  2. +++ b/core/modules/rest/src/Tests/NodeTest.php
    @@ -50,9 +56,15 @@ protected function postNode($data) {
    +   * Testes the title on a newly created node.
    

    Tests*

Otherwise, those changes look ok to me.

damiankloip’s picture

+++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
@@ -43,22 +43,55 @@ public function __construct(EntityManagerInterface $entity_manager) {
+      $bundle_types = ($bundle_entity_type !== 'bundle') ? $this->entityManager->getStorage($bundle_entity_type)->getQuery()->execute() : [];

We can now simplify this again based on #2346857: Set default property bundle_entity_type in EntityType to NULL

Jaesin’s picture

Fixed spelling, simplified the $bundle_entity_type check as per #2346857: Set default property bundle_entity_type in EntityType to NULL and re-wrote the \Drupal\rest\Tests\NodeTest::postNode() docblock description.

dawehner’s picture

  1. +++ b/core/modules/rest/src/Tests/NodeTest.php
    @@ -90,4 +124,80 @@ public function testNodes() {
    +    // testing
    +    $this->postNode($data);
    

    That comment is a bit pointless

  2. +++ b/core/modules/rest/src/Tests/RESTTestBase.php
    @@ -388,4 +394,27 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
    +   * Check to see if the http request response body is identical to the expected
    

    Nitpick: HTTP instead of http

  3. +++ b/core/modules/rest/src/Tests/RESTTestBase.php
    @@ -388,4 +394,27 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
    +    return $this->assertIdentical($expected, $this->responseBody, $message ? $message : SafeMarkup::format('Response body @expected (expected) is equal to @response (actual).', array('@expected' => var_export($expected, TRUE), '@response' => var_export($this->responseBody, TRUE))), $group);
    

    Let's not introduce a new Safemarkup::format call but use strtr instead

  4. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -43,22 +43,54 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +      throw new UnexpectedValueException('A valid entity type is required for denormalization.');
    

    I'm curious whether we should be more explicit, so something like: The specified entity type $entity_type does not exist, but is required for denormalization.

  5. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -43,22 +43,54 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +      $key_id = 'value';
    +      // Get the ID key from the base field definition for the bundle key.
    +      if (isset($base_field_definitions[$bundle_key])) {
    +        $key_id = $base_field_definitions[$bundle_key]->getFieldStorageDefinition()->getMainPropertyName();
    +      }
    

    If we want, we could move that to a ternary as well

  6. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -43,22 +43,54 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +      $data[$bundle_key] = isset($data[$bundle_key][0][$key_id])
    +        ? $data[$bundle_key][0][$key_id]
    +        : (isset($data[$bundle_key]) ? $data[$bundle_key] : NULL);
    

    It is a bit odd to break this ternary into multiple lines

  7. +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php
    @@ -43,22 +43,54 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +      if (!is_string($data[$bundle_key]) || ($bundle_types && !in_array($data[$bundle_key], $bundle_types))) {
    +        throw new UnexpectedValueException(sprintf('"%s" is not a valid bundle type for denormalization.', $data[$bundle_key]));
    

    Do you mind splitting up the exception into one which makes it clear that the data does not contain bundle information + one that the specified bundle type does not exist?

damiankloip’s picture

FileSize
17.18 KB
4.71 KB

Thanks Daniel, here are some changes based on those comments.

Status: Needs review » Needs work

The last submitted patch, 53: 2451397-53.patch, failed testing.

The last submitted patch, 53: 2451397-53.patch, failed testing.

damiankloip’s picture

Status: Needs work » Needs review
FileSize
17.18 KB
563 bytes

Status: Needs review » Needs work

The last submitted patch, 56: 2451397-56.patch, failed testing.

damiankloip queued 56: 2451397-56.patch for re-testing.

damiankloip’s picture

Status: Needs work » Needs review
dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Thank you @Jaesin and @damiankloip

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

The patch has changed a lot since my last involvement. Committed fe81cfa and pushed to 8.0.x. Thanks!

diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 6a461ab..5e84827 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -411,7 +411,7 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) {
    *   translate this string. Defaults to 'Other'; most tests do not override
    *   this default.
    *
-   * @return
+   * @return bool
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertResponseBody($expected, $message = '', $group = 'REST Response') {

Fixed on commit.

  • alexpott committed fe81cfa on 8.0.x
    Issue #2451397 by Jaesin, damiankloip, alexpott, dawehner: Entity...
Jaesin’s picture

Status: Fixed » Closed (fixed)

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