From cdd398594b89a132c6ff41040865ff4baa186aab Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Wed, 22 Jan 2014 00:21:45 -0800
Subject: [PATCH] Issue #1892320 by damiankloip, linclark: Add deserialize for
 JSON/AJAX.

---
 .../serialization/Encoder/JsonEncoder.php     | 15 +++-
 .../Normalizer/EntityNormalizer.php           | 76 +++++++++++++++++++
 .../Normalizer/NormalizerBase.php             | 56 +++++++++++++-
 .../Tests/EntitySerializationTest.php         | 27 ++++++-
 .../serialization/serialization.services.yml  |  5 ++
 5 files changed, 169 insertions(+), 10 deletions(-)
 create mode 100644 core/modules/serialization/lib/Drupal/serialization/Normalizer/EntityNormalizer.php

diff --git a/core/modules/serialization/lib/Drupal/serialization/Encoder/JsonEncoder.php b/core/modules/serialization/lib/Drupal/serialization/Encoder/JsonEncoder.php
index 35898a749f06..c7bedb55095a 100644
--- a/core/modules/serialization/lib/Drupal/serialization/Encoder/JsonEncoder.php
+++ b/core/modules/serialization/lib/Drupal/serialization/Encoder/JsonEncoder.php
@@ -7,25 +7,34 @@
 
 namespace Drupal\serialization\Encoder;
 
+use Symfony\Component\Serializer\Encoder\DecoderInterface;
 use Symfony\Component\Serializer\Encoder\EncoderInterface;
 use Symfony\Component\Serializer\Encoder\JsonEncoder as BaseJsonEncoder;
 
 /**
  * Adds 'ajax to the supported content types of the JSON encoder'
  */
