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