diff --git a/src/Normalizer/ConfigEntityNormalizer.php b/src/Normalizer/ConfigEntityNormalizer.php index 420d502..b23f888 100644 --- a/src/Normalizer/ConfigEntityNormalizer.php +++ b/src/Normalizer/ConfigEntityNormalizer.php @@ -3,6 +3,8 @@ namespace Drupal\jsonapi\Normalizer; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\jsonapi\Relationship; +use Drupal\jsonapi\RelationshipInterface; use Symfony\Component\Serializer\Exception\UnexpectedValueException; /** @@ -26,7 +28,20 @@ class ConfigEntityNormalizer extends ContentEntityNormalizer { */ protected function getFields($entity, $bundle_id) { /* @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ - return $entity->toArray(); + $fields = $entity->toArray(); + + if (!$bundle_id) { + // Now create a relationship to the bundle level resource so we can + // include the rest of the fields. + $target_resource_config = $this->currentContext->getResourceManager() + ->get($entity->getEntityTypeId(), $entity->bundle()); + $field_name = 'bundle-resource'; + $fields[$field_name] = new Relationship($target_resource_config, $field_name, 1, [ + 'target_id' => $entity->id(), + ]); + $fields[$field_name]->setHostEntity($entity); + } + return $fields; } /** @@ -34,7 +49,9 @@ class ConfigEntityNormalizer extends ContentEntityNormalizer { */ protected function serializeField($field, $context, $format) { $output = $this->serializer->normalize($field, $format, $context); - $output->setPropertyType('attributes'); + $field instanceof RelationshipInterface ? + $output->setPropertyType('relationships') : + $output->setPropertyType('attributes'); return $output; } diff --git a/src/Normalizer/ContentEntityNormalizer.php b/src/Normalizer/ContentEntityNormalizer.php index aba4227..ece9a6a 100644 --- a/src/Normalizer/ContentEntityNormalizer.php +++ b/src/Normalizer/ContentEntityNormalizer.php @@ -4,6 +4,7 @@ namespace Drupal\jsonapi\Normalizer; use Drupal\Core\Access\AccessibleInterface; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\EntityReferenceFieldItemList; use Drupal\Core\Field\FieldItemListInterface; use Drupal\jsonapi\Configuration\ResourceConfigInterface; @@ -80,7 +81,7 @@ class ContentEntityNormalizer extends NormalizerBase implements DenormalizerInte if (!empty($context['sparse_fieldset'][$resource_type])) { $field_names = $context['sparse_fieldset'][$resource_type]; } - else { + else { $field_names = $this->getFieldNames($entity, $bundle_id); } /* @var Value\FieldNormalizerValueInterface[] $normalizer_values */ @@ -162,24 +163,9 @@ class ContentEntityNormalizer extends NormalizerBase implements DenormalizerInte protected function getFields($entity, $bundle_id) { /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $fields = $entity->getFields(); - if (!$bundle_id) { - // If this request if for the base entity, then strip all fields attached to a specific bundles. That way all the - // entity requests will look the same regardless of their bundle. If you need the information about the fields - // attached to the bundle, you can request the whole entity by using the bundle resource. As a shortcut, all - // entity resources contain a fake relationship to the bundle specific resource. - $fields = array_filter($fields, function (FieldItemListInterface $field) { - return (bool) !$field->getDataDefinition()->getTargetBundle(); - }); - // Now create a relationship to the bundle level resource so we can - // include the rest of the fields. - $target_resource_config = $this->currentContext->getResourceManager()->get($entity->getEntityTypeId(), $entity->bundle()); - $field_name = 'bundle-resource'; - $fields[$field_name] = new Relationship($target_resource_config, $field_name, 1, [ - 'target_id' => $entity->id(), - ]); - $fields[$field_name]->setHostEntity($entity); - } - return $fields; + return $bundle_id ? + $fields : + $this->getResourceBundleRelationshipFields($entity, $fields); } @@ -209,4 +195,36 @@ class ContentEntityNormalizer extends NormalizerBase implements DenormalizerInte return $output; } + /** + * Checks if the fields pertain to the bundle and removes them from the list. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The host entity + * @param array $fields + * The fields to check. + * + * @return array + * The list of fields keyed by field name. + */ + protected function getResourceBundleRelationshipFields(EntityInterface $entity, array $fields) { + // If this request if for the base entity, then strip all fields attached to a specific bundles. That way all the + // entity requests will look the same regardless of their bundle. If you need the information about the fields + // attached to the bundle, you can request the whole entity by using the bundle resource. As a shortcut, all + // entity resources contain a fake relationship to the bundle specific resource. + $fields = array_filter($fields, function (FieldItemListInterface $field) { + return (bool) !$field->getDataDefinition()->getTargetBundle(); + }); + // Now create a relationship to the bundle level resource so we can + // include the rest of the fields. + $target_resource_config = $this->currentContext->getResourceManager() + ->get($entity->getEntityTypeId(), $entity->bundle()); + $field_name = 'bundle-resource'; + $fields[$field_name] = new Relationship($target_resource_config, $field_name, 1, [ + 'target_id' => $entity->id(), + ]); + $fields[$field_name]->setHostEntity($entity); + + return $fields; + } + } diff --git a/tests/src/Kernel/Normalizer/DocumentRootNormalizerTest.php b/tests/src/Kernel/Normalizer/DocumentRootNormalizerTest.php index 6410437..d0d9964 100644 --- a/tests/src/Kernel/Normalizer/DocumentRootNormalizerTest.php +++ b/tests/src/Kernel/Normalizer/DocumentRootNormalizerTest.php @@ -186,7 +186,7 @@ class DocumentRootNormalizerTest extends KernelTestBase { $request = $this->prophesize(Request::class); $query = $this->prophesize(ParameterBag::class); $query->get('fields')->willReturn([]); - $query->get('include')->willReturn(NULL); + $query->get('include')->willReturn('bundle-resource'); $query->getIterator()->willReturn(new \ArrayIterator()); $request->query = $query->reveal(); $route = $this->prophesize(Route::class); @@ -234,6 +234,18 @@ class DocumentRootNormalizerTest extends KernelTestBase { $this->assertTrue(!isset($normalized['data']['attributes']['body'])); $this->assertTrue(!isset($normalized['data']['attributes']['field_tags'])); $this->assertSame('node', $normalized['data']['type']); + $this->assertSame([ + 'data' => [ + 'type' => 'node--article', + 'id' => '1', + ], + 'links' => [ + 'self' => 'dummy_entity_link', + 'related' => 'dummy_entity_link', + ], + ], $normalized['data']['relationships']['bundle-resource']); + $this->assertEquals('node--article', $normalized['included'][0]['data']['type']); + $this->assertEquals('1', $normalized['included'][0]['data']['id']); } /** @@ -280,6 +292,61 @@ class DocumentRootNormalizerTest extends KernelTestBase { } /** + * @covers ::normalize + */ + public function testNormalizeConfigNoBundle() { + $request = $this->prophesize(Request::class); + $query = $this->prophesize(ParameterBag::class); + $query->get('fields')->willReturn([ + 'node_type' => 'uuid,display_submitted', + ]); + $query->get('include')->willReturn('bundle-resource'); + $query->getIterator()->willReturn(new \ArrayIterator()); + $request->query = $query->reveal(); + $route = $this->prophesize(Route::class); + $route->getPath()->willReturn('/node_type/{node_type}'); + $route->getRequirement('_entity_type')->willReturn('node'); + $route->getRequirement('_bundle')->willReturn(NULL); + $request->get(RouteObjectInterface::ROUTE_OBJECT)->willReturn($route->reveal()); + $document_wrapper = $this->prophesize(DocumentWrapper::class); + $document_wrapper->getData()->willReturn($this->nodeType); + $resource_config = $this->prophesize(ResourceConfigInterface::CLASS); + $resource_config->getTypeName()->willReturn('node_type'); + $resource_config->getBundleId()->willReturn(NULL); + + // Make sure the route contains the entity type and bundle. + $current_context = $this->container->get('jsonapi.current_context'); + $current_context->setCurrentRoute($route->reveal()); + + $this->container->set('jsonapi.current_context', $current_context); + $this->container->get('serializer'); + $normalized = $this + ->container + ->get('serializer.normalizer.document_root.jsonapi') + ->normalize($document_wrapper->reveal(), 'api_json', [ + 'request' => $request->reveal(), + 'resource_config' => $resource_config->reveal(), + ]); + $this->assertTrue(empty($normalized['data']['attributes']['type'])); + $this->assertTrue(!empty($normalized['data']['attributes']['uuid'])); + $this->assertSame($normalized['data']['attributes']['display_submitted'], TRUE); + $this->assertSame($normalized['data']['id'], 'article'); + $this->assertSame($normalized['data']['type'], 'node_type'); + $this->assertSame([ + 'data' => [ + 'type' => 'node_type--node_type', + 'id' => 'article', + ], + 'links' => [ + 'self' => 'dummy_entity_link', + 'related' => 'dummy_entity_link', + ], + ], $normalized['data']['relationships']['bundle-resource']); + $this->assertEquals('node_type--node_type', $normalized['included'][0]['data']['type']); + $this->assertEquals('article', $normalized['included'][0]['data']['id']); + } + + /** * Try to POST a node and check if it exists afterwards. * * @covers ::denormalize