-class JsonEncoder extends BaseJsonEncoder implements EncoderInterface {
+class JsonEncoder extends BaseJsonEncoder implements EncoderInterface, DecoderInterface {
 
   /**
    * The formats that this Encoder supports.
    *
    * @var array
    */
-  static protected $format = array('json', 'ajax');
+  protected static $format = array('json', 'ajax');
 
   /**
-   * Overrides Symfony\Component\Serializer\Encoder\JsonEncoder::supportEncoding().
+   * {@inheritdoc}
    */
   public function supportsEncoding($format) {
     return in_array($format, static::$format);
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function supportsDecoding($format) {
+    return in_array($format, static::$format);
+  }
+
 }
diff --git a/core/modules/serialization/lib/Drupal/serialization/Normalizer/EntityNormalizer.php b/core/modules/serialization/lib/Drupal/serialization/Normalizer/EntityNormalizer.php
new file mode 100644
index 000000000000..2e433525d8f5
--- /dev/null
+++ b/core/modules/serialization/lib/Drupal/serialization/Normalizer/EntityNormalizer.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\serialization\Normalizer\EntityNormalizer.
+ */
+
+namespace Drupal\serialization\Normalizer;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
+
+/**
+ * Normalizes/denormalizes Drupal entity objects into an array structure.
+ */
+class EntityNormalizer extends NormalizerBase implements DenormalizerInterface {
+
+  /**
+   * The interface or class that this Normalizer supports.
+   *
+   * @var array
+   */
+  protected $supportedInterfaceOrClass = array('Drupal\Core\Entity\EntityInterface');
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs an EntityNormalizer object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+     * {@inheritdoc}
+     */
+  public function normalize($object, $format = NULL, array $context = array()) {
+    $attributes = array();
+    foreach ($object as $name => $field) {
+      $attributes[$name] = $this->serializer->normalize($field, $format);
+    }
+
+    return $attributes;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = array()) {
+    if (empty($context['entity_type'])) {
+      throw new UnexpectedValueException('Entity type parameter must be included in context.');
+    }
+
+    $entity_info = $this->entityManager->getDefinition($context['entity_type']);
+
+    // The bundle property behaves differently from other entity properties.
+    // i.e. the nested structure with a 'value' key does not work.
+    if ($entity_info->hasKey('bundle')) {
+      $bundle_key = $entity_info->getKey('bundle');
+      $type = $data[$bundle_key][0]['value'];
+      $data[$bundle_key] = $type;
+    }
+
+    return $this->entityManager->getStorageController($context['entity_type'])->create($data);
+  }
+
+}
diff --git a/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php b/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php
index 6b5b65d098c3..ae7eaaa3cdf5 100644
--- a/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php
+++ b/core/modules/serialization/lib/Drupal/serialization/Normalizer/NormalizerBase.php
@@ -18,15 +18,65 @@ abstract class NormalizerBase extends SerializerAwareNormalizer implements Norma
   /**
    * The interface or class that this Normalizer supports.
    *
-   * @var string
+   * @var string|array
    */
   protected $supportedInterfaceOrClass;
 
   /**
-   * Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::supportsNormalization().
+   * {@inheritdoc}
    */
   public function supportsNormalization($data, $format = NULL) {
-    return is_object($data) && (isset($this->supportedInterfaceOrClass) && ($data instanceof $this->supportedInterfaceOrClass));
+    // If we aren't dealing with an object or the format is not supported return
+    // now.
+    if (!is_object($data) || !$this->checkFormat($format)) {
+      return FALSE;
+    }
+
+    $supported = (array) $this->supportedInterfaceOrClass;
+
+    return (bool) array_filter($supported, function($name) use ($data) {
+      return $data instanceof $name;
+    });
+  }
+
+  /**
+   * Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::supportsDenormalization()
+   *
+   * This class doesn't implement DenormalizerInterface, but most of its child
+   * classes do, so this method is implemented at this level to reduce code
+   * duplication.
+   */
+  public function supportsDenormalization($data, $type, $format = NULL) {
+    // If the format is not supported return now.
+    if (!$this->checkFormat($format)) {
+      return FALSE;
+    }
+
+    $supported = (array) $this->supportedInterfaceOrClass;
+
+    $subclass_check = function($name) use ($type) {
+      return (class_exists($name) || interface_exists($name)) && is_subclass_of($type, $name, TRUE);
+    };
+
+    return in_array($type, $supported) || array_filter($supported, $subclass_check);
   }
 
+  /**
+   * Checks if the provided format is supported by this normalizer.
+   *
+   * @param string $format
+   *   The format to check.
+   *
+   * @return bool
+   *   TRUE if the format is supported, FALSE otherwise. If no format is
+   *   specified this will return TRUE.
+   */
+  protected function checkFormat($format = NULL) {
+    if (!isset($format) || !isset($this->format)) {
+      return TRUE;
+    }
+
+    return in_array($format, (array) $this->format);
+   }
+
 }
diff --git a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
index 262d0a658d8c..c3dc920bd2b8 100644
--- a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
+++ b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
@@ -8,11 +8,8 @@
 namespace Drupal\serialization\Tests;
 
 use Drupal\Core\Language\Language;
-use Drupal\serialization\Encoder\JsonEncoder;
-use Drupal\serialization\Normalizer\ComplexDataNormalizer;
-use Drupal\serialization\Normalizer\TypedDataNormalizer;
-use Drupal\simpletest\DrupalUnitTestBase;
 use Symfony\Component\Serializer\Serializer;
+use Drupal\Component\Utility\String;
 
 /**
  * Tests entity normalization and serialization of supported core formats.
@@ -40,6 +37,13 @@ class EntitySerializationTest extends NormalizerTestBase {
    */
   protected $serializer;
 
+  /**
+   * The class name of the test class.
+   *
+   * @var string
+   */
+  protected $entityClass = 'Drupal\entity_test\Entity\EntityTest';
+
   public static function getInfo() {
     return array(
       'name' => 'Entity serialization tests',
@@ -159,4 +163,19 @@ public function testSerialize() {
     $actual = $this->serializer->serialize($normalized, 'xml');
     $this->assertIdentical($actual, $expected);
   }
+
+  /**
+   * Tests denormalization of an entity.
+   */
+  public function testDenormalize() {
+    $normalized = $this->serializer->normalize($this->entity);
+
+    foreach (array('json', 'xml') as $type) {
+      $denormalized = $this->serializer->denormalize($normalized, $this->entityClass, $type, array('entity_type' => 'entity_test_mulrev'));
+      $this->assertTrue($denormalized instanceof $this->entityClass, String::format('Denormalized entity is an instance of @class', array('@class' => $this->entityClass)));
+      $this->assertIdentical($denormalized->entityType(), $this->entity->entityType(), 'Expected entity type found.');
+      $this->assertIdentical($denormalized->bundle(), $this->entity->bundle(), 'Expected entity bundle found.');
+      $this->assertIdentical($denormalized->uuid(), $this->entity->uuid(), 'Expected entity UUID found.');
+    }
+  }
 }
diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml
index 160d8b034a8e..a293a6bb4209 100644
--- a/core/modules/serialization/serialization.services.yml
+++ b/core/modules/serialization/serialization.services.yml
@@ -2,6 +2,11 @@ services:
   serializer:
     class: Symfony\Component\Serializer\Serializer
     arguments: [{  }, {  }]
+  serializer.normalizer.entity:
+    class: Drupal\serialization\Normalizer\EntityNormalizer
+    tags:
+      - { name: normalizer }
+    arguments: ['@entity.manager']
   serializer.normalizer.complex_data:
     class: Drupal\serialization\Normalizer\ComplexDataNormalizer
     tags:
-- 
GitLab