Commit e2e68321 authored by Dries's avatar Dries

Issue #1838596 by linclark, klausi, effulgentsia: deserialize for JSON-LD.

parent 22fa7f4d
......@@ -7,7 +7,6 @@
namespace Drupal\jsonld;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
/**
......@@ -15,7 +14,7 @@
*
* Simply respond to JSON-LD requests using the JSON encoder.
*/
class JsonldEncoder extends JsonEncoder implements EncoderInterface {
class JsonldEncoder extends JsonEncoder {
/**
* The formats that this Encoder supports.
......@@ -25,15 +24,17 @@ class JsonldEncoder extends JsonEncoder implements EncoderInterface {
static protected $format = array('jsonld', 'drupal_jsonld');
/**
* Check whether the request is for JSON-LD.
*
* @param string $format
* The short name of the format returned by ContentNegotiation.
*
* @return bool
* Returns TRUE if the encoder can handle the request.
* Overrides \Symfony\Component\Serializer\Encoder\JsonEncoder::supportsEncoding()
*/
public function supportsEncoding($format) {
return in_array($format, static::$format);
}
/**
* Overrides \Symfony\Component\Serializer\Encoder\JsonEncoder::supportsDecoding()
*/
public function supportsDecoding($format) {
return in_array($format, static::$format);
}
}
......@@ -7,14 +7,20 @@
namespace Drupal\jsonld;
use Drupal\Core\Entity\EntityNG;
use Drupal\jsonld\JsonldNormalizerBase;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/**
* Converts the Drupal entity object structure to JSON-LD array structure.
*/
class JsonldEntityNormalizer extends JsonldNormalizerBase {
class JsonldEntityNormalizer extends JsonldNormalizerBase implements DenormalizerInterface {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected static $supportedInterfaceOrClass = 'Drupal\Core\Entity\EntityInterface';
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
......@@ -23,17 +29,103 @@ public function normalize($entity, $format = NULL) {
$entityWrapper = new JsonldEntityWrapper($entity, $format, $this->serializer);
$attributes = $entityWrapper->getProperties();
$attributes = array('@id' => $entityWrapper->getId()) + $attributes;
$attributes = array(
'@id' => $entityWrapper->getId(),
'@type' => $entityWrapper->getTypeUri(),
) + $attributes;
return $attributes;
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::supportsNormalization()
* Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
*/
public function supportsNormalization($data, $format = NULL) {
// @todo Switch to EntityInterface once all entity types are converted to
// EntityNG.
return parent::supportsNormalization($data, $format) && ($data instanceof EntityNG);
public function denormalize($data, $class, $format = null) {
if (!isset($data['@type'])) {
// @todo Throw an exception?
}
// Every bundle has a type, identified by URI. A schema which provides more
// information about the type can be requested from that URI. From that
// schema, the entity type and bundle name are extracted.
$typeUri = is_array($data['@type']) ? $data['@type'][0] : $data['@type'];
// @todo Instead of manually parsing the URI use an approach as discussed in
// http://drupal.org/node/1852812
$parts = explode('/', $typeUri);
$bundle = array_pop($parts);
$entity_type = array_pop($parts);
$values = array(
'type' => $bundle,
);
// If the data specifies a default language, use it to create the entity.
if (isset($data['langcode'])) {
$values['langcode'] = $data['langcode'][LANGUAGE_NOT_SPECIFIED][0]['value'];
}
// Otherwise, if the default language is not specified but there are
// translations of field values, explicitly set the entity's default
// language to the site's default language. This is required to enable
// field translation on this entity.
else if ($this->containsTranslation($data)) {
$values['langcode'] = language(LANGUAGE_TYPE_CONTENT)->langcode;
}
$entity = entity_create($entity_type, $values);
// For each attribute in the JSON-LD, add the values as fields to the newly
// created entity. It is assumed that the JSON attribute names are the same
// as the site's field names.
// @todo Possibly switch to URI expansion of attribute names.
foreach ($data as $fieldName => $incomingFieldValues) {
// Skip the JSON-LD specific terms, which start with '@'.
if ($fieldName[0] === '@') {
continue;
}
// Figure out the designated class for this field type, which is used by
// the Serializer to determine which Denormalizer to use.
// @todo Is there a better way to get the field type's associated class?
$fieldItemClass = get_class($entity->get($fieldName)->offsetGet(0));
// Iterate through the language keyed values and add them to the entity.
// The vnd.drupal.ld+json mime type will always use language keys, per
// http://drupal.org/node/1838700.
foreach ($incomingFieldValues as $langcode => $incomingFieldItems) {
$fieldValue = $this->serializer->denormalize($incomingFieldItems, $fieldItemClass, $format);
$entity->getTranslation($langcode)
->set($fieldName, $fieldValue);
}
}
return $entity;
}
/**
* Determine whether incoming data contains translated content.
*
* @param array $data
* The incoming data.
*
* @return bool
* Whether or not this data contains translated content.
*/
protected function containsTranslation($data) {
// Langcodes which do not represent a translation of the entity.
$defaultLangcodes = array(
LANGUAGE_DEFAULT,
LANGUAGE_NOT_SPECIFIED,
LANGUAGE_NOT_APPLICABLE,
language(LANGUAGE_TYPE_CONTENT)->langcode,
);
// Combine the langcodes from the field value keys in a single array.
$fieldLangcodes = array();
foreach ($data as $propertyName => $property) {
//@todo Once @context has been added, check whether this property
// corresponds to an annotation instead. This will allow us to support
// incoming data that doesn't use language annotations.
if ('@' !== $propertyName[0]) {
$fieldLangcodes += array_keys($property);
}
}
$translationLangcodes = array_diff($fieldLangcodes, $defaultLangcodes);
return !empty($translationLangcodes);
}
}
......@@ -7,14 +7,23 @@
namespace Drupal\jsonld;
use Drupal\Core\Entity\Field\Type\EntityReferenceItem;
use Drupal\Core\Entity\EntityNG;
use Drupal\jsonld\JsonldNormalizerBase;
use ReflectionClass;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/**
* Converts an EntityReferenceItem to a JSON-LD array structure.
*/
class JsonldEntityReferenceNormalizer extends JsonldNormalizerBase {
class JsonldEntityReferenceNormalizer extends JsonldNormalizerBase implements DenormalizerInterface {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected static $supportedInterfaceOrClass = 'Drupal\Core\Entity\Field\Type\EntityReferenceItem';
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
......@@ -32,10 +41,19 @@ public function normalize($object, $format = NULL) {
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::supportsNormalization()
* Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
*/
public function denormalize($data, $class, $format = null) {
// @todo Support denormalization for Entity Reference.
return array();
}
/**
* Overrides \Drupal\jsonld\JsonldNormalizerBase::supportsDenormalization()
*/
public function supportsNormalization($data, $format = NULL) {
return parent::supportsNormalization($data, $format) && ($data instanceof EntityReferenceItem);
public function supportsDenormalization($data, $type, $format = NULL) {
$reflection = new ReflectionClass($type);
return in_array($format, static::$format) && ($reflection->getName() == static::$supportedInterfaceOrClass || $reflection->isSubclassOf(static::$supportedInterfaceOrClass));
}
}
......@@ -11,6 +11,13 @@
/**
* Provide an interface for JsonldNormalizer to get required properties.
*
* @todo Eventually, this class should be removed. It allows both the
* EntityNormalizer and the EntityReferenceNormalizer to have access to the
* same functions. If an $options parameter is added to the serialize
* signature, as requested in https://github.com/symfony/symfony/pull/4938,
* then the EntityReferenceNormalizer could simply call
* EntityNormalizer::normalize(), passing in the referenced entity.
*/
class JsonldEntityWrapper {
......@@ -62,6 +69,18 @@ public function getId() {
return url($uri_info['path'], array('absolute' => TRUE));
}
/**
* Get the type URI.
*
* @todo update or remove this method once the schema dependency to RDF module
* is sorted out.
*/
public function getTypeUri() {
$entity_type = $this->entity->entityType();
$bundle = $this->entity->bundle();
return url('site-schema/content-staging/' . $entity_type . '/' . $bundle, array('absolute' => TRUE));
}
/**
* Get properties, excluding JSON-LD specific properties.
*
......
......@@ -10,11 +10,19 @@
use Drupal\Core\Entity\Field\FieldItemInterface;
use Drupal\jsonld\JsonldNormalizerBase;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/**
* Converts the Drupal entity object structure to JSON-LD array structure.
*/
class JsonldFieldItemNormalizer extends JsonldNormalizerBase {
class JsonldFieldItemNormalizer extends JsonldNormalizerBase implements DenormalizerInterface {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected static $supportedInterfaceOrClass = 'Drupal\Core\Entity\Field\FieldItemInterface';
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
......@@ -24,10 +32,11 @@ public function normalize($object, $format = NULL) {
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::supportsNormalization()
* Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
*/
public function supportsNormalization($data, $format = NULL) {
return parent::supportsNormalization($data, $format) && ($data instanceof FieldItemInterface);
public function denormalize($data, $class, $format = null) {
// For most fields, the field items array should simply be returned as is.
return $data;
}
}
......@@ -7,8 +7,7 @@
namespace Drupal\jsonld;
use Drupal\Core\Entity\EntityNG;
use Symfony\Component\Serializer\Exception\RuntimeException;
use ReflectionClass;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer;
......@@ -17,6 +16,13 @@
*/
abstract class JsonldNormalizerBase extends SerializerAwareNormalizer implements NormalizerInterface {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected static $supportedInterfaceOrClass;
/**
* The formats that this Normalizer supports.
*
......@@ -28,7 +34,18 @@ abstract class JsonldNormalizerBase extends SerializerAwareNormalizer implements
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
*/
public function supportsNormalization($data, $format = NULL) {
return is_object($data) && in_array($format, static::$format);
return is_object($data) && in_array($format, static::$format) && ($data instanceof static::$supportedInterfaceOrClass);
}
/**
* 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) {
$reflection = new ReflectionClass($type);
return in_array($format, static::$format) && $reflection->implementsInterface(static::$supportedInterfaceOrClass);
}
}
<?php
/**
* @file
* Definition of Drupal\jsonld\Tests\JsonldTestBase.
*/
namespace Drupal\jsonld\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\config\Tests\ConfigEntityTest;
/**
* Parent class for JSON-LD tests.
*/
abstract class JsonldNormalizerTestBase extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'entity_test');
/**
* Get the Entity ID.
*
* @param Drupal\Core\Entity\EntityNG $entity
* Entity to get URI for.
*
* @return string
* Return the entity URI.
*/
protected function getEntityId($entity) {
global $base_url;
$uriInfo = $entity->uri();
return $base_url . '/' . $uriInfo['path'];
}
}
......@@ -2,23 +2,30 @@
/**
* @file
* Definition of Drupal\jsonld\Tests\DrupalJsonldNormalizerTest.
* Contains Drupal\jsonld\Tests\NormalizeDenormalizeTest.
*/
namespace Drupal\jsonld\Tests;
use Drupal\config\Tests\ConfigEntityTest;
use Drupal\Core\Language\Language;
use Drupal\jsonld\JsonldEncoder;
use Drupal\jsonld\JsonldEntityNormalizer;
use Drupal\jsonld\JsonldEntityReferenceNormalizer;
use Drupal\jsonld\JsonldFieldItemNormalizer;
use Drupal\jsonld\Tests\JsonldNormalizerTestBase;
use Drupal\simpletest\WebTestBase;
use Symfony\Component\Serializer\Serializer;
/**
* Test the vendor specific JSON-LD normalizer.
*/
class DrupalJsonldNormalizerTest extends JsonldNormalizerTestBase {
class NormalizeDenormalizeTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'entity_test');
/**
* The format being tested.
......@@ -32,8 +39,8 @@ class DrupalJsonldNormalizerTest extends JsonldNormalizerTestBase {
public static function getInfo() {
return array(
'name' => 'vnd.drupal.ld+json Normalization',
'description' => "Test Drupal's vendor specific JSON-LD normalizer.",
'name' => 'Normalize/Denormalize Test',
'description' => "Test that entities can be normalized/denormalized in JSON-LD.",
'group' => 'JSON-LD',
);
}
......@@ -49,42 +56,21 @@ function setUp() {
'field_item' => new JsonldFieldItemNormalizer(),
'entity' => new JsonldEntityNormalizer(),
);
$serializer = new Serializer($this->normalizers);
$serializer = new Serializer($this->normalizers, array(new JsonldEncoder()));
$this->normalizers['entity']->setSerializer($serializer);
}
/**
* Tests the supportsNormalization function.
*/
public function testSupportsNormalization() {
$format = static::$format;
$supportedEntity = entity_create('entity_test', array());
$unsupportedEntity = new ConfigEntityTest();
$field = $supportedEntity->get('uuid');
$entityreferenceField = $supportedEntity->get('user_id');
// Supported entity.
$this->assertTrue($this->normalizers['entity']->supportsNormalization($supportedEntity, static::$format), "Entity normalization is supported for $format on content entities.");
// Unsupported entity.
$this->assertFalse($this->normalizers['entity']->supportsNormalization($unsupportedEntity, static::$format), "Normalization is not supported for other entity types.");
// Field item.
$this->assertTrue($this->normalizers['field_item']->supportsNormalization($field->offsetGet(0), static::$format), "Field item normalization is supported for $format.");
// Entity reference field item.
$this->assertTrue($this->normalizers['entityreference']->supportsNormalization($entityreferenceField->offsetGet(0), static::$format), "Entity reference field item normalization is supported for $format.");
}
/**
* Tests the normalize function.
*/
public function testNormalize() {
// Add German as a language.
$language = new Language(array(
'langcode' => 'de',
'name' => 'Deutsch',
));
language_save($language);
}
/**
* Tests the normalize function.
*/
public function testNormalize() {
// Create a German entity.
$values = array(
'langcode' => 'de',
......@@ -159,4 +145,51 @@ public function testNormalize() {
$this->assertEqual($normalized['field_test_text'], $expectedArray['field_test_text'], 'Field with properties is nested correctly.');
}
function testDenormalize() {
$incomingData = array(
'@type' => url('jsonld-test/content-staging/entity_test/entity_test', array('absolute' => TRUE)),
'name' => array(
'en' => array(
array(
'value' => $this->randomName(),
),
),
'de' => array(
array(
'value' => $this->randomName(),
),
),
),
'field_test_text' => array(
'und' => array(
array(
'value' => $this->randomName(),
'format' => 'full_html',
),
),
),
);
$entity = $this->normalizers['entity']->denormalize($incomingData, 'Drupal\Core\Entity\EntityNG', static::$format);
$this->assertEqual('entity_test', $entity->bundle(), "Denormalize creates entity with correct bundle.");
$this->assertEqual($incomingData['name']['en'], $entity->get('name')->getValue(), "Translatable field denormalized correctly in default language.");
$this->assertEqual($incomingData['name']['de'], $entity->getTranslation('de')->get('name')->getValue(), "Translatable field denormalized correctly in translation language.");
$this->assertEqual($incomingData['field_test_text']['und'], $entity->get('field_test_text')->getValue(), "Untranslatable field denormalized correctly.");
}
/**
* Get the Entity ID.
*
* @param Drupal\Core\Entity\EntityNG $entity
* Entity to get URI for.
*
* @return string
* Return the entity URI.
*/
protected function getEntityId($entity) {
global $base_url;
$uriInfo = $entity->uri();
return $base_url . '/' . $uriInfo['path'];
}
}
<?php
/**
* @file
* Contains Drupal\jsonld\Tests\SupportsSerializationTest.
*/
namespace Drupal\jsonld\Tests;
use Drupal\config\Tests\ConfigEntityTest;
use Drupal\jsonld\JsonldEntityNormalizer;
use Drupal\jsonld\JsonldEntityReferenceNormalizer;
use Drupal\jsonld\JsonldFieldItemNormalizer;
use Drupal\simpletest\WebTestBase;
use Symfony\Component\Serializer\Serializer;
/**
* Test the vendor specific JSON-LD normalizer.
*/
class SupportsSerializationTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_test');
/**
* The format being tested.
*/
protected static $format = 'drupal_jsonld';
/**
* The Normalizers to be tested.
*/
protected $normalizers;
public static function getInfo() {
return array(
'name' => 'Supports class/format serialization test',
'description' => "Test that normalizers and encoders support expected classes and formats.",
'group' => 'JSON-LD',
);
}
/**
* Add the normalizer to be tested.
*/
function setUp() {
parent::setUp();
$this->normalizers = array(
'entityreference' => new JsonldEntityReferenceNormalizer(),
'field_item' => new JsonldFieldItemNormalizer(),
'entity' => new JsonldEntityNormalizer(),
);
$serializer = new Serializer($this->normalizers);
$this->normalizers['entity']->setSerializer($serializer);
}
/**
* Tests the supportsNormalization function.
*/
public function testSupportsNormalization() {
$format = static::$format;
$supportedEntity = entity_create('entity_test', array());
$unsupportedEntity = new ConfigEntityTest();
$field = $supportedEntity->get('uuid');
$entityreferenceField = $supportedEntity->get('user_id');
// Supported entity.
$this->assertTrue($this->normalizers['entity']->supportsNormalization($supportedEntity, static::$format), "Entity normalization is supported for $format on content entities.");
// Unsupported entity.
$this->assertFalse($this->normalizers['entity']->supportsNormalization($unsupportedEntity, static::$format), "Normalization is not supported for other entity types.");
// Field item.
$this->assertTrue($this->normalizers['field_item']->supportsNormalization($field->offsetGet(0), static::$format), "Field item normalization is supported for $format.");
// Entity reference field item.
$this->assertTrue($this->normalizers['entityreference']->supportsNormalization($entityreferenceField->offsetGet(0), static::$format), "Entity reference field item normalization is supported for $format.");
}
/**
* Tests the supportsDenormalization function.
*/
public function testSupportsDenormalization() {
$format = static::$format;
$data = array();
$supportedEntityClass = 'Drupal\Core\Entity\EntityNG';
$unsupportedEntityClass = 'Drupal\config\Tests\ConfigEntityTest';
$fieldClass = 'Drupal\Core\Entity\Field\Type\StringItem';
$entityreferenceFieldClass = 'Drupal\Core\Entity\Field\Type\EntityReferenceItem';
// Supported entity.
$this->assertTrue($this->normalizers['entity']->supportsDenormalization($data, $supportedEntityClass, static::$format), "Entity denormalization is supported for $format on content entities.");
// Unsupported entity.
$this->assertFalse($this->normalizers['entity']->supportsDenormalization($data, $unsupportedEntityClass, static::$format), "Denormalization is not supported for other entity types.");
// Field item.
$this->assertTrue($this->normalizers['field_item']->supportsDenormalization($data, $fieldClass, static::$format), "Field item denormalization is supported for $format.");
// Entity reference field item.
$this->assertTrue($this->normalizers['entityreference']->supportsDenormalization($data, $entityreferenceFieldClass, static::$format), "Entity reference field item denormalization is supported for $format.");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment