View source
<?php
namespace Drupal\jsonapi\Normalizer;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\TypedData\TypedDataInternalPropertiesHelper;
use Drupal\jsonapi\Normalizer\Value\NullFieldNormalizerValue;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface;
use Drupal\jsonapi\Resource\EntityCollection;
use Drupal\jsonapi\LinkManager\LinkManager;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class EntityReferenceFieldNormalizer extends FieldNormalizer implements DenormalizerInterface {
protected $supportedInterfaceOrClass = EntityReferenceFieldItemListInterface::class;
protected $linkManager;
protected $fieldManager;
protected $pluginManager;
protected $entityRepository;
public function __construct(LinkManager $link_manager, EntityFieldManagerInterface $field_manager, FieldTypePluginManagerInterface $plugin_manager, ResourceTypeRepositoryInterface $resource_type_repository, EntityRepositoryInterface $entity_repository) {
$this->linkManager = $link_manager;
$this->fieldManager = $field_manager;
$this->pluginManager = $plugin_manager;
$this->resourceTypeRepository = $resource_type_repository;
$this->entityRepository = $entity_repository;
}
public function normalize($field, $format = NULL, array $context = []) {
$field_access = $field
->access('view', $context['account'], TRUE);
if (!$field_access
->isAllowed()) {
return new NullFieldNormalizerValue($field_access, 'relationships');
}
$main_property = $field
->getItemDefinition()
->getMainPropertyName();
$definition = $field
->getFieldDefinition();
$cardinality = $definition
->getFieldStorageDefinition()
->getCardinality();
$entity_list_metadata = [];
$entity_list = [];
foreach ($field as $item) {
if (!$item
->isEmpty() && $item
->get('entity')
->getValue() === NULL) {
$entity_list[] = NULL;
$entity_list_metadata[] = [
'links' => [
'help' => [
'href' => 'https://www.drupal.org/docs/8/modules/json-api/core-concepts#virtual',
'meta' => [
'about' => "Usage and meaning of the 'virtual' resource identifier.",
],
],
],
];
continue;
}
$metadata = [];
$properties = floatval(\Drupal::VERSION) < 8.5 ? $item
->getProperties() : TypedDataInternalPropertiesHelper::getNonInternalProperties($item);
foreach ($properties as $property_key => $property) {
if ($property_key !== $main_property) {
$metadata[$property_key] = $this->serializer
->normalize($property, $format, $context);
}
}
$entity_list_metadata[] = $metadata;
$entity = $item
->get('entity')
->getValue();
if ($this
->isInternalResourceType($entity)) {
continue;
}
$entity_list[] = $this->entityRepository
->getTranslationFromContext($entity);
}
$entity_collection = new EntityCollection($entity_list);
$relationship = new Relationship($this->resourceTypeRepository, $field
->getName(), $entity_collection, $field
->getEntity(), $field_access, $cardinality, $main_property, $entity_list_metadata);
return $this->serializer
->normalize($relationship, $format, $context);
}
public function denormalize($data, $class, $format = NULL, array $context = []) {
$resource_type = $context['resource_type'];
$entity_type_id = $resource_type
->getEntityTypeId();
$field_definitions = $this->fieldManager
->getFieldDefinitions($entity_type_id, $resource_type
->getBundle());
if (empty($context['related']) || empty($field_definitions[$context['related']])) {
throw new BadRequestHttpException('Invalid or missing related field.');
}
$field_definition = $field_definitions[$context['related']];
$item_definition = $field_definition
->getItemDefinition();
$property_key = $item_definition
->getMainPropertyName();
$target_resource_types = $resource_type
->getRelatableResourceTypesByField($context['related']);
$target_resource_type_names = array_map(function (ResourceType $resource_type) {
return $resource_type
->getTypeName();
}, $target_resource_types);
$is_multiple = $field_definition
->getFieldStorageDefinition()
->isMultiple();
$data = $this
->massageRelationshipInput($data, $is_multiple);
$values = array_map(function ($value) use ($property_key, $target_resource_type_names) {
if (!in_array($value['type'], $target_resource_type_names)) {
throw new BadRequestHttpException(sprintf('The provided type (%s) does not mach the destination resource types (%s).', $value['type'], implode(', ', $target_resource_type_names)));
}
list($entity_type_id, ) = explode('--', $value['type']);
$entity = $this->entityRepository
->loadEntityByUuid($entity_type_id, $value['id']);
$value['id'] = $entity ? $entity
->id() : NULL;
$properties = [
$property_key => $value['id'],
];
if (!empty($value['meta'])) {
foreach ($value['meta'] as $meta_key => $meta_value) {
$properties[$meta_key] = $meta_value;
}
}
return $properties;
}, $data['data']);
return $this->pluginManager
->createFieldItemList($context['target_entity'], $context['related'], $values);
}
protected function massageRelationshipInput(array $data, $is_multiple) {
if ($is_multiple) {
if (!is_array($data['data'])) {
throw new BadRequestHttpException('Invalid body payload for the relationship.');
}
$invalid_elements = array_filter($data['data'], function ($element) {
return empty($element['type']) || empty($element['id']);
});
if ($invalid_elements) {
throw new BadRequestHttpException('Invalid body payload for the relationship.');
}
}
else {
if (is_null($data['data'])) {
return [
'data' => [],
];
}
if (empty($data['data']['type']) || empty($data['data']['id'])) {
throw new BadRequestHttpException('Invalid body payload for the relationship.');
}
$data['data'] = [
$data['data'],
];
}
return $data;
}
protected function isInternalResourceType(EntityInterface $entity) {
return ($resource_type = $this->resourceTypeRepository
->get($entity
->getEntityTypeId(), $entity
->bundle())) && $resource_type
->isInternal();
}
}