Newer
Older
<?php
/**
* @file
Alex Pott
committed
* Contains \Drupal\hal\Normalizer\ContentEntityNormalizer.
*/
namespace Drupal\hal\Normalizer;
use Drupal\Component\Utility\NestedArray;
Alex Pott
committed
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
Alex Pott
committed
use Drupal\rest\LinkManager\LinkManagerInterface;
Angie Byron
committed
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Converts the Drupal entity object structure to a HAL array structure.
*/
Alex Pott
committed
class ContentEntityNormalizer extends NormalizerBase {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
Alex Pott
committed
protected $supportedInterfaceOrClass = 'Drupal\Core\Entity\ContentEntityInterface';
Alex Pott
committed
/**
* The hypermedia link manager.
*
* @var \Drupal\rest\LinkManager\LinkManagerInterface
*/
protected $linkManager;
/**
Alex Pott
committed
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs an ContentEntityNormalizer object.
Alex Pott
committed
*
* @param \Drupal\rest\LinkManager\LinkManagerInterface $link_manager
* The hypermedia link manager.
*/
Alex Pott
committed
public function __construct(LinkManagerInterface $link_manager, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler) {
Alex Pott
committed
$this->linkManager = $link_manager;
Alex Pott
committed
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
Alex Pott
committed
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
*/
public function normalize($entity, $format = NULL, array $context = array()) {
$context += array(
'account' => NULL,
'included_fields' => NULL,
);
Alex Pott
committed
// Create the array of normalized fields, starting with the URI.
/** @var $entity \Drupal\Core\Entity\ContentEntityInterface */
$normalized = array(
'_links' => array(
'self' => array(
'href' => $this->getEntityUri($entity),
),
'type' => array(
'href' => $this->linkManager->getTypeUri($entity->getEntityTypeId(), $entity->bundle()),
),
),
);
Alex Pott
committed
// 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'])) {
Alex Pott
committed
$fields = array();
foreach ($context['included_fields'] as $field_name) {
$fields[] = $entity->get($field_name);
}
}
else {
Angie Byron
committed
$fields = $entity->getFields();
Alex Pott
committed
// Ignore the entity ID and revision ID.
$exclude = array($entity->getEntityType()->getKey('id'), $entity->getEntityType()->getKey('revision'));
foreach ($fields as $field) {
// Continue if this is an excluded field or the current user does not have
// access to view it.
if (in_array($field->getFieldDefinition()->getName(), $exclude) || !$field->access('view', $context['account'])) {
continue;
}
Alex Pott
committed
$normalized_property = $this->serializer->normalize($field, $format, $context);
$normalized = NestedArray::mergeDeep($normalized, $normalized_property);
}
return $normalized;
}
Angie Byron
committed
/**
* Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize().
*
* @param array $data
* Entity data to restore.
* @param string $class
* Unused, entity_create() is used to instantiate entity objects.
* @param string $format
* Format the given data was extracted from.
* @param array $context
* Options available to the denormalizer. Keys that can be used:
* - request_method: if set to "patch" the denormalization will clear out
* all default values for entity fields before applying $data to the
* entity.
*
Angie Byron
committed
* @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
public function denormalize($data, $class, $format = NULL, array $context = array()) {
// Get type, necessary for determining which bundle to create.
if (!isset($data['_links']['type'])) {
throw new UnexpectedValueException('The type link relation must be specified.');
}
// Create the entity.
$typed_data_ids = $this->getTypedDataIds($data['_links']['type']);
$entity_type = $this->entityManager->getDefinition($typed_data_ids['entity_type']);
$langcode_key = $entity_type->getKey('langcode');
$values = array();
catch
committed
// Figure out the language to use.
if (isset($data[$langcode_key])) {
$values[$langcode_key] = $data[$langcode_key][0]['value'];
Alex Pott
committed
// Remove the langcode so it does not get iterated over below.
unset($data[$langcode_key]);
catch
committed
}
Alex Pott
committed
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]);
}
catch
committed
$entity = $this->entityManager->getStorage($typed_data_ids['entity_type'])->create($values);
Angie Byron
committed
// Remove links from data array.
Angie Byron
committed
unset($data['_links']);
// Get embedded resources and remove from data array.
$embedded = array();
if (isset($data['_embedded'])) {
$embedded = $data['_embedded'];
unset($data['_embedded']);
}
// Flatten the embedded values.
foreach ($embedded as $relation => $field) {
$field_ids = $this->linkManager->getRelationInternalIds($relation);
if (!empty($field_ids)) {
$field_name = $field_ids['field_name'];
$data[$field_name] = $field;
}
}
// Special handling for PATCH: pass the names of the fields whose values
// should be merged.
if (isset($context['request_method']) && $context['request_method'] == 'patch') {
$entity->_restPatchFields = array_keys($data);
}
Angie Byron
committed
// Iterate through remaining items in data array. These should all
// correspond to fields.
foreach ($data as $field_name => $field_data) {
// Remove any values that were set as a part of entity creation (e.g
// uuid). If this field is set to an empty array in the data, this will
// also have the effect of marking the field for deletion in REST module.
$entity->{$field_name} = array();
$field = $entity->get($field_name);
// Get the class of the field. This will generally be the default Field
// class.
Angie Byron
committed
$field_class = get_class($field);
Angie Byron
committed
// Pass in the empty field object as a target instance. Since the context
// is already prepared for the field, any data added to it is
// automatically added to the entity.
$context['target_instance'] = $field;
Angie Byron
committed
$this->serializer->denormalize($field_data, $field_class, $format, $context);
Angie Byron
committed
}
return $entity;
}
/**
* Constructs the entity URI.
*
* @param $entity
* The entity.
*
* @return string
* The entity URI.
*/
protected function getEntityUri($entity) {
Alex Pott
committed
return $entity->url('canonical', array('absolute' => TRUE));
Angie Byron
committed
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
* Gets the typed data IDs for a type URI.
*
* @param array $types
* The type array(s) (value of the 'type' attribute of the incoming data).
*
* @return array
* The typed data IDs.
*
* @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
protected function getTypedDataIds($types) {
// The 'type' can potentially contain an array of type objects. By default,
// Drupal only uses a single type in serializing, but allows for multiple
// types when deserializing.
if (isset($types['href'])) {
$types = array($types);
}
foreach ($types as $type) {
if (!isset($type['href'])) {
throw new UnexpectedValueException('Type must contain an \'href\' attribute.');
}
$type_uri = $type['href'];
// Check whether the URI corresponds to a known type on this site. Break
// once one does.
if ($typed_data_ids = $this->linkManager->getTypeInternalIds($type['href'])) {
break;
}
}
// If none of the URIs correspond to an entity type on this site, no entity
// can be created. Throw an exception.
if (empty($typed_data_ids)) {
throw new UnexpectedValueException(sprintf('Type %s does not correspond to an entity on this site.', $type_uri));
}
return $typed_data_ids;
}