diff --git a/src/Normalizer/EntityReferenceFieldNormalizer.php b/src/Normalizer/EntityReferenceFieldNormalizer.php index 247e82c..7795c37 100644 --- a/src/Normalizer/EntityReferenceFieldNormalizer.php +++ b/src/Normalizer/EntityReferenceFieldNormalizer.php @@ -85,17 +85,31 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal $cardinality = $definition ->getFieldStorageDefinition() ->getCardinality(); - $entity_list = array_filter($field->getIterator()->getArrayCopy(), function ($item) { + /** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem[] $entity_reference_item_list */ + $entity_reference_item_list = array_filter(iterator_to_array($field), function ($item) { return (bool) $item->get('entity')->getValue(); }); - $entity_list = array_map(function ($item) { + $entity_list_metadata = []; + $entity_list = []; + foreach ($entity_reference_item_list as $item) { + // Prepare a list of additional properties stored by the field. + $metadata = []; + /** @var \Drupal\Core\TypedData\TypedDataInterface[] $properties */ + $properties = $item->getProperties(); + foreach ($properties as $property_key => $property) { + if ($property_key !== $main_property) { + $metadata[$property_key] = $property->getValue(); + } + } + $entity_list_metadata[] = $metadata; + // Get the referenced entity. $entity = $item->get('entity')->getValue(); // And get the translation in the requested language. - return $this->entityRepository->getTranslationFromContext($entity); - }, $entity_list); + $entity_list[] = $this->entityRepository->getTranslationFromContext($entity); + } $entity_collection = new EntityCollection($entity_list); - $relationship = new Relationship($this->resourceTypeRepository, $field->getName(), $cardinality, $entity_collection, $field->getEntity(), $main_property); + $relationship = new Relationship($this->resourceTypeRepository, $field->getName(), $cardinality, $entity_collection, $field->getEntity(), $main_property, $entity_list_metadata); return $this->serializer->normalize($relationship, $format, $context); } diff --git a/src/Normalizer/Relationship.php b/src/Normalizer/Relationship.php index 2483b6d..9abc7cd 100644 --- a/src/Normalizer/Relationship.php +++ b/src/Normalizer/Relationship.php @@ -68,19 +68,23 @@ class Relationship implements AccessibleInterface { * The host entity. * @param string $target_key * The property name of the relationship id. + * @param array $entity_list_metadata + * An array of additional properties stored by the field and that will be + * added to the meta in the relationship. */ - public function __construct(ResourceTypeRepository $resource_type_repository, $field_name, $cardinality = FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, EntityCollection $entities, EntityInterface $host_entity, $target_key = 'target_id') { + public function __construct(ResourceTypeRepository $resource_type_repository, $field_name, $cardinality = FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, EntityCollection $entities, EntityInterface $host_entity, $target_key = 'target_id', array $entity_list_metadata = array()) { $this->resourceTypeRepository = $resource_type_repository; $this->propertyName = $field_name; $this->cardinality = $cardinality; $this->hostEntity = $host_entity; $this->items = []; - foreach ($entities as $entity) { + foreach ($entities as $key => $entity) { $this->items[] = new RelationshipItem( $resource_type_repository, $entity, $this, - $target_key + $target_key, + $entity_list_metadata[$key] ); } } diff --git a/src/Normalizer/RelationshipItem.php b/src/Normalizer/RelationshipItem.php index b10201e..9f8ecdb 100644 --- a/src/Normalizer/RelationshipItem.php +++ b/src/Normalizer/RelationshipItem.php @@ -39,6 +39,13 @@ class RelationshipItem { protected $parent; /** + * The list of metadata associated with this relationship item value. + * + * @var array + */ + protected $metadata; + + /** * Relationship item constructor. * * @param \Drupal\jsonapi\ResourceType\ResourceTypeRepository $resource_type_repository @@ -49,8 +56,10 @@ class RelationshipItem { * The parent of this item. * @param string $target_key * The key name of the target relationship. + * @param array $metadata + * The list of metadata associated with this relationship item value. */ - public function __construct(ResourceTypeRepository $resource_type_repository, EntityInterface $target_entity, Relationship $parent, $target_key = 'target_id') { + public function __construct(ResourceTypeRepository $resource_type_repository, EntityInterface $target_entity, Relationship $parent, $target_key = 'target_id', array $metadata = []) { $this->targetResourceType = $resource_type_repository->get( $target_entity->getEntityTypeId(), $target_entity->bundle() @@ -58,6 +67,7 @@ class RelationshipItem { $this->targetKey = $target_key; $this->targetEntity = $target_entity; $this->parent = $parent; + $this->metadata = $metadata; } /** @@ -86,7 +96,10 @@ class RelationshipItem { * @return string */ public function getValue() { - return [$this->targetKey => $this->getTargetEntity()->uuid()]; + return [ + 'target_uuid' => $this->getTargetEntity()->uuid(), + 'meta' => $this->metadata, + ]; } /** diff --git a/src/Normalizer/Value/RelationshipItemNormalizerValue.php b/src/Normalizer/Value/RelationshipItemNormalizerValue.php index 8955eaf..7a60273 100644 --- a/src/Normalizer/Value/RelationshipItemNormalizerValue.php +++ b/src/Normalizer/Value/RelationshipItemNormalizerValue.php @@ -39,10 +39,16 @@ class RelationshipItemNormalizerValue extends FieldItemNormalizerValue implement if (!$value = parent::rasterizeValue()) { return $value; } - return [ + $rasterized_value = [ 'type' => $this->resource->getTypeName(), - 'id' => $value, + 'id' => empty($value['target_uuid']) ? $value : $value['target_uuid'], ]; + + if (!empty($value['meta'])) { + $rasterized_value['meta'] = $value['meta']; + } + + return $rasterized_value; } /** diff --git a/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php index c50d165..d10d73a 100644 --- a/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php +++ b/tests/src/Kernel/Normalizer/JsonApiDocumentTopLevelNormalizerTest.php @@ -5,6 +5,7 @@ namespace Drupal\Tests\jsonapi\Kernel\Normalizer; use Drupal\Component\Serialization\Json; use Drupal\Core\Cache\Cache; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\file\Entity\File; use Drupal\jsonapi\ResourceType\ResourceType; use Drupal\jsonapi\LinkManager\LinkManager; use Drupal\jsonapi\Normalizer\JsonApiDocumentTopLevelNormalizer; @@ -14,6 +15,7 @@ use Drupal\node\Entity\NodeType; use Drupal\jsonapi\ResourceResponse; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; +use Drupal\Tests\image\Kernel\ImageFieldCreationTrait; use Drupal\Tests\jsonapi\Kernel\JsonapiKernelTestBase; use Drupal\user\Entity\Role; use Drupal\user\Entity\User; @@ -31,6 +33,8 @@ use Symfony\Component\Routing\Route; */ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { + use ImageFieldCreationTrait; + /** * {@inheritdoc} */ @@ -43,6 +47,8 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { 'taxonomy', 'text', 'user', + 'file', + 'image', ]; /** @@ -68,10 +74,12 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { $this->installEntitySchema('node'); $this->installEntitySchema('user'); $this->installEntitySchema('taxonomy_term'); + $this->installEntitySchema('file'); // Add the additional table schemas. $this->installSchema('system', ['sequences']); $this->installSchema('node', ['node_access']); $this->installSchema('user', ['users_data']); + $this->installSchema('file', ['file_usage']); $type = NodeType::create([ 'type' => 'article', ]); @@ -86,6 +94,9 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { ['target_bundles' => ['tags']], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED ); + + $this->createImageField('field_image', 'article'); + $this->user = User::create([ 'name' => 'user1', 'mail' => 'user@localhost', @@ -113,6 +124,12 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { $this->term1->save(); $this->term2->save(); + $this->file = File::create([ + 'uri' => 'public://example.png', + 'filename' => 'example.png', + ]); + $this->file->save(); + $this->node = Node::create([ 'title' => 'dummy_title', 'type' => 'article', @@ -121,6 +138,15 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { ['target_id' => $this->term1->id()], ['target_id' => $this->term2->id()], ], + 'field_image' => [ + [ + 'target_id' => $this->file->id(), + 'alt' => 'test alt', + 'title' => 'test title', + 'width' => 10, + 'height' => 11, + ], + ], ]); $this->node->save(); @@ -176,10 +202,10 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { list($request, $resource_type) = $this->generateProphecies('node', 'article'); $request->query = new ParameterBag([ 'fields' => [ - 'node--article' => 'title,type,uid,field_tags', + 'node--article' => 'title,type,uid,field_tags,field_image', 'user--user' => 'name', ], - 'include' => 'uid,field_tags', + 'include' => 'uid,field_tags,field_image', ]); $response = new ResourceResponse(); @@ -208,6 +234,12 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { ], ], $normalized['data']['relationships']['type']); $this->assertTrue(!isset($normalized['data']['attributes']['created'])); + $this->assertEquals([ + 'alt' => 'test alt', + 'title' => 'test title', + 'width' => 10, + 'height' => 11, + ], $normalized['data']['relationships']['field_image']['data']['meta']); $this->assertSame('node--article', $normalized['data']['type']); $this->assertEquals([ 'data' => [ @@ -231,7 +263,7 @@ class JsonApiDocumentTopLevelNormalizerTest extends JsonapiKernelTestBase { // Make sure that the cache tags for the includes and the requested entities // are bubbling as expected. $this->assertSame( - ['node:1', 'taxonomy_term:1', 'taxonomy_term:2'], + ['file:1', 'node:1', 'taxonomy_term:1', 'taxonomy_term:2'], $response->getCacheableMetadata()->getCacheTags() ); $this->assertSame(