Commit 9dc59dda authored by alexpott's avatar alexpott

Issue #2219795 by damiankloip, Berdir: Config entity references broken and other bugs.

parent 02f761ba
......@@ -16,10 +16,10 @@ services:
class: Drupal\hal\Normalizer\FileEntityNormalizer
tags:
- { name: normalizer, priority: 20 }
arguments: ['@entity.manager', '@http_default_client', '@rest.link_manager']
arguments: ['@entity.manager', '@http_default_client', '@rest.link_manager', '@module_handler']
serializer.normalizer.entity.hal:
class: Drupal\hal\Normalizer\EntityNormalizer
arguments: ['@rest.link_manager']
class: Drupal\hal\Normalizer\ContentEntityNormalizer
arguments: ['@rest.link_manager', '@entity.manager', '@module_handler']
tags:
- { name: normalizer, priority: 10 }
serializer.encoder.hal:
......
......@@ -2,12 +2,14 @@
/**
* @file
* Contains \Drupal\hal\Normalizer\EntityNormalizer.
* Contains \Drupal\hal\Normalizer\ContentEntityNormalizer.
*/
namespace Drupal\hal\Normalizer;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\Language;
use Drupal\rest\LinkManager\LinkManagerInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
......@@ -15,14 +17,14 @@
/**
* Converts the Drupal entity object structure to a HAL array structure.
*/
class EntityNormalizer extends NormalizerBase {
class ContentEntityNormalizer extends NormalizerBase {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected $supportedInterfaceOrClass = 'Drupal\Core\Entity\EntityInterface';
protected $supportedInterfaceOrClass = 'Drupal\Core\Entity\ContentEntityInterface';
/**
* The hypermedia link manager.
......@@ -32,20 +34,37 @@ class EntityNormalizer extends NormalizerBase {
protected $linkManager;
/**
* Constructs an EntityNormalizer object.
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs an ContentEntityNormalizer object.
*
* @param \Drupal\rest\LinkManager\LinkManagerInterface $link_manager
* The hypermedia link manager.
*/
public function __construct(LinkManagerInterface $link_manager) {
public function __construct(LinkManagerInterface $link_manager, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler) {
$this->linkManager = $link_manager;
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
*/
public function normalize($entity, $format = NULL, array $context = array()) {
// Create the array of normalized properties, starting with the URI.
// Create the array of normalized fields, starting with the URI.
/** @var $entity \Drupal\Core\Entity\ContentEntityInterface */
$normalized = array(
'_links' => array(
......@@ -58,24 +77,24 @@ public function normalize($entity, $format = NULL, array $context = array()) {
),
);
// If the properties to use were specified, only output those properties.
// Otherwise, output all properties except internal ID.
// If the fields to use were specified, only output those field values.
// Otherwise, output all field values except internal ID.
if (isset($context['included_fields'])) {
$properties = array();
foreach ($context['included_fields'] as $property_name) {
$properties[] = $entity->get($property_name);
$fields = array();
foreach ($context['included_fields'] as $field_name) {
$fields[] = $entity->get($field_name);
}
}
else {
$properties = $entity->getProperties();
$fields = $entity->getProperties();
}
foreach ($properties as $property) {
// In some cases, Entity API will return NULL array items. Ensure this is
// a real property and that it is not the internal id.
if (!is_object($property) || $property->getName() == 'id') {
// Ignore the entity ID and revision ID.
$exclude = array($entity->getEntityType()->getKey('id'), $entity->getEntityType()->getKey('revision'));
foreach ($fields as $field) {
if (in_array($field->getFieldDefinition()->getName(), $exclude)) {
continue;
}
$normalized_property = $this->serializer->normalize($property, $format, $context);
$normalized_property = $this->serializer->normalize($field, $format, $context);
$normalized = NestedArray::mergeDeep($normalized, $normalized_property);
}
......@@ -113,14 +132,24 @@ public function denormalize($data, $class, $format = NULL, array $context = arra
// Remove the langcode so it does not get iterated over below.
unset($data['langcode']);
}
elseif (\Drupal::moduleHandler()->moduleExists('language')) {
elseif ($this->moduleHandler->moduleExists('language')) {
$langcode = language_get_default_langcode($typed_data_ids['entity_type'], $typed_data_ids['bundle']);
}
else {
$langcode = Language::LANGCODE_NOT_SPECIFIED;
}
$entity = entity_create($typed_data_ids['entity_type'], array('langcode' => $langcode, 'type' => $typed_data_ids['bundle']));
$entity_type = $this->entityManager->getDefinition($typed_data_ids['entity_type']);
$values = array('langcode' => $langcode);
if ($entity_type->hasKey('bundle')) {
$bundle_key = $entity_type->getKey('bundle');
$values[$bundle_key] = $typed_data_ids['bundle'];
// Unset the bundle key from data, if it's there.
unset($data[$bundle_key]);
}
$entity = $this->entityManager->getStorageController($typed_data_ids['entity_type'])->create($values);
// Special handling for PATCH: destroy all possible default values that
// might have been set on entity creation. We want an "empty" entity that
......
......@@ -7,6 +7,7 @@
namespace Drupal\hal\Normalizer;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\rest\LinkManager\LinkManagerInterface;
use Drupal\serialization\EntityResolver\EntityResolverInterface;
use Drupal\serialization\EntityResolver\UuidReferenceInterface;
......@@ -57,6 +58,12 @@ public function normalize($field_item, $format = NULL, array $context = array())
/** @var $field_item \Drupal\Core\Field\FieldItemInterface */
$target_entity = $field_item->get('entity')->getValue();
// If this is not a content entity, let the parent implementation handle it,
// only content entities are supported as embedded resources.
if (!($target_entity instanceof ContentEntityInterface)) {
return parent::normalize($field_item, $format, $context);
}
// If the parent entity passed in a langcode, unset it before normalizing
// the target entity. Otherwise, untranslatable fields of the target entity
// will include the langcode.
......
......@@ -99,28 +99,19 @@ protected function constructValue($data, $context) {
* The translated field item instance.
*/
protected function createTranslatedInstance(FieldItemInterface $field_item, $langcode) {
$parent = $field_item->getParent();
$ancestors = array();
$field_items = $field_item->getParent();
// Remove the untranslated instance from the field's list of items.
$parent->offsetUnset($field_item->getName());
$field_items->offsetUnset($field_item->getName());
// Get the property path.
while (!method_exists($parent, 'getTranslation')) {
array_unshift($ancestors, $parent);
$parent = $parent->getParent();
}
// Recreate the property path with translations.
$translation = $parent->getTranslation($langcode);
foreach ($ancestors as $ancestor) {
$ancestor_name = $ancestor->getName();
$translation = $translation->get($ancestor_name);
}
// Get the entity in the requested language and the field's item list from
// that.
$entity_translation = $field_item->getEntity()->getTranslation($langcode);
$field_items_translation = $entity_translation->get($field_item->getFieldDefinition()->getName());
// Create a new instance at the end of the property path and return it.
$count = $translation->isEmpty() ? 0 : $translation->count();
return $translation->get($count);
// Create a new instance and return it.
$count = $field_items_translation->isEmpty() ? 0 : $field_items_translation->count();
return $field_items_translation->get($count);
}
}
......@@ -8,14 +8,14 @@
namespace Drupal\hal\Normalizer;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\file\Plugin\Core\Entity\File;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\rest\LinkManager\LinkManagerInterface;
use Guzzle\Http\ClientInterface;
/**
* Converts the Drupal entity object structure to a HAL array structure.
*/
class FileEntityNormalizer extends EntityNormalizer {
class FileEntityNormalizer extends ContentEntityNormalizer {
/**
* The interface or class that this Normalizer supports.
......@@ -24,13 +24,6 @@ class FileEntityNormalizer extends EntityNormalizer {
*/
protected $supportedInterfaceOrClass = 'Drupal\file\FileInterface';
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The HTTP client.
*
......@@ -47,11 +40,12 @@ class FileEntityNormalizer extends EntityNormalizer {
* The HTTP Client.
* @param \Drupal\rest\LinkManager\LinkManagerInterface $link_manager
* The hypermedia link manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(EntityManagerInterface $entity_manager, ClientInterface $http_client, LinkManagerInterface $link_manager) {
parent::__construct($link_manager);
public function __construct(EntityManagerInterface $entity_manager, ClientInterface $http_client, LinkManagerInterface $link_manager, ModuleHandlerInterface $module_handler) {
parent::__construct($link_manager, $entity_manager, $module_handler);
$this->entityManager = $entity_manager;
$this->httpClient = $http_client;
}
......
<?php
/**
* @file
* Contains \Drupal\hal\Tests\NormalizeTest.
*/
namespace Drupal\hal\Tests;
/**
* Test the HAL normalizer on various entities
*/
class EntityTest extends NormalizerTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'taxonomy');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Entity normalizer Test',
'description' => 'Test that nodes and terms are correctly normalized and denormalized.',
'group' => 'HAL',
);
}
/**
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
\Drupal::service('router.builder')->rebuild();
$this->installSchema('system', array('sequences'));
$this->installSchema('node', array('node', 'node_field_data', 'node_revision', 'node_field_revision'));
$this->installSchema('user', array('users_roles'));
$this->installSchema('taxonomy', array('taxonomy_term_data', 'taxonomy_term_hierarchy'));
}
/**
* Tests the normalization of nodes.
*/
public function testNode() {
$node_type = entity_create('node_type', array('type' => 'example_type'));
$node_type->save();
$user = entity_create('user', array('name' => $this->randomName()));
$user->save();
$node = entity_create('node', array(
'title' => $this->randomName(),
'uid' => $user->id(),
'type' => $node_type->id(),
'status' => NODE_PUBLISHED,
'promote' => 1,
'sticky' => 0,
'body' => array(
'value' => $this->randomName(),
'format' => $this->randomName(),
)
));
$node->save();
$original_values = $node->getPropertyValues();
unset($original_values['nid']);
unset($original_values['vid']);
$normalized = $this->serializer->normalize($node, $this->format);
$denormalized_node = $this->serializer->denormalize($normalized, 'Drupal\node\Entity\Node', $this->format);
// Verify that the ID and revision ID were skipped by the normalizer.
$this->assertEqual(NULL, $denormalized_node->id());
$this->assertEqual(NULL, $denormalized_node->getRevisionId());
// Loop over the remaining fields and verify that they are identical.
foreach ($original_values as $field_name => $field_values) {
$this->assertEqual($field_values, $denormalized_node->get($field_name)->getValue());
}
}
/**
* Tests the normalization of terms.
*/
public function testTerm() {
$vocabulary = entity_create('taxonomy_vocabulary', array('vid' => 'example_vocabulary'));
$vocabulary->save();
$term = entity_create('taxonomy_term', array(
'name' => $this->randomName(),
'vid' => $vocabulary->id(),
'description' => array(
'value' => $this->randomName(),
'format' => $this->randomName(),
)
));
$term->save();
$original_values = $term->getPropertyValues();
unset($original_values['tid']);
$normalized = $this->serializer->normalize($term, $this->format);
$denormalized_term = $this->serializer->denormalize($normalized, 'Drupal\taxonomy\Entity\Term', $this->format);
// Verify that the ID and revision ID were skipped by the normalizer.
$this->assertEqual(NULL, $denormalized_term->id());
// Loop over the remaining fields and verify that they are identical.
foreach ($original_values as $field_name => $field_values) {
$this->assertEqual($field_values, $denormalized_term->get($field_name)->getValue());
}
}
}
......@@ -10,13 +10,15 @@
use Drupal\Core\Cache\MemoryBackend;
use Drupal\Core\Language\Language;
use Drupal\hal\Encoder\JsonEncoder;
use Drupal\hal\Normalizer\EntityNormalizer;
use Drupal\hal\Normalizer\ContentEntityNormalizer;
use Drupal\hal\Normalizer\EntityReferenceItemNormalizer;
use Drupal\hal\Normalizer\FieldItemNormalizer;
use Drupal\hal\Normalizer\FieldNormalizer;
use Drupal\rest\LinkManager\LinkManager;
use Drupal\rest\LinkManager\RelationLinkManager;
use Drupal\rest\LinkManager\TypeLinkManager;
use Drupal\serialization\EntityResolver\ChainEntityResolver;
use Drupal\serialization\EntityResolver\TargetIdResolver;
use Drupal\serialization\EntityResolver\UuidResolver;
use Drupal\simpletest\DrupalUnitTestBase;
use Symfony\Component\Serializer\Serializer;
......@@ -120,12 +122,14 @@ function setUp() {
'bundle' => 'entity_test',
))->save();
$link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('cache')), new RelationLinkManager(new MemoryBackend('cache')));
$link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('cache')), new RelationLinkManager(new MemoryBackend('cache'), \Drupal::entityManager()));
$chain_resolver = new ChainEntityResolver(array(new UuidResolver(), new TargetIdResolver()));
// Set up the mock serializer.
$normalizers = array(
new EntityNormalizer($link_manager),
new EntityReferenceItemNormalizer($link_manager, new UuidResolver()),
new ContentEntityNormalizer($link_manager, \Drupal::entityManager(), \Drupal::moduleHandler()),
new EntityReferenceItemNormalizer($link_manager, $chain_resolver),
new FieldItemNormalizer(),
new FieldNormalizer(),
);
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityManagerInterface;
class RelationLinkManager implements RelationLinkManagerInterface{
......@@ -17,14 +18,24 @@ class RelationLinkManager implements RelationLinkManagerInterface{
*/
protected $cache;
/**
* Entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructor.
*
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* The cache of relation URIs and their associated Typed Data IDs.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(CacheBackendInterface $cache) {
public function __construct(CacheBackendInterface $cache, EntityManagerInterface $entity_manager) {
$this->cache = $cache;
$this->entityManager = $entity_manager;
}
/**
......@@ -75,20 +86,22 @@ public function getRelations() {
protected function writeCache() {
$data = array();
foreach (field_info_field_map() as $entity_type => $entity_type_map) {
foreach ($entity_type_map as $field_name => $field_info) {
foreach ($field_info['bundles'] as $bundle) {
$relation_uri = $this->getRelationUri($entity_type, $bundle, $field_name);
$data[$relation_uri] = array(
'entity_type' => $entity_type,
'bundle' => $bundle,
'field_name' => $field_name,
);
foreach ($this->entityManager->getDefinitions() as $entity_type) {
if ($entity_type->isFieldable()) {
foreach ($this->entityManager->getBundleInfo($entity_type->id()) as $bundle => $bundle_info) {
foreach ($this->entityManager->getFieldDefinitions($entity_type->id(), $bundle) as $field_definition) {
$relation_uri = $this->getRelationUri($entity_type->id(), $bundle, $field_definition->getName());
$data[$relation_uri] = array(
'entity_type' => $entity_type,
'bundle' => $bundle,
'field_name' => $field_definition->getName(),
);
}
}
}
}
// These URIs only change when field info changes, so cache it permanently
// and only clear it when field_info is cleared.
$this->cache->set('rest:links:relations', $data, Cache::PERMANENT, array('field_info' => TRUE));
// and only clear it when the fields cache is cleared.
$this->cache->set('rest:links:relations', $data, Cache::PERMANENT, array('entity_field_info' => TRUE));
}
}
......@@ -21,4 +21,4 @@ services:
arguments: ['@cache.cache']
rest.link_manager.relation:
class: Drupal\rest\LinkManager\RelationLinkManager
arguments: ['@cache.cache']
arguments: ['@cache.cache', '@entity.manager']
<?php
/**
* @file
* Contains \Drupal\serialization\EntityResolver\TargetIdResolver.
*/
namespace Drupal\serialization\EntityResolver;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Resolves entities from data that contains an entity target ID.
*/
class TargetIdResolver implements EntityResolverInterface {
/**
* {@inheritdoc}
*/
public function resolve(NormalizerInterface $normalizer, $data, $entity_type) {
if (isset($data['target_id'])) {
return $data['target_id'];
}
return NULL;
}
}
......@@ -39,3 +39,7 @@ services:
class: Drupal\serialization\EntityResolver\UuidResolver
tags:
- { name: entity_resolver}
serialization.entity_resolver.target_id:
class: Drupal\serialization\EntityResolver\TargetIdResolver
tags:
- { name: entity_resolver}
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