Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,11 @@
# Before Drupal 8.3, typed data primitive values were normalized as strings, as
# this was usually returned from database storage. A primitive data normalizer
# has been introduced to get the casted value instead.
bc_primitives_as_strings: false
# Before Drupal 8.3, timestamps were always returned as Unix timestamps, which
# are not a universal format for interchange. Now, RFC3339 timestamps are
# returned. New Drupal installations opt out from this by default (hence this
# is set to false), existing installations opt in to it.
# @see serialization_update_8301()
# @see https://www.drupal.org/node/2768651
bc_timestamp_normalizer_unix: false

View file

@ -0,0 +1,10 @@
serialization.settings:
type: config_object
label: 'Serialization settings'
mapping:
bc_primitives_as_strings:
type: boolean
label: 'Whether to retain pre Drupal 8.3 behavior of serializing all primitive items as strings.'
bc_timestamp_normalizer_unix:
type: boolean
label: 'Whether the pre Drupal 8.4 behavior of returning Unix timestamps instead of RFC3339 timestamps for TimestampItem fields is enabled or not.'

View file

@ -0,0 +1,6 @@
name: Serialization
type: module
description: Provides a service for (de)serializing data to/from formats such as JSON and XML
package: Web services
version: VERSION
core: 8.x

View file

@ -0,0 +1,58 @@
<?php
/**
* @file
* Update functions for the Serialization module.
*/
/**
* Implements hook_requirements().
*/
function serialization_requirements($phase) {
$requirements = [];
if ($phase == 'runtime') {
$requirements['serialization_as_strings'] = [
'title' => t('Serialized data types'),
'severity' => REQUIREMENT_INFO,
];
if (\Drupal::config('serialization.settings')->get('bc_primitives_as_strings')) {
$requirements['serialization_as_strings']['value'] = t('Enabled');
$requirements['serialization_as_strings']['description'] = t('The Serialization API is configured to output only string values for REST and other applications (instead of integers or Booleans when appropriate). <a href="https://www.drupal.org/node/2837696">Disabling this backwards compatibility mode</a> is recommended unless your sites or applications require string output.');
}
else {
$requirements['serialization_as_strings']['value'] = t('Not enabled');
$requirements['serialization_as_strings']['description'] = t('The Serialization API is configured with the recommended default and outputs typed values (integers, Booleans, or strings as appropriate) for REST and other applications. If your site or applications require string output, you can <a href="https://www.drupal.org/node/2837696">enable backwards compatibility mode</a>.');
}
}
return $requirements;
}
/**
* @see hal_update_8301()
*/
function serialization_update_8301() {
}
/**
* Add serialization.settings::bc_primitives_as_strings configuration.
*/
function serialization_update_8302() {
$config_factory = \Drupal::configFactory();
$config_factory->getEditable('serialization.settings')
->set('bc_primitives_as_strings', FALSE)
->save(TRUE);
return t('The REST API will no longer output all values as strings. Integers/booleans will be used where appropriate. If your site depends on these value being strings, <a href="https://www.drupal.org/node/2837696">read the change record to learn how to enable the BC mode.</a>');
}
/**
* Enable BC for timestamp formatting: continue to return UNIX timestamps.
*/
function serialization_update_8401() {
$config_factory = \Drupal::configFactory();
$serialization_settings = $config_factory->getEditable('serialization.settings');
$serialization_settings->set('bc_timestamp_normalizer_unix', TRUE)->save(TRUE);
}

View file

@ -0,0 +1,25 @@
<?php
/**
* @file
* Provides a service for (de)serializing data to/from formats such as JSON and XML.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function serialization_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.serialization':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Serialization module provides a service for serializing and deserializing data to and from formats such as JSON and XML.') . '</p>';
$output .= '<p>' . t('Serialization is the process of converting data structures like arrays and objects into a string. This allows the data to be represented in a way that is easy to exchange and store (for example, for transmission over the Internet or for storage in a local file system). These representations can then be deserialized to get back to the original data structures.') . '</p>';
$output .= '<p>' . t('The serializer splits this process into two parts. Normalization converts an object to a normalized array structure. Encoding takes that array and converts it to a string.') . '</p>';
$output .= '<p>' . t('This module does not have a user interface. It is used by other modules which need to serialize data, such as <a href=":rest">REST</a>.', [':rest' => (\Drupal::moduleHandler()->moduleExists('rest')) ? \Drupal::url('help.page', ['name' => 'rest']) : '#']) . '</p>';
$output .= '<p>' . t('For more information, see the <a href=":doc_url">online documentation for the Serialization module</a>.', [':doc_url' => 'https://www.drupal.org/documentation/modules/serialization']) . '</p>';
return $output;
}
}

View file

@ -0,0 +1,107 @@
services:
serializer:
class: Symfony\Component\Serializer\Serializer
arguments: [{ }, { }]
serializer.normalizer.config_entity:
class: Drupal\serialization\Normalizer\ConfigEntityNormalizer
tags:
- { name: normalizer }
arguments: ['@entity.manager']
serializer.normalizer.content_entity:
class: Drupal\serialization\Normalizer\ContentEntityNormalizer
tags:
- { name: normalizer }
arguments: ['@entity.manager']
serializer.normalizer.entity:
class: Drupal\serialization\Normalizer\EntityNormalizer
tags:
- { name: normalizer }
arguments: ['@entity.manager']
serializer.normalizer.primitive_data:
class: Drupal\serialization\Normalizer\PrimitiveDataNormalizer
tags:
- { name: normalizer, priority: 5, bc: bc_primitives_as_strings, bc_config_name: 'serialization.settings' }
serializer.normalizer.complex_data:
class: Drupal\serialization\Normalizer\ComplexDataNormalizer
tags:
- { name: normalizer }
serializer.normalizer.entity_reference_field_item:
class: Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer
tags:
# Set the priority lower than the hal entity reference field item
# normalizer, so that we do not replace that for hal_json but higher than
# this modules generic field item normalizer.
# @todo Find a better way for this in https://www.drupal.org/node/2575761.
- { name: normalizer, priority: 8 }
arguments: ['@entity.repository']
serialization.normalizer.field_item:
class: Drupal\serialization\Normalizer\FieldItemNormalizer
tags:
# Priority must be lower than serializer.normalizer.field_item.hal and any
# field type specific normalizer such as
# serializer.normalizer.entity_reference_field_item.
- { name: normalizer, priority: 6 }
serialization.normalizer.field:
class: Drupal\serialization\Normalizer\FieldNormalizer
tags:
# Priority must be lower than serializer.normalizer.field.hal.
- { name: normalizer, priority: 6 }
serializer.normalizer.list:
class: Drupal\serialization\Normalizer\ListNormalizer
tags:
# Priority must be higher than serialization.normalizer.field but less
# than hal field normalizer.
- { name: normalizer, priority: 9 }
serializer.normalizer.timestamp_item:
class: Drupal\serialization\Normalizer\TimestampItemNormalizer
tags:
# Priority must be higher than serializer.normalizer.field_item and lower
# than hal normalizers.
- { name: normalizer, priority: 8, bc: bc_timestamp_normalizer_unix, bc_config_name: 'serialization.settings' }
serializer.normalizer.password_field_item:
class: Drupal\serialization\Normalizer\NullNormalizer
arguments: ['Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem']
tags:
- { name: normalizer, priority: 20 }
serializer.normalizer.safe_string:
class: Drupal\serialization\Normalizer\MarkupNormalizer
tags:
- { name: normalizer }
serializer.normalizer.typed_data:
class: Drupal\serialization\Normalizer\TypedDataNormalizer
tags:
- { name: normalizer }
serializer.encoder.json:
class: Drupal\serialization\Encoder\JsonEncoder
tags:
- { name: encoder, format: json }
serializer.encoder.xml:
class: Drupal\serialization\Encoder\XmlEncoder
tags:
- { name: encoder, format: xml }
serializer.entity_resolver:
class: Drupal\serialization\EntityResolver\ChainEntityResolver
serializer.entity_resolver.uuid:
class: Drupal\serialization\EntityResolver\UuidResolver
tags:
- { name: entity_resolver}
arguments: ['@entity.repository']
serialization.entity_resolver.target_id:
class: Drupal\serialization\EntityResolver\TargetIdResolver
tags:
- { name: entity_resolver}
serialization.exception.default:
class: Drupal\serialization\EventSubscriber\DefaultExceptionSubscriber
tags:
- { name: event_subscriber }
arguments: ['@serializer', '%serializer.formats%']
serialization.user_route_alter_subscriber:
class: Drupal\serialization\EventSubscriber\UserRouteAlterSubscriber
tags:
- { name: event_subscriber }
arguments: ['%serializer.formats%']
serialization.bc_config_subscriber:
class: Drupal\serialization\EventSubscriber\BcConfigSubscriber
tags:
- { name: event_subscriber }
arguments: ['@kernel']

View file

@ -0,0 +1,53 @@
<?php
namespace Drupal\serialization\Encoder;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Encoder\JsonDecode;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder as BaseJsonEncoder;
/**
* Adds 'ajax to the supported content types of the JSON encoder'
*
* @internal
* This encoder should not be used directly. Rather, use the `serializer`
* service.
*/
class JsonEncoder extends BaseJsonEncoder implements EncoderInterface, DecoderInterface {
/**
* The formats that this Encoder supports.
*
* @var array
*/
protected static $format = ['json', 'ajax'];
/**
* {@inheritdoc}
*/
public function __construct(JsonEncode $encodingImpl = NULL, JsonDecode $decodingImpl = NULL) {
// Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be
// embedded into HTML.
// @see \Symfony\Component\HttpFoundation\JsonResponse
$json_encoding_options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
$this->encodingImpl = $encodingImpl ?: new JsonEncode($json_encoding_options);
$this->decodingImpl = $decodingImpl ?: new JsonDecode(TRUE);
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format) {
return in_array($format, static::$format);
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format) {
return in_array($format, static::$format);
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Drupal\serialization\Encoder;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\XmlEncoder as BaseXmlEncoder;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
/**
* Adds XML support for serializer.
*
* This acts as a wrapper class for Symfony's XmlEncoder so that it is not
* implementing NormalizationAwareInterface, and can be normalized externally.
*
* @internal
* This encoder should not be used directly. Rather, use the `serializer`
* service.
*/
class XmlEncoder implements SerializerAwareInterface, EncoderInterface, DecoderInterface {
use SerializerAwareTrait;
/**
* The formats that this Encoder supports.
*
* @var array
*/
static protected $format = ['xml'];
/**
* An instance of the Symfony XmlEncoder to perform the actual encoding.
*
* @var \Symfony\Component\Serializer\Encoder\XmlEncoder
*/
protected $baseEncoder;
/**
* Gets the base encoder instance.
*
* @return \Symfony\Component\Serializer\Encoder\XmlEncoder
* The base encoder.
*/
public function getBaseEncoder() {
if (!isset($this->baseEncoder)) {
$this->baseEncoder = new BaseXmlEncoder();
$this->baseEncoder->setSerializer($this->serializer);
}
return $this->baseEncoder;
}
/**
* Sets the base encoder instance.
*
* @param \Symfony\Component\Serializer\Encoder\XmlEncoder $encoder
*/
public function setBaseEncoder($encoder) {
$this->baseEncoder = $encoder;
}
/**
* {@inheritdoc}
*/
public function encode($data, $format, array $context = []) {
return $this->getBaseEncoder()->encode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format) {
return in_array($format, static::$format);
}
/**
* {@inheritdoc}
*/
public function decode($data, $format, array $context = []) {
return $this->getBaseEncoder()->decode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format) {
return in_array($format, static::$format);
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace Drupal\serialization\EntityResolver;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Resolver delegating the entity resolution to a chain of resolvers.
*/
class ChainEntityResolver implements ChainEntityResolverInterface {
/**
* The concrete resolvers.
*
* @var \Drupal\serialization\EntityResolver\EntityResolverInterface[]
*/
protected $resolvers = [];
/**
* Constructs a ChainEntityResolver object.
*
* @param \Drupal\serialization\EntityResolver\EntityResolverInterface[] $resolvers
* The array of concrete resolvers.
*/
public function __construct(array $resolvers = []) {
$this->resolvers = $resolvers;
}
/**
* {@inheritdoc}
*/
public function addResolver(EntityResolverInterface $resolver) {
$this->resolvers[] = $resolver;
}
/**
* {@inheritdoc}
*/
public function resolve(NormalizerInterface $normalizer, $data, $entity_type) {
foreach ($this->resolvers as $resolver) {
$resolved = $resolver->resolve($normalizer, $data, $entity_type);
if (isset($resolved)) {
return $resolved;
}
}
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Drupal\serialization\EntityResolver;
/**
* An interface for delegating a entity resolution to a chain of resolvers.
*/
interface ChainEntityResolverInterface extends EntityResolverInterface {
/**
* Adds an entity resolver.
*
* @param \Drupal\serialization\EntityResolver\EntityResolverInterface $resolver
* The entity resolver to add.
*/
public function addResolver(EntityResolverInterface $resolver);
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\serialization\EntityResolver;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
interface EntityResolverInterface {
/**
* Returns the local ID of an entity referenced by serialized data.
*
* Drupal entities are loaded by and internally referenced by a local ID.
* Because different websites can use the same local ID to refer to different
* entities (e.g., node "1" can be a different node on foo.com and bar.com, or
* on example.com and staging.example.com), it is generally unsuitable for use
* in hypermedia data exchanges. Instead, UUIDs, URIs, or other globally
* unique IDs are preferred.
*
* This function takes a $data array representing partially deserialized data
* for an entity reference, and resolves it to a local entity ID. For example,
* depending on the data specification being used, $data might contain a
* 'uuid' key, a 'uri' key, a 'href' key, or some other data identifying the
* entity, and it is up to the implementor of this interface to resolve that
* appropriately for the specification being used.
*
* @param \Symfony\Component\Serializer\Normalizer\NormalizerInterface $normalizer
* The Normalizer which is handling the data.
* @param array $data
* The data passed into the calling Normalizer.
* @param string $entity_type
* The type of entity being resolved; e.g., 'node' or 'user'.
*
* @return string|null
* Returns the local entity ID, if found. Otherwise, returns NULL.
*/
public function resolve(NormalizerInterface $normalizer, $data, $entity_type);
}

View file

@ -0,0 +1,22 @@
<?php
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;
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Drupal\serialization\EntityResolver;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Interface for extracting UUID from entity reference data when denormalizing.
*/
interface UuidReferenceInterface extends NormalizerInterface {
/**
* Get the uuid from the data array.
*
* @param array $data
* The data, as was passed into the Normalizer.
*
* @return string
* A UUID.
*/
public function getUuid($data);
}

View file

@ -0,0 +1,45 @@
<?php
namespace Drupal\serialization\EntityResolver;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Resolves entities from data that contains an entity UUID.
*/
class UuidResolver implements EntityResolverInterface {
/**
* The entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* Constructs a UuidResolver object.
*
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
*/
public function __construct(EntityRepositoryInterface $entity_repository) {
$this->entityRepository = $entity_repository;
}
/**
* {@inheritdoc}
*/
public function resolve(NormalizerInterface $normalizer, $data, $entity_type) {
// The normalizer is what knows the specification of the data being
// deserialized. If it can return a UUID from that data, and if there's an
// entity with that UUID, then return its ID.
if (($normalizer instanceof UuidReferenceInterface) && ($uuid = $normalizer->getUuid($data))) {
if ($entity = $this->entityRepository->loadEntityByUuid($entity_type, $uuid)) {
return $entity->id();
}
}
return NULL;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\serialization\EventSubscriber;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Drupal\Core\DrupalKernelInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Config event subscriber to rebuild the container when BC config is saved.
*/
class BcConfigSubscriber implements EventSubscriberInterface {
/**
* The Drupal Kernel.
*
* @var \Drupal\Core\DrupalKernelInterface
*/
protected $kernel;
/**
* BcConfigSubscriber constructor.
*
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The Drupal Kernel.
*/
public function __construct(DrupalKernelInterface $kernel) {
$this->kernel = $kernel;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[ConfigEvents::SAVE][] = 'onConfigSave';
return $events;
}
/**
* Invalidates the service container if serialization BC config gets updated.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
*/
public function onConfigSave(ConfigCrudEvent $event) {
$saved_config = $event->getConfig();
if ($saved_config->getName() === 'serialization.settings') {
if ($event->isChanged('bc_primitives_as_strings') || $event->isChanged('bc_timestamp_normalizer_unix')) {
$this->kernel->invalidateContainer();
}
}
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Drupal\serialization\EventSubscriber;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableResponse;
use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Handles default error responses in serialization formats.
*/
class DefaultExceptionSubscriber extends HttpExceptionSubscriberBase {
/**
* The serializer.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
/**
* The available serialization formats.
*
* @var array
*/
protected $serializerFormats = [];
/**
* DefaultExceptionSubscriber constructor.
*
* @param \Symfony\Component\Serializer\SerializerInterface $serializer
* The serializer service.
* @param array $serializer_formats
* The available serialization formats.
*/
public function __construct(SerializerInterface $serializer, array $serializer_formats) {
$this->serializer = $serializer;
$this->serializerFormats = $serializer_formats;
}
/**
* {@inheritdoc}
*/
protected function getHandledFormats() {
return $this->serializerFormats;
}
/**
* {@inheritdoc}
*/
protected static function getPriority() {
// This will fire after the most common HTML handler, since HTML requests
// are still more common than HTTP requests. But it has a lower priority
// than \Drupal\Core\EventSubscriber\ExceptionJsonSubscriber::on4xx(), so
// that this also handles the 'json' format. Then all serialization formats
// (::getHandledFormats()) are handled by this exception subscriber, which
// results in better consistency.
return -70;
}
/**
* Handles all 4xx errors for all serialization failures.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on4xx(GetResponseForExceptionEvent $event) {
/** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception */
$exception = $event->getException();
$request = $event->getRequest();
$format = $request->getRequestFormat();
$content = ['message' => $exception->getMessage()];
$encoded_content = $this->serializer->serialize($content, $format);
$headers = $exception->getHeaders();
// Add the MIME type from the request to send back in the header.
$headers['Content-Type'] = $request->getMimeType($format);
// If the exception is cacheable, generate a cacheable response.
if ($exception instanceof CacheableDependencyInterface) {
$response = new CacheableResponse($encoded_content, $exception->getStatusCode(), $headers);
$response->addCacheableDependency($exception);
}
else {
$response = new Response($encoded_content, $exception->getStatusCode(), $headers);
}
$event->setResponse($response);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\serialization\EventSubscriber;
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Alters user authentication routes to support additional serialization formats.
*/
class UserRouteAlterSubscriber implements EventSubscriberInterface {
/**
* The available serialization formats.
*
* @var array
*/
protected $serializerFormats = [];
/**
* UserRouteAlterSubscriber constructor.
*
* @param array $serializer_formats
* The available serializer formats.
*/
public function __construct(array $serializer_formats) {
$this->serializerFormats = $serializer_formats;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[RoutingEvents::ALTER][] = 'onRoutingAlterAddFormats';
return $events;
}
/**
* Adds supported formats to the user authentication HTTP routes.
*
* @param \Drupal\Core\Routing\RouteBuildEvent $event
* The event to process.
*/
public function onRoutingAlterAddFormats(RouteBuildEvent $event) {
$route_names = [
'user.login_status.http',
'user.login.http',
'user.logout.http',
'user.pass.http',
];
$routes = $event->getRouteCollection();
foreach ($route_names as $route_name) {
if ($route = $routes->get($route_name)) {
$formats = explode('|', $route->getRequirement('_format'));
$formats = array_unique(array_merge($formats, $this->serializerFormats));
$route->setRequirement('_format', implode('|', $formats));
}
}
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Drupal\serialization\Normalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Defines the interface for normalizers producing cacheable normalizations.
*
* @see cache
*/
interface CacheableNormalizerInterface extends NormalizerInterface {
/**
* Name of key for bubbling cacheability metadata via serialization context.
*
* @see \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
* @see \Symfony\Component\Serializer\SerializerInterface::serialize()
* @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::renderResponseBody()
*/
const SERIALIZATION_CONTEXT_CACHEABILITY = 'cacheability';
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\TypedDataInternalPropertiesHelper;
/**
* Converts the Drupal entity object structures to a normalized array.
*
* This is the default Normalizer for entities. All formats that have Encoders
* registered with the Serializer in the DIC will be normalized with this
* class unless another Normalizer is registered which supersedes it. If a
* module wants to use format-specific or class-specific normalization, then
* that module can register a new Normalizer and give it a higher priority than
* this one.
*/
class ComplexDataNormalizer extends NormalizerBase {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ComplexDataInterface';
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
$attributes = [];
// $object will not always match $supportedInterfaceOrClass.
// @see \Drupal\serialization\Normalizer\EntityNormalizer
// Other normalizers that extend this class may only provide $object that
// implements \Traversable.
if ($object instanceof ComplexDataInterface) {
// If there are no properties to normalize, just normalize the value.
$object = !empty($object->getProperties(TRUE))
? TypedDataInternalPropertiesHelper::getNonInternalProperties($object)
: $object->getValue();
}
/** @var \Drupal\Core\TypedData\TypedDataInterface $property */
foreach ($object as $name => $property) {
$attributes[$name] = $this->serializer->normalize($property, $format, $context);
}
return $attributes;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\serialization\Normalizer;
/**
* Normalizes/denormalizes Drupal config entity objects into an array structure.
*/
class ConfigEntityNormalizer extends EntityNormalizer {
/**
* The interface or class that this Normalizer supports.
*
* @var array
*/
protected $supportedInterfaceOrClass = ['Drupal\Core\Config\Entity\ConfigEntityInterface'];
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
return static::getDataWithoutInternals($object->toArray());
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
return parent::denormalize(static::getDataWithoutInternals($data), $class, $format, $context);
}
/**
* Gets the given data without the internal implementation details.
*
* @param array $data
* The data that is either currently or about to be stored in configuration.
*
* @return array
* The same data, but without internals. Currently, that is only the '_core'
* key, which is reserved by Drupal core to handle complex edge cases
* correctly. Data in the '_core' key is irrelevant to clients reading
* configuration, and is not allowed to be set by clients writing
* configuration: it is for Drupal core only, and managed by Drupal core.
*
* @see https://www.drupal.org/node/2653358
*/
protected static function getDataWithoutInternals(array $data) {
return array_diff_key($data, ['_core' => TRUE]);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\TypedData\TypedDataInternalPropertiesHelper;
/**
* Normalizes/denormalizes Drupal content entities into an array structure.
*/
class ContentEntityNormalizer extends EntityNormalizer {
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = ['Drupal\Core\Entity\ContentEntityInterface'];
/**
* {@inheritdoc}
*/
public function normalize($entity, $format = NULL, array $context = []) {
$context += [
'account' => NULL,
];
$attributes = [];
/** @var \Drupal\Core\Entity\Entity $entity */
foreach (TypedDataInternalPropertiesHelper::getNonInternalProperties($entity->getTypedData()) as $name => $field_items) {
if ($field_items->access('view', $context['account'])) {
$attributes[$name] = $this->serializer->normalize($field_items, $format, $context);
}
}
return $attributes;
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/**
* Normalizes/denormalizes Drupal entity objects into an array structure.
*/
class EntityNormalizer extends ComplexDataNormalizer implements DenormalizerInterface {
use FieldableEntityNormalizerTrait;
/**
* The interface or class that this Normalizer supports.
*
* @var array
*/
protected $supportedInterfaceOrClass = [EntityInterface::class];
/**
* Constructs an EntityNormalizer object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
$entity_type_id = $this->determineEntityTypeId($class, $context);
$entity_type_definition = $this->getEntityTypeDefinition($entity_type_id);
// The bundle property will be required to denormalize a bundleable
// fieldable entity.
if ($entity_type_definition->entityClassImplements(FieldableEntityInterface::class)) {
// Extract bundle data to pass into entity creation if the entity type uses
// bundles.
if ($entity_type_definition->hasKey('bundle')) {
// Get an array containing the bundle only. This also remove the bundle
// key from the $data array.
$create_params = $this->extractBundleData($data, $entity_type_definition);
}
else {
$create_params = [];
}
// Create the entity from bundle data only, then apply field values after.
$entity = $this->entityManager->getStorage($entity_type_id)->create($create_params);
$this->denormalizeFieldData($data, $entity, $format, $context);
}
else {
// Create the entity from all data.
$entity = $this->entityManager->getStorage($entity_type_id)->create($data);
}
// Pass the names of the fields whose values can be merged.
// @todo https://www.drupal.org/node/2456257 remove this.
$entity->_restSubmittedFields = array_keys($data);
return $entity;
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Adds the file URI to embedded file entities.
*/
class EntityReferenceFieldItemNormalizer extends FieldItemNormalizer {
use EntityReferenceFieldItemNormalizerTrait;
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected $supportedInterfaceOrClass = EntityReferenceItem::class;
/**
* The entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* Constructs a EntityReferenceFieldItemNormalizer object.
*
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
*/
public function __construct(EntityRepositoryInterface $entity_repository) {
$this->entityRepository = $entity_repository;
}
/**
* {@inheritdoc}
*/
public function normalize($field_item, $format = NULL, array $context = []) {
$values = parent::normalize($field_item, $format, $context);
$this->normalizeRootReferenceValue($values, $field_item);
/** @var \Drupal\Core\Entity\EntityInterface $entity */
if ($entity = $field_item->get('entity')->getValue()) {
$values['target_type'] = $entity->getEntityTypeId();
// Add the target entity UUID to the normalized output values.
$values['target_uuid'] = $entity->uuid();
// Add a 'url' value if there is a reference and a canonical URL. Hard
// code 'canonical' here as config entities override the default $rel
// parameter value to 'edit-form.
if ($url = $entity->url('canonical')) {
$values['url'] = $url;
}
}
return $values;
}
/**
* {@inheritdoc}
*/
protected function constructValue($data, $context) {
if (isset($data['target_uuid'])) {
/** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $field_item */
$field_item = $context['target_instance'];
if (empty($data['target_uuid'])) {
throw new InvalidArgumentException(sprintf('If provided "target_uuid" cannot be empty for field "%s".', $data['target_type'], $data['target_uuid'], $field_item->getName()));
}
$target_type = $field_item->getFieldDefinition()->getSetting('target_type');
if (!empty($data['target_type']) && $target_type !== $data['target_type']) {
throw new UnexpectedValueException(sprintf('The field "%s" property "target_type" must be set to "%s" or omitted.', $field_item->getFieldDefinition()->getName(), $target_type));
}
if ($entity = $this->entityRepository->loadEntityByUuid($target_type, $data['target_uuid'])) {
return ['target_id' => $entity->id()] + array_intersect_key($data, $field_item->getProperties());
}
else {
// Unable to load entity by uuid.
throw new InvalidArgumentException(sprintf('No "%s" entity found with UUID "%s" for field "%s".', $data['target_type'], $data['target_uuid'], $field_item->getName()));
}
}
return parent::constructValue($data, $context);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
/**
* Converts empty reference values for entity reference items.
*/
trait EntityReferenceFieldItemNormalizerTrait {
protected function normalizeRootReferenceValue(&$values, EntityReferenceItem $field_item) {
// @todo Generalize for all tree-structured entity types.
if ($this->fieldItemReferencesTaxonomyTerm($field_item) && empty($values['target_id'])) {
$values['target_id'] = NULL;
}
}
/**
* Determines if a field item references a taxonomy term.
*
* @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $field_item
*
* @return bool
*/
protected function fieldItemReferencesTaxonomyTerm(EntityReferenceItem $field_item) {
return $field_item->getFieldDefinition()->getSetting('target_type') === 'taxonomy_term';
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Field\FieldItemInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/**
* Denormalizes field item object structure by updating the entity field values.
*/
class FieldItemNormalizer extends ComplexDataNormalizer implements DenormalizerInterface {
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = FieldItemInterface::class;
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
if (!isset($context['target_instance'])) {
throw new InvalidArgumentException('$context[\'target_instance\'] must be set to denormalize with the FieldItemNormalizer');
}
if ($context['target_instance']->getParent() == NULL) {
throw new InvalidArgumentException('The field item passed in via $context[\'target_instance\'] must have a parent set.');
}
/** @var \Drupal\Core\Field\FieldItemInterface $field_item */
$field_item = $context['target_instance'];
$field_item->setValue($this->constructValue($data, $context));
return $field_item;
}
/**
* Build the field item value using the incoming data.
*
* Most normalizers that extend this class can simply use this method to
* construct the denormalized value without having to override denormalize()
* and reimplementing its validation logic or its call to set the field value.
*
* @param mixed $data
* The incoming data for this field item.
* @param array $context
* The context passed into the Normalizer.
*
* @return mixed
* The value to use in Entity::setValue().
*/
protected function constructValue($data, $context) {
return $data;
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Field\FieldItemListInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/**
* Denormalizes data to Drupal field values.
*
* This class simply calls denormalize() on the individual FieldItems. The
* FieldItem normalizers are responsible for setting the field values for each
* item.
*
* @see \Drupal\serialization\Normalizer\FieldItemNormalizer.
*/
class FieldNormalizer extends ListNormalizer implements DenormalizerInterface {
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = FieldItemListInterface::class;
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
if (!isset($context['target_instance'])) {
throw new InvalidArgumentException('$context[\'target_instance\'] must be set to denormalize with the FieldNormalizer');
}
if ($context['target_instance']->getParent() == NULL) {
throw new InvalidArgumentException('The field passed in via $context[\'target_instance\'] must have a parent set.');
}
/** @var \Drupal\Core\Field\FieldItemListInterface $items */
$items = $context['target_instance'];
$item_class = $items->getItemDefinition()->getClass();
if (!is_array($data)) {
throw new UnexpectedValueException(sprintf('Field values for "%s" must use an array structure', $items->getName()));
}
foreach ($data as $item_data) {
// Create a new item and pass it as the target for the unserialization of
// $item_data. All items in field should have removed before this method
// was called.
// @see \Drupal\serialization\Normalizer\ContentEntityNormalizer::denormalize().
$context['target_instance'] = $items->appendItem();
$this->serializer->denormalize($item_data, $item_class, $format, $context);
}
return $items;
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* A trait for providing fieldable entity normalization/denormalization methods.
*
* @todo Move this into a FieldableEntityNormalizer in Drupal 9. This is a trait
* used in \Drupal\serialization\Normalizer\EntityNormalizer to maintain BC.
* @see https://www.drupal.org/node/2834734
*/
trait FieldableEntityNormalizerTrait {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Determines the entity type ID to denormalize as.
*
* @param string $class
* The entity type class to be denormalized to.
* @param array $context
* The serialization context data.
*
* @return string
* The entity type ID.
*/
protected function determineEntityTypeId($class, $context) {
// Get the entity type ID while letting context override the $class param.
return !empty($context['entity_type']) ? $context['entity_type'] : $this->entityManager->getEntityTypeFromClass($class);
}
/**
* Gets the entity type definition.
*
* @param string $entity_type_id
* The entity type ID to load the definition for.
*
* @return \Drupal\Core\Entity\EntityTypeInterface
* The loaded entity type definition.
*
* @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
protected function getEntityTypeDefinition($entity_type_id) {
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition */
// Get the entity type definition.
$entity_type_definition = $this->entityManager->getDefinition($entity_type_id, FALSE);
// Don't try to create an entity without an entity type id.
if (!$entity_type_definition) {
throw new UnexpectedValueException(sprintf('The specified entity type "%s" does not exist. A valid entity type is required for denormalization', $entity_type_id));
}
return $entity_type_definition;
}
/**
* Denormalizes the bundle property so entity creation can use it.
*
* @param array $data
* The data being denormalized.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition
* The entity type definition.
*
* @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
*
* @return string
* The valid bundle name.
*/
protected function extractBundleData(array &$data, EntityTypeInterface $entity_type_definition) {
$bundle_key = $entity_type_definition->getKey('bundle');
// Get the base field definitions for this entity type.
$base_field_definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_definition->id());
// Get the ID key from the base field definition for the bundle key or
// default to 'value'.
$key_id = isset($base_field_definitions[$bundle_key]) ? $base_field_definitions[$bundle_key]->getFieldStorageDefinition()->getMainPropertyName() : 'value';
// Normalize the bundle if it is not explicitly set.
$bundle_value = isset($data[$bundle_key][0][$key_id]) ? $data[$bundle_key][0][$key_id] : (isset($data[$bundle_key]) ? $data[$bundle_key] : NULL);
// Unset the bundle from the data.
unset($data[$bundle_key]);
// Get the bundle entity type from the entity type definition.
$bundle_type_id = $entity_type_definition->getBundleEntityType();
$bundle_types = $bundle_type_id ? $this->entityManager->getStorage($bundle_type_id)->getQuery()->execute() : [];
// Make sure a bundle has been provided.
if (!is_string($bundle_value)) {
throw new UnexpectedValueException(sprintf('Could not determine entity type bundle: "%s" field is missing.', $bundle_key));
}
// Make sure the submitted bundle is a valid bundle for the entity type.
if ($bundle_types && !in_array($bundle_value, $bundle_types)) {
throw new UnexpectedValueException(sprintf('"%s" is not a valid bundle type for denormalization.', $bundle_value));
}
return [$bundle_key => $bundle_value];
}
/**
* Denormalizes entity data by denormalizing each field individually.
*
* @param array $data
* The data to denormalize.
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The fieldable entity to set field values for.
* @param string $format
* The serialization format.
* @param array $context
* The context data.
*/
protected function denormalizeFieldData(array $data, FieldableEntityInterface $entity, $format, array $context) {
foreach ($data as $field_name => $field_data) {
$field_item_list = $entity->get($field_name);
// Remove any values that were set as a part of entity creation (e.g
// uuid). If the incoming field data is set to an empty array, this will
// also have the effect of emptying the field in REST module.
$field_item_list->setValue([]);
$field_item_list_class = get_class($field_item_list);
if ($field_data) {
// The field instance must be passed in the context so that the field
// denormalizer can update field values for the parent entity.
$context['target_instance'] = $field_item_list;
$this->serializer->denormalize($field_data, $field_item_list_class, $format, $context);
}
}
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\serialization\Normalizer;
/**
* Converts list objects to arrays.
*
* Ordinarily, this would be handled automatically by Serializer, but since
* there is a TypedDataNormalizer and the Field class extends TypedData, any
* Field will be handled by that Normalizer instead of being traversed. This
* class ensures that TypedData classes that also implement ListInterface are
* traversed instead of simply returning getValue().
*/
class ListNormalizer extends NormalizerBase {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\ListInterface';
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
$attributes = [];
foreach ($object as $fieldItem) {
$attributes[] = $this->serializer->normalize($fieldItem, $format, $context);
}
return $attributes;
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Drupal\serialization\Normalizer;
/**
* Normalizes MarkupInterface objects into a string.
*/
class MarkupNormalizer extends NormalizerBase {
/**
* The interface or class that this Normalizer supports.
*
* @var array
*/
protected $supportedInterfaceOrClass = ['Drupal\Component\Render\MarkupInterface'];
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
return (string) $object;
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
/**
* Base class for Normalizers.
*/
abstract class NormalizerBase implements SerializerAwareInterface, CacheableNormalizerInterface {
use SerializerAwareTrait;
/**
* The interface or class that this Normalizer supports.
*
* @var string|array
*/
protected $supportedInterfaceOrClass;
/**
* List of formats which supports (de-)normalization.
*
* @var string|string[]
*/
protected $format;
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = NULL) {
// If we aren't dealing with an object or the format is not supported return
// now.
if (!is_object($data) || !$this->checkFormat($format)) {
return FALSE;
}
$supported = (array) $this->supportedInterfaceOrClass;
return (bool) array_filter($supported, function ($name) use ($data) {
return $data instanceof $name;
});
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::supportsDenormalization()
*
* This class doesn't implement DenormalizerInterface, but most of its child
* classes do, so this method is implemented at this level to reduce code
* duplication.
*/
public function supportsDenormalization($data, $type, $format = NULL) {
// If the format is not supported return now.
if (!$this->checkFormat($format)) {
return FALSE;
}
$supported = (array) $this->supportedInterfaceOrClass;
$subclass_check = function ($name) use ($type) {
return (class_exists($name) || interface_exists($name)) && is_subclass_of($type, $name, TRUE);
};
return in_array($type, $supported) || array_filter($supported, $subclass_check);
}
/**
* Checks if the provided format is supported by this normalizer.
*
* @param string $format
* The format to check.
*
* @return bool
* TRUE if the format is supported, FALSE otherwise. If no format is
* specified this will return TRUE.
*/
protected function checkFormat($format = NULL) {
if (!isset($format) || !isset($this->format)) {
return TRUE;
}
return in_array($format, (array) $this->format, TRUE);
}
/**
* Adds cacheability if applicable.
*
* @param array $context
* Context options for the normalizer.
* @param $data
* The data that might have cacheability information.
*/
protected function addCacheableDependency(array $context, $data) {
if ($data instanceof CacheableDependencyInterface && isset($context[static::SERIALIZATION_CONTEXT_CACHEABILITY])) {
$context[static::SERIALIZATION_CONTEXT_CACHEABILITY]->addCacheableDependency($data);
}
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\serialization\Normalizer;
/**
* Null normalizer.
*/
class NullNormalizer extends NormalizerBase {
/**
* Constructs a NullNormalizer object.
*
* @param string|array $supported_interface_of_class
* The supported interface(s) or class(es).
*/
public function __construct($supported_interface_of_class) {
$this->supportedInterfaceOrClass = $supported_interface_of_class;
}
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
return NULL;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\TypedData\PrimitiveInterface;
/**
* Converts primitive data objects to their casted values.
*/
class PrimitiveDataNormalizer extends NormalizerBase {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected $supportedInterfaceOrClass = PrimitiveInterface::class;
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
// Typed data casts NULL objects to their empty variants, so for example
// the empty string ('') for string type data, or 0 for integer typed data.
// In a better world with typed data implementing algebraic data types,
// getCastedValue would return NULL, but as typed data is not aware of real
// optional values on the primitive level, we implement our own optional
// value normalization here.
return $object->getValue() === NULL ? NULL : $object->getCastedValue();
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace Drupal\serialization\Normalizer;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* A trait for TimestampItem normalization functionality.
*/
trait TimeStampItemNormalizerTrait {
/**
* Allowed timestamps formats for the denormalizer.
*
* The denormalizer allows deserialization to timestamps from three
* different formats. Validation of the input data and creation of the
* numerical timestamp value is handled with \DateTime::createFromFormat().
* The list is chosen to be unambiguous and language neutral, but also common
* for data interchange.
*
* @var string[]
*
* @see http://php.net/manual/datetime.createfromformat.php
*/
protected $allowedFormats = [
'UNIX timestamp' => 'U',
'ISO 8601' => \DateTime::ISO8601,
'RFC 3339' => \DateTime::RFC3339,
];
/**
* Processes normalized timestamp values to add a formatted date and format.
*
* @param array $normalized
* The normalized field data to process.
* @return array
* The processed data.
*/
protected function processNormalizedValues(array $normalized) {
// Use a RFC 3339 timestamp with the time zone set to UTC to replace the
// timestamp value.
$date = new \DateTime();
$date->setTimestamp($normalized['value']);
$date->setTimezone(new \DateTimeZone('UTC'));
$normalized['value'] = $date->format(\DateTime::RFC3339);
// 'format' is not a property on TimestampItem fields. This is present to
// assist consumers of this data.
$normalized['format'] = \DateTime::RFC3339;
return $normalized;
}
/**
* {@inheritdoc}
*/
protected function constructValue($data, $context) {
// Loop through the allowed formats and create a TimestampItem from the
// input data if it matches the defined pattern. Since the formats are
// unambiguous (i.e., they reference an absolute time with a defined time
// zone), only one will ever match.
$timezone = new \DateTimeZone('UTC');
// First check for a provided format.
if (!empty($data['format']) && in_array($data['format'], $this->allowedFormats)) {
$date = \DateTime::createFromFormat($data['format'], $data['value'], $timezone);
return ['value' => $date->getTimestamp()];
}
// Otherwise, loop through formats.
else {
foreach ($this->allowedFormats as $format) {
if (($date = \DateTime::createFromFormat($format, $data['value'], $timezone)) !== FALSE) {
return ['value' => $date->getTimestamp()];
}
}
}
$format_strings = [];
foreach ($this->allowedFormats as $label => $format) {
$format_strings[] = "\"$format\" ($label)";
}
$formats = implode(', ', $format_strings);
throw new UnexpectedValueException(sprintf('The specified date "%s" is not in an accepted format: %s.', $data['value'], $formats));
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
/**
* Converts values for TimestampItem to and from common formats.
*/
class TimestampItemNormalizer extends FieldItemNormalizer {
use TimeStampItemNormalizerTrait;
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected $supportedInterfaceOrClass = TimestampItem::class;
/**
* {@inheritdoc}
*/
public function normalize($field_item, $format = NULL, array $context = []) {
$data = parent::normalize($field_item, $format, $context);
return $this->processNormalizedValues($data);
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
if (empty($data['value'])) {
throw new InvalidArgumentException('No "value" attribute present');
}
return parent::denormalize($data, $class, $format, $context);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Drupal\serialization\Normalizer;
/**
* Converts typed data objects to arrays.
*/
class TypedDataNormalizer extends NormalizerBase {
/**
* The interface or class that this Normalizer supports.
*
* @var string
*/
protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\TypedDataInterface';
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
$this->addCacheableDependency($context, $object);
$value = $object->getValue();
// Support for stringable value objects: avoid numerous custom normalizers.
if (is_object($value) && method_exists($value, '__toString')) {
$value = (string) $value;
}
return $value;
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\serialization;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged 'normalizer' and 'encoder' to the Serializer.
*/
class RegisterEntityResolversCompilerPass implements CompilerPassInterface {
/**
* Adds services to the Serializer.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
$definition = $container->getDefinition('serializer.entity_resolver');
$resolvers = [];
// Retrieve registered Normalizers and Encoders from the container.
foreach ($container->findTaggedServiceIds('entity_resolver') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$resolvers[$priority][] = new Reference($id);
}
// Add the registered concrete EntityResolvers to the ChainEntityResolver.
foreach ($this->sort($resolvers) as $resolver) {
$definition->addMethodCall('addResolver', [$resolver]);
}
}
/**
* Sorts by priority.
*
* Order services from highest priority number to lowest (reverse sorting).
*
* @param array $services
* A nested array keyed on priority number. For each priority number, the
* value is an array of Symfony\Component\DependencyInjection\Reference
* objects, each a reference to a normalizer or encoder service.
*
* @return array
* A flattened array of Reference objects from $services, ordered from high
* to low priority.
*/
protected function sort($services) {
$sorted = [];
krsort($services);
// Flatten the array.
foreach ($services as $a) {
$sorted = array_merge($sorted, $a);
}
return $sorted;
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace Drupal\serialization;
use Drupal\Core\Config\BootstrapConfigStorageFactory;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged 'normalizer' and 'encoder' to the Serializer.
*/
class RegisterSerializationClassesCompilerPass implements CompilerPassInterface {
/**
* Adds services to the Serializer.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
$definition = $container->getDefinition('serializer');
// Retrieve registered Normalizers and Encoders from the container.
foreach ($container->findTaggedServiceIds('normalizer') as $id => $attributes) {
// The 'serializer' service is the public API: mark normalizers private.
$container->getDefinition($id)->setPublic(FALSE);
// If there is a BC key present, pass this to determine if the normalizer
// should be skipped.
if (isset($attributes[0]['bc']) && $this->normalizerBcSettingIsEnabled($attributes[0]['bc'], $attributes[0]['bc_config_name'])) {
continue;
}
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$normalizers[$priority][] = new Reference($id);
}
foreach ($container->findTaggedServiceIds('encoder') as $id => $attributes) {
// The 'serializer' service is the public API: mark encoders private.
$container->getDefinition($id)->setPublic(FALSE);
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$encoders[$priority][] = new Reference($id);
}
// Add the registered Normalizers and Encoders to the Serializer.
if (!empty($normalizers)) {
$definition->replaceArgument(0, $this->sort($normalizers));
}
if (!empty($encoders)) {
$definition->replaceArgument(1, $this->sort($encoders));
}
// Find all serialization formats known.
$formats = [];
$format_providers = [];
foreach ($container->findTaggedServiceIds('encoder') as $service_id => $attributes) {
$format = $attributes[0]['format'];
$formats[] = $format;
if ($provider_tag = $container->getDefinition($service_id)->getTag('_provider')) {
$format_providers[$format] = $provider_tag[0]['provider'];
}
}
$container->setParameter('serializer.formats', $formats);
$container->setParameter('serializer.format_providers', $format_providers);
}
/**
* Returns whether a normalizer BC setting is disabled or not.
*
* @param string $key
*
* @return bool
*/
protected function normalizerBcSettingIsEnabled($key, $config_name) {
$settings = BootstrapConfigStorageFactory::get()->read($config_name);
return !empty($settings[$key]);
}
/**
* Sorts by priority.
*
* Order services from highest priority number to lowest (reverse sorting).
*
* @param array $services
* A nested array keyed on priority number. For each priority number, the
* value is an array of Symfony\Component\DependencyInjection\Reference
* objects, each a reference to a normalizer or encoder service.
*
* @return array
* A flattened array of Reference objects from $services, ordered from high
* to low priority.
*/
protected function sort($services) {
$sorted = [];
krsort($services);
// Flatten the array.
foreach ($services as $a) {
$sorted = array_merge($sorted, $a);
}
return $sorted;
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Drupal\serialization;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
/**
* Serialization dependency injection container.
*/
class SerializationServiceProvider implements ServiceProviderInterface {
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
// Add a compiler pass for adding Normalizers and Encoders to Serializer.
$container->addCompilerPass(new RegisterSerializationClassesCompilerPass());
// Add a compiler pass for adding concrete Resolvers to chain Resolver.
$container->addCompilerPass(new RegisterEntityResolversCompilerPass());
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Drupal\serialization\Tests;
use Drupal\Tests\serialization\Kernel\NormalizerTestBase as SerializationNormalizerTestBase;
/**
* Helper base class to set up some test fields for serialization testing.
*
* @deprecated Scheduled for removal in Drupal 9.0.0.
* Use \Drupal\Tests\serialization\Kernel\NormalizerTestBase instead.
*/
abstract class NormalizerTestBase extends SerializationNormalizerTestBase {}

View file

@ -0,0 +1,6 @@
name: 'Entity serialization test support'
type: module
description: 'Provides test support for entity serialization tests.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,23 @@
<?php
/**
* @file
* Test support module for entity serialization tests.
*/
use Drupal\Core\Access\AccessResult;
/**
* Implements hook_entity_field_access_alter().
*
* Overrides some default access control to support testing.
*
* @see Drupal\serialization\Tests\EntitySerializationTest::testUserNormalize()
*/
function entity_serialization_test_entity_field_access_alter(array &$grants, array $context) {
// Override default access control from UserAccessControlHandler to allow
// access to 'pass' field for the test user.
if ($context['field_definition']->getName() == 'pass' && $context['account']->getUsername() == 'serialization_test_user') {
$grants[':default'] = AccessResult::allowed()->inheritCacheability($grants[':default'])->addCacheableDependency($context['items']->getEntity());
}
}

View file

@ -0,0 +1,6 @@
name: 'FieldItem normalization test support'
type: module
description: 'Provides test support for fieldItem normalization test support.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,6 @@
services:
serializer.normalizer.silly_fielditem:
class: Drupal\field_normalization_test\Normalization\TextItemSillyNormalizer
tags:
# The priority must be higher than serialization.normalizer.field_item.
- { name: normalizer , priority: 9 }

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\field_normalization_test\Normalization;
use Drupal\serialization\Normalizer\FieldItemNormalizer;
use Drupal\text\Plugin\Field\FieldType\TextItemBase;
/**
* A test TextItem normalizer to test denormalization.
*/
class TextItemSillyNormalizer extends FieldItemNormalizer {
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = TextItemBase::class;
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
$data = parent::normalize($object, $format, $context);
$data['value'] .= '::silly_suffix';
return $data;
}
/**
* {@inheritdoc}
*/
protected function constructValue($data, $context) {
$value = parent::constructValue($data, $context);
$value['value'] = str_replace('::silly_suffix', '', $value['value']);
return $value;
}
}

View file

@ -0,0 +1,6 @@
name: Serialization test module
type: module
description: "Support module for serialization tests."
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,9 @@
services:
serializer.normalizer.serialization_test:
class: Drupal\serialization_test\SerializationTestNormalizer
tags:
- { name: normalizer }
serializer.encoder.serialization_test:
class: Drupal\serialization_test\SerializationTestEncoder
tags:
- { name: encoder, format: serialization_test}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\serialization_test;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
class SerializationTestEncoder implements EncoderInterface {
/**
* The format that this Encoder supports.
*
* @var string
*/
static protected $format = 'serialization_test';
/**
* {@inheritdoc}
*/
public function encode($data, $format, array $context = []) {
// @see \Drupal\serialization_test\SerializationTestNormalizer::normalize().
return 'Normalized by ' . $data['normalized_by'] . ', Encoded by SerializationTestEncoder';
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format) {
return static::$format === $format;
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Drupal\serialization_test;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class SerializationTestNormalizer implements NormalizerInterface {
/**
* The format that this Normalizer supports.
*
* @var string
*/
static protected $format = 'serialization_test';
/**
* Normalizes an object into a set of arrays/scalars.
*
* @param object $object
* Object to normalize.
* @param string $format
* Format the normalization result will be encoded as.
*
* @return array
* An array containing a normalized representation of $object, appropriate
* for encoding to the requested format.
*/
public function normalize($object, $format = NULL, array $context = []) {
$normalized = (array) $object;
// Add identifying value that can be used to verify that the expected
// normalizer was invoked.
$normalized['normalized_by'] = 'SerializationTestNormalizer';
return $normalized;
}
/**
* Checks whether format is supported by this normalizer.
*
* @param mixed $data
* Data to normalize.
* @param string $format
* Format the normalization result will be encoded as.
*
* @return bool
* Returns TRUE if the normalizer can handle the request.
*/
public function supportsNormalization($data, $format = NULL) {
return static::$format === $format;
}
}

View file

@ -0,0 +1,98 @@
<?php
namespace Drupal\Tests\serialization\Kernel;
use Drupal\Core\Url;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests that entities references can be resolved.
*
* @group serialization
*/
class EntityResolverTest extends NormalizerTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['hal', 'rest'];
/**
* The format being tested.
*
* @var string
*/
protected $format = 'hal_json';
protected function setUp() {
parent::setUp();
\Drupal::service('router.builder')->rebuild();
// Create the test field storage.
FieldStorageConfig::create([
'entity_type' => 'entity_test_mulrev',
'field_name' => 'field_test_entity_reference',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'entity_test_mulrev',
],
])->save();
// Create the test field.
FieldConfig::create([
'entity_type' => 'entity_test_mulrev',
'field_name' => 'field_test_entity_reference',
'bundle' => 'entity_test_mulrev',
])->save();
}
/**
* Test that fields referencing UUIDs can be denormalized.
*/
public function testUuidEntityResolver() {
// Create an entity to get the UUID from.
$entity = EntityTestMulRev::create(['type' => 'entity_test_mulrev']);
$entity->set('name', 'foobar');
$entity->set('field_test_entity_reference', [['target_id' => 1]]);
$entity->save();
$field_uri = Url::fromUri('base:rest/relation/entity_test_mulrev/entity_test_mulrev/field_test_entity_reference', ['absolute' => TRUE])->toString();
$data = [
'_links' => [
'type' => [
'href' => Url::fromUri('base:rest/type/entity_test_mulrev/entity_test_mulrev', ['absolute' => TRUE])->toString(),
],
$field_uri => [
[
'href' => $entity->url(),
],
],
],
'_embedded' => [
$field_uri => [
[
'_links' => [
'self' => $entity->url(),
],
'uuid' => [
[
'value' => $entity->uuid(),
],
],
],
],
],
];
$denormalized = $this->container->get('serializer')->denormalize($data, 'Drupal\entity_test\Entity\EntityTestMulRev', $this->format);
$field_value = $denormalized->get('field_test_entity_reference')->getValue();
$this->assertEqual($field_value[0]['target_id'], 1, 'Entity reference resolved using UUID.');
}
}

View file

@ -0,0 +1,263 @@
<?php
namespace Drupal\Tests\serialization\Kernel;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
/**
* Tests that entities can be serialized to supported core formats.
*
* @group serialization
*/
class EntitySerializationTest extends NormalizerTestBase {
use BcTimestampNormalizerUnixTestTrait;
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['serialization', 'system', 'field', 'entity_test', 'text', 'filter', 'user', 'entity_serialization_test'];
/**
* The test values.
*
* @var array
*/
protected $values;
/**
* The test entity.
*
* @var \Drupal\Core\Entity\ContentEntityInterface
*/
protected $entity;
/**
* The test user.
*
* @var \Drupal\user\Entity\User
*/
protected $user;
/**
* The serializer service.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
/**
* The class name of the test class.
*
* @var string
*/
protected $entityClass = 'Drupal\entity_test\Entity\EntityTest';
protected function setUp() {
parent::setUp();
// User create needs sequence table.
$this->installSchema('system', ['sequences']);
FilterFormat::create([
'format' => 'my_text_format',
'name' => 'My Text Format',
'filters' => [
'filter_html' => [
'module' => 'filter',
'status' => TRUE,
'weight' => 10,
'settings' => [
'allowed_html' => '<p>',
],
],
'filter_autop' => [
'module' => 'filter',
'status' => TRUE,
'weight' => 10,
'settings' => [],
],
],
])->save();
// Create a test user to use as the entity owner.
$this->user = \Drupal::entityManager()->getStorage('user')->create([
'name' => 'serialization_test_user',
'mail' => 'foo@example.com',
'pass' => '123456',
]);
$this->user->save();
// Create a test entity to serialize.
$test_text_value = $this->randomMachineName();
$this->values = [
'name' => $this->randomMachineName(),
'user_id' => $this->user->id(),
'field_test_text' => [
'value' => $test_text_value,
'format' => 'my_text_format',
],
];
$this->entity = EntityTestMulRev::create($this->values);
$this->entity->save();
$this->serializer = $this->container->get('serializer');
$this->installConfig(['field']);
}
/**
* Test the normalize function.
*/
public function testNormalize() {
$expected = [
'id' => [
['value' => 1],
],
'uuid' => [
['value' => $this->entity->uuid()],
],
'langcode' => [
['value' => 'en'],
],
'name' => [
['value' => $this->values['name']],
],
'type' => [
['value' => 'entity_test_mulrev'],
],
'created' => [
$this->formatExpectedTimestampItemValues($this->entity->created->value),
],
'user_id' => [
[
// id() will return the string value as it comes from the database.
'target_id' => (int) $this->user->id(),
'target_type' => $this->user->getEntityTypeId(),
'target_uuid' => $this->user->uuid(),
'url' => $this->user->url(),
],
],
'revision_id' => [
['value' => 1],
],
'default_langcode' => [
['value' => TRUE],
],
'revision_translation_affected' => [
['value' => TRUE],
],
'non_rev_field' => [],
'non_mul_field' => [],
'field_test_text' => [
[
'value' => $this->values['field_test_text']['value'],
'format' => $this->values['field_test_text']['format'],
'processed' => "<p>{$this->values['field_test_text']['value']}</p>",
],
],
];
$normalized = $this->serializer->normalize($this->entity);
foreach (array_keys($expected) as $fieldName) {
$this->assertSame($expected[$fieldName], $normalized[$fieldName], "Normalization produces expected array for $fieldName.");
}
$this->assertEqual(array_diff_key($normalized, $expected), [], 'No unexpected data is added to the normalized array.');
}
/**
* Tests user normalization, using the entity_serialization_test module to
* override some default access controls.
*/
public function testUserNormalize() {
// Test password isn't available.
$normalized = $this->serializer->normalize($this->user);
$this->assertFalse(array_key_exists('pass', $normalized), '"pass" key does not exist in normalized user');
$this->assertFalse(array_key_exists('mail', $normalized), '"mail" key does not exist in normalized user');
// Test again using our test user, so that our access control override will
// allow password viewing.
$normalized = $this->serializer->normalize($this->user, NULL, ['account' => $this->user]);
// The key 'pass' will now exist, but the password value should be
// normalized to NULL.
$this->assertIdentical($normalized['pass'], [NULL], '"pass" value is normalized to [NULL]');
}
/**
* Test registered Serializer's entity serialization for core's formats.
*/
public function testSerialize() {
// Test that Serializer responds using the ComplexDataNormalizer and
// JsonEncoder. The output of ComplexDataNormalizer::normalize() is tested
// elsewhere, so we can just assume that it works properly here.
$normalized = $this->serializer->normalize($this->entity, 'json');
$expected = Json::encode($normalized);
// Test 'json'.
$actual = $this->serializer->serialize($this->entity, 'json');
$this->assertIdentical($actual, $expected, 'Entity serializes to JSON when "json" is requested.');
$actual = $this->serializer->serialize($normalized, 'json');
$this->assertIdentical($actual, $expected, 'A normalized array serializes to JSON when "json" is requested');
// Test 'ajax'.
$actual = $this->serializer->serialize($this->entity, 'ajax');
$this->assertIdentical($actual, $expected, 'Entity serializes to JSON when "ajax" is requested.');
$actual = $this->serializer->serialize($normalized, 'ajax');
$this->assertIdentical($actual, $expected, 'A normalized array serializes to JSON when "ajax" is requested');
// Generate the expected xml in a way that allows changes to entity property
// order.
$expected_created = $this->formatExpectedTimestampItemValues($this->entity->created->value);
$expected = [
'id' => '<id><value>' . $this->entity->id() . '</value></id>',
'uuid' => '<uuid><value>' . $this->entity->uuid() . '</value></uuid>',
'langcode' => '<langcode><value>en</value></langcode>',
'name' => '<name><value>' . $this->values['name'] . '</value></name>',
'type' => '<type><value>entity_test_mulrev</value></type>',
'created' => '<created><value>' . $expected_created['value'] . '</value><format>' . $expected_created['format'] . '</format></created>',
'user_id' => '<user_id><target_id>' . $this->user->id() . '</target_id><target_type>' . $this->user->getEntityTypeId() . '</target_type><target_uuid>' . $this->user->uuid() . '</target_uuid><url>' . $this->user->url() . '</url></user_id>',
'revision_id' => '<revision_id><value>' . $this->entity->getRevisionId() . '</value></revision_id>',
'default_langcode' => '<default_langcode><value>1</value></default_langcode>',
'revision_translation_affected' => '<revision_translation_affected><value>1</value></revision_translation_affected>',
'non_mul_field' => '<non_mul_field/>',
'non_rev_field' => '<non_rev_field/>',
'field_test_text' => '<field_test_text><value>' . $this->values['field_test_text']['value'] . '</value><format>' . $this->values['field_test_text']['format'] . '</format><processed><![CDATA[<p>' . $this->values['field_test_text']['value'] . '</p>]]></processed></field_test_text>',
];
// Sort it in the same order as normalised.
$expected = array_merge($normalized, $expected);
// Add header and footer.
array_unshift($expected, '<?xml version="1.0"?>' . PHP_EOL . '<response>');
$expected[] = '</response>' . PHP_EOL;
// Reduced the array to a string.
$expected = implode('', $expected);
// Test 'xml'. The output should match that of Symfony's XmlEncoder.
$actual = $this->serializer->serialize($this->entity, 'xml');
$this->assertIdentical($actual, $expected);
$actual = $this->serializer->serialize($normalized, 'xml');
$this->assertIdentical($actual, $expected);
}
/**
* Tests denormalization of an entity.
*/
public function testDenormalize() {
$normalized = $this->serializer->normalize($this->entity);
foreach (['json', 'xml'] as $type) {
$denormalized = $this->serializer->denormalize($normalized, $this->entityClass, $type, ['entity_type' => 'entity_test_mulrev']);
$this->assertTrue($denormalized instanceof $this->entityClass, new FormattableMarkup('Denormalized entity is an instance of @class', ['@class' => $this->entityClass]));
$this->assertIdentical($denormalized->getEntityTypeId(), $this->entity->getEntityTypeId(), 'Expected entity type found.');
$this->assertIdentical($denormalized->bundle(), $this->entity->bundle(), 'Expected entity bundle found.');
$this->assertIdentical($denormalized->uuid(), $this->entity->uuid(), 'Expected entity UUID found.');
}
}
}

View file

@ -0,0 +1,135 @@
<?php
namespace Drupal\Tests\serialization\Kernel;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Test field level normalization process.
*
* @group serialization
*/
class FieldItemSerializationTest extends NormalizerTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['serialization', 'system', 'field', 'entity_test', 'text', 'filter', 'user', 'field_normalization_test'];
/**
* The class name of the test class.
*
* @var string
*/
protected $entityClass = 'Drupal\entity_test\Entity\EntityTestMulRev';
/**
* The test values.
*
* @var array
*/
protected $values;
/**
* The test entity.
*
* @var \Drupal\Core\Entity\ContentEntityBase
*/
protected $entity;
/**
* The serializer service.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Auto-create a field for testing default field values.
FieldStorageConfig::create([
'entity_type' => 'entity_test_mulrev',
'field_name' => 'field_test_text_default',
'type' => 'text',
'cardinality' => 1,
'translatable' => FALSE,
])->save();
FieldConfig::create([
'entity_type' => 'entity_test_mulrev',
'field_name' => 'field_test_text_default',
'bundle' => 'entity_test_mulrev',
'label' => 'Test text-field with default',
'default_value' => [
[
'value' => 'This is the default',
'format' => 'full_html',
],
],
'widget' => [
'type' => 'text_textfield',
'weight' => 0,
],
])->save();
// Create a test entity to serialize.
$this->values = [
'name' => $this->randomMachineName(),
'field_test_text' => [
'value' => $this->randomMachineName(),
'format' => 'full_html',
],
];
$this->entity = EntityTestMulRev::create($this->values);
$this->entity->save();
$this->serializer = $this->container->get('serializer');
$this->installConfig(['field']);
}
/**
* Tests normalizing and denormalizing an entity with field item normalizer.
*/
public function testFieldNormalizeDenormalize() {
$normalized = $this->serializer->normalize($this->entity, 'json');
$expected_field_value = $this->entity->field_test_text[0]->getValue()['value'] . '::silly_suffix';
$this->assertEquals($expected_field_value, $normalized['field_test_text'][0]['value'], 'Text field item normalized');
$denormalized = $this->serializer->denormalize($normalized, $this->entityClass, 'json');
$this->assertEquals($denormalized->field_test_text[0]->getValue(), $this->entity->field_test_text[0]->getValue(), 'Text field item denormalized.');
$this->assertEquals($denormalized->field_test_text_default[0]->getValue(), $this->entity->field_test_text_default[0]->getValue(), 'Text field item with default denormalized.');
// Unset the values for text field that has a default value.
unset($normalized['field_test_text_default']);
$denormalized_without_all_fields = $this->serializer->denormalize($normalized, $this->entityClass, 'json');
// Check that denormalized entity is still the same even if not all fields
// are not provided.
$this->assertEquals($denormalized_without_all_fields->field_test_text[0]->getValue(), $this->entity->field_test_text[0]->getValue(), 'Text field item denormalized.');
// Even though field_test_text_default value was unset before
// denormalization it should still have the default values for the field.
$this->assertEquals($denormalized_without_all_fields->field_test_text_default[0]->getValue(), $this->entity->field_test_text_default[0]->getValue(), 'Text field item with default denormalized.');
}
/**
* Tests denormalizing using a scalar field value.
*/
public function testFieldDenormalizeWithScalarValue() {
$this->setExpectedException(UnexpectedValueException::class, 'Field values for "uuid" must use an array structure');
$normalized = $this->serializer->normalize($this->entity, 'json');
// Change the UUID value to use the UUID directly. No array structure.
$normalized['uuid'] = $normalized['uuid'][0]['value'];
$this->serializer->denormalize($normalized, $this->entityClass, 'json');
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace Drupal\Tests\serialization\Kernel;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\MapDataDefinition;
use Drupal\KernelTests\KernelTestBase;
/**
* @group typedData
*/
class MapDataNormalizerTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'serialization'];
/**
* The serializer service.
*
* @var \Symfony\Component\Serializer\Serializer
*/
protected $serializer;
/**
* The typed data manager.
*
* @var \Drupal\Core\TypedData\TypedDataManagerInterface
*/
protected $typedDataManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->serializer = \Drupal::service('serializer');
$this->typedDataManager = \Drupal::typedDataManager();
}
/**
* Tests whether map data can be normalized.
*/
public function testMapNormalize() {
$typed_data = $this->buildExampleTypedData();
$data = $this->serializer->normalize($typed_data, 'json');
$expect_value = [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 3,
'key4' => [
0 => TRUE,
1 => 'value6',
'key7' => 'value7',
],
];
$this->assertSame($expect_value, $data);
}
/**
* Test whether map data with properties can be normalized.
*/
public function testMapWithPropertiesNormalize() {
$typed_data = $this->buildExampleTypedDataWithProperties();
$data = $this->serializer->normalize($typed_data, 'json');
$expect_value = [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 3,
'key4' => [
0 => TRUE,
1 => 'value6',
'key7' => 'value7',
],
];
$this->assertSame($expect_value, $data);
}
/**
* Builds some example typed data object with no properties.
*/
protected function buildExampleTypedData() {
$tree = [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 3,
'key4' => [
0 => TRUE,
1 => 'value6',
'key7' => 'value7',
],
];
$map_data_definition = MapDataDefinition::create();
$typed_data = $this->typedDataManager->create(
$map_data_definition,
$tree,
'test name'
);
return $typed_data;
}
/**
* Builds some example typed data object with properties.
*/
protected function buildExampleTypedDataWithProperties() {
$tree = [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 3,
'key4' => [
0 => TRUE,
1 => 'value6',
'key7' => 'value7',
],
];
$map_data_definition = MapDataDefinition::create()
->setPropertyDefinition('key1', DataDefinition::create('string'))
->setPropertyDefinition('key2', DataDefinition::create('string'))
->setPropertyDefinition('key3', DataDefinition::create('integer'))
->setPropertyDefinition('key4', MapDataDefinition::create()
->setPropertyDefinition(0, DataDefinition::create('boolean'))
->setPropertyDefinition(1, DataDefinition::create('string'))
->setPropertyDefinition('key7', DataDefinition::create('string'))
);
$typed_data = $this->typedDataManager->create(
$map_data_definition,
$tree,
'test name'
);
return $typed_data;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\Tests\serialization\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Helper base class to set up some test fields for serialization testing.
*/
abstract class NormalizerTestBase extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['serialization', 'system', 'field', 'entity_test', 'text', 'filter', 'user'];
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_mulrev');
$this->installEntitySchema('user');
$this->installConfig(['field']);
\Drupal::service('router.builder')->rebuild();
\Drupal::moduleHandler()->invoke('rest', 'install');
// Auto-create a field for testing.
FieldStorageConfig::create([
'entity_type' => 'entity_test_mulrev',
'field_name' => 'field_test_text',
'type' => 'text',
'cardinality' => 1,
'translatable' => FALSE,
])->save();
FieldConfig::create([
'entity_type' => 'entity_test_mulrev',
'field_name' => 'field_test_text',
'bundle' => 'entity_test_mulrev',
'label' => 'Test text-field',
'widget' => [
'type' => 'text_textfield',
'weight' => 0,
],
])->save();
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\serialization\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Functional tests for serialization system.
*
* @group serialization
*/
class SerializationTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['serialization', 'serialization_test'];
/**
* The serializer service to test.
*
* @var \Symfony\Component\Serializer\SerializerInterface
*/
protected $serializer;
protected function setUp() {
parent::setUp();
$this->serializer = $this->container->get('serializer');
}
/**
* Confirms that modules can register normalizers and encoders.
*/
public function testSerializerComponentRegistration() {
$object = new \stdClass();
$format = 'serialization_test';
$expected = 'Normalized by SerializationTestNormalizer, Encoded by SerializationTestEncoder';
// Ensure the serialization invokes the expected normalizer and encoder.
$this->assertIdentical($this->serializer->serialize($object, $format), $expected);
// Ensure the serialization fails for an unsupported format.
try {
$this->serializer->serialize($object, 'unsupported_format');
$this->fail('The serializer was expected to throw an exception for an unsupported format, but did not.');
}
catch (UnexpectedValueException $e) {
$this->pass('The serializer threw an exception for an unsupported format.');
}
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Drupal\Tests\serialization\Unit\CompilerPass;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\serialization\RegisterSerializationClassesCompilerPass;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Serializer\Serializer;
/**
* @coversDefaultClass \Drupal\serialization\RegisterSerializationClassesCompilerPass
* @group serialization
*/
class RegisterSerializationClassesCompilerPassTest extends UnitTestCase {
/**
* @covers ::process
*/
public function testEncoders() {
$container = new ContainerBuilder();
$container->setDefinition('serializer', new Definition(Serializer::class, [[], []]));
$encoder_1_definition = new Definition('TestClass');
$encoder_1_definition->addTag('encoder', ['format' => 'xml']);
$encoder_1_definition->addTag('_provider', ['provider' => 'test_provider_a']);
$container->setDefinition('encoder_1', $encoder_1_definition);
$encoder_2_definition = new Definition('TestClass');
$encoder_2_definition->addTag('encoder', ['format' => 'json']);
$encoder_2_definition->addTag('_provider', ['provider' => 'test_provider_a']);
$container->setDefinition('encoder_2', $encoder_2_definition);
$encoder_3_definition = new Definition('TestClass');
$encoder_3_definition->addTag('encoder', ['format' => 'hal_json']);
$encoder_3_definition->addTag('_provider', ['provider' => 'test_provider_b']);
$container->setDefinition('encoder_3', $encoder_3_definition);
$normalizer_1_definition = new Definition('TestClass');
$normalizer_1_definition->addTag('normalizer');
$container->setDefinition('normalizer_1', $normalizer_1_definition);
$compiler_pass = new RegisterSerializationClassesCompilerPass();
$compiler_pass->process($container);
// Check registration of formats and providers.
$this->assertEquals(['xml', 'json', 'hal_json'], $container->getParameter('serializer.formats'));
$this->assertEquals(['xml' => 'test_provider_a', 'json' => 'test_provider_a', 'hal_json' => 'test_provider_b'], $container->getParameter('serializer.format_providers'));
// Check all encoder and normalizer service definitions are marked private.
$this->assertFalse($encoder_1_definition->isPublic());
$this->assertFalse($encoder_2_definition->isPublic());
$this->assertFalse($encoder_3_definition->isPublic());
$this->assertFalse($normalizer_1_definition->isPublic());
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Drupal\Tests\serialization\Unit\Encoder;
use Drupal\serialization\Encoder\JsonEncoder;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\serialization\Encoder\JsonEncoder
* @group serialization
*/
class JsonEncoderTest extends UnitTestCase {
/**
* Tests the supportsEncoding() method.
*/
public function testSupportsEncoding() {
$encoder = new JsonEncoder();
$this->assertTrue($encoder->supportsEncoding('json'));
$this->assertTrue($encoder->supportsEncoding('ajax'));
$this->assertFalse($encoder->supportsEncoding('xml'));
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\Tests\serialization\Unit\Encoder;
use Drupal\serialization\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder as BaseXmlEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\serialization\Encoder\XmlEncoder
* @group serialization
*/
class XmlEncoderTest extends UnitTestCase {
/**
* The XmlEncoder instance.
*
* @var \Drupal\serialization\Encoder\XmlEncoder
*/
protected $encoder;
/**
* @var \Symfony\Component\Serializer\Encoder\XmlEncoder|\PHPUnit_Framework_MockObject_MockObject
*/
protected $baseEncoder;
/**
* An array of test data.
*
* @var array
*/
protected $testArray = ['test' => 'test'];
protected function setUp() {
$this->baseEncoder = $this->getMock(BaseXmlEncoder::class);
$this->encoder = new XmlEncoder();
$this->encoder->setBaseEncoder($this->baseEncoder);
}
/**
* Tests the supportsEncoding() method.
*/
public function testSupportsEncoding() {
$this->assertTrue($this->encoder->supportsEncoding('xml'));
$this->assertFalse($this->encoder->supportsEncoding('json'));
}
/**
* Tests the supportsDecoding() method.
*/
public function testSupportsDecoding() {
$this->assertTrue($this->encoder->supportsDecoding('xml'));
$this->assertFalse($this->encoder->supportsDecoding('json'));
}
/**
* Tests the encode() method.
*/
public function testEncode() {
$this->baseEncoder->expects($this->once())
->method('encode')
->with($this->testArray, 'test', [])
->will($this->returnValue('test'));
$this->assertEquals('test', $this->encoder->encode($this->testArray, 'test'));
}
/**
* Tests the decode() method.
*/
public function testDecode() {
$this->baseEncoder->expects($this->once())
->method('decode')
->with('test', 'test', [])
->will($this->returnValue($this->testArray));
$this->assertEquals($this->testArray, $this->encoder->decode('test', 'test'));
}
/**
* @covers ::getBaseEncoder
*/
public function testDefaultEncoderHasSerializer() {
// The serializer should be set on the Drupal encoder, which should then
// set it on our default encoder.
$encoder = new XmlEncoder();
$serialzer = new Serializer([new GetSetMethodNormalizer()]);
$encoder->setSerializer($serialzer);
$base_encoder = $encoder->getBaseEncoder();
$this->assertInstanceOf(BaseXmlEncoder::class, $base_encoder);
// Test the encoder.
$base_encoder->encode(['a' => new TestObject()], 'xml');
}
}
class TestObject {
public function getA() {
return 'A';
}
}

View file

@ -0,0 +1,153 @@
<?php
namespace Drupal\Tests\serialization\Unit\EntityResolver;
use Drupal\Tests\UnitTestCase;
use Drupal\serialization\EntityResolver\ChainEntityResolver;
/**
* @coversDefaultClass \Drupal\serialization\EntityResolver\ChainEntityResolver
* @group serialization
*/
class ChainEntityResolverTest extends UnitTestCase {
/**
* A mocked normalizer.
*
* @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $testNormalizer;
/**
* Test data passed to the resolve method.
*
* @var \stdClass
*/
protected $testData;
/**
* A test entity type.
*
* @var string
*/
protected $testEntityType = 'test_type';
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->testNormalizer = $this->getMock('Symfony\Component\Serializer\Normalizer\NormalizerInterface');
$this->testData = new \stdClass();
}
/**
* Test the resolve method with no matching resolvers.
*
* @covers ::__construct
* @covers ::resolve
*/
public function testResolverWithNoneResolved() {
$resolvers = [
$this->createEntityResolverMock(),
$this->createEntityResolverMock(),
];
$resolver = new ChainEntityResolver($resolvers);
$this->assertNull($resolver->resolve($this->testNormalizer, $this->testData, $this->testEntityType));
}
/**
* Test the resolve method with no matching resolvers, using addResolver.
*
* @covers ::addResolver
* @covers ::resolve
*/
public function testResolverWithNoneResolvedUsingAddResolver() {
$resolver = new ChainEntityResolver();
$resolver->addResolver($this->createEntityResolverMock());
$resolver->addResolver($this->createEntityResolverMock());
$this->assertNull($resolver->resolve($this->testNormalizer, $this->testData, $this->testEntityType));
}
/**
* Test the resolve method with a matching resolver first.
*
* @covers ::__construct
* @covers ::resolve
*/
public function testResolverWithFirstResolved() {
$resolvers = [
$this->createEntityResolverMock(10),
$this->createEntityResolverMock(NULL, FALSE),
];
$resolver = new ChainEntityResolver($resolvers);
$this->assertSame(10, $resolver->resolve($this->testNormalizer, $this->testData, $this->testEntityType));
}
/**
* Test the resolve method with a matching resolver last.
*
* @covers ::__construct
* @covers ::resolve
*/
public function testResolverWithLastResolved() {
$resolvers = [
$this->createEntityResolverMock(),
$this->createEntityResolverMock(10),
];
$resolver = new ChainEntityResolver($resolvers);
$this->assertSame(10, $resolver->resolve($this->testNormalizer, $this->testData, $this->testEntityType));
}
/**
* Test the resolve method where one resolver returns 0.
*
* @covers ::__construct
* @covers ::resolve
*/
public function testResolverWithResolvedToZero() {
$resolvers = [
$this->createEntityResolverMock(0),
$this->createEntityResolverMock(NULL, FALSE),
];
$resolver = new ChainEntityResolver($resolvers);
$this->assertSame(0, $resolver->resolve($this->testNormalizer, $this->testData, $this->testEntityType));
}
/**
* Creates a mock entity resolver.
*
* @param null|int $return
* Whether the mocked resolve method should return TRUE or FALSE.
* @param bool $called
* Whether or not the resolve method is expected to be called.
*
* @return \Drupal\serialization\EntityResolver\EntityResolverInterface|\PHPUnit_Framework_MockObject_MockObject
* The mocked entity resolver.
*/
protected function createEntityResolverMock($return = NULL, $called = TRUE) {
$mock = $this->getMock('Drupal\serialization\EntityResolver\EntityResolverInterface');
if ($called) {
$mock->expects($this->once())
->method('resolve')
->with($this->testNormalizer, $this->testData, $this->testEntityType)
->will($this->returnValue($return));
}
else {
$mock->expects($this->never())
->method('resolve');
}
return $mock;
}
}

View file

@ -0,0 +1,110 @@
<?php
namespace Drupal\Tests\serialization\Unit\EntityResolver;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\serialization\EntityResolver\UuidResolver;
/**
* @coversDefaultClass \Drupal\serialization\EntityResolver\UuidResolver
* @group serialization
*/
class UuidResolverTest extends UnitTestCase {
/**
* The UuidResolver instance.
*
* @var \Drupal\serialization\EntityResolver\UuidResolver
*/
protected $resolver;
/**
* The mock EntityManager instance.
*
* @var \Drupal\Core\Entity\EntityManager|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityManager = $this->getMockBuilder(EntityRepositoryInterface::class)
->disableOriginalConstructor()
->getMock();
$this->resolver = new UuidResolver($this->entityManager);
}
/**
* Test resolve() with a class using the incorrect interface.
*/
public function testResolveNotInInterface() {
$this->entityManager->expects($this->never())
->method('loadEntityByUuid');
$normalizer = $this->getMock('Symfony\Component\Serializer\Normalizer\NormalizerInterface');
$this->assertNull($this->resolver->resolve($normalizer, [], 'test_type'));
}
/**
* Test resolve() with a class using the correct interface but no UUID.
*/
public function testResolveNoUuid() {
$this->entityManager->expects($this->never())
->method('loadEntityByUuid');
$normalizer = $this->getMock('Drupal\serialization\EntityResolver\UuidReferenceInterface');
$normalizer->expects($this->once())
->method('getUuid')
->with([])
->will($this->returnValue(NULL));
$this->assertNull($this->resolver->resolve($normalizer, [], 'test_type'));
}
/**
* Test resolve() with correct interface but no matching entity for the UUID.
*/
public function testResolveNoEntity() {
$uuid = '392eab92-35c2-4625-872d-a9dab4da008e';
$this->entityManager->expects($this->once())
->method('loadEntityByUuid')
->with('test_type')
->will($this->returnValue(NULL));
$normalizer = $this->getMock('Drupal\serialization\EntityResolver\UuidReferenceInterface');
$normalizer->expects($this->once())
->method('getUuid')
->with([])
->will($this->returnValue($uuid));
$this->assertNull($this->resolver->resolve($normalizer, [], 'test_type'));
}
/**
* Test resolve() when a UUID corresponds to an entity.
*/
public function testResolveWithEntity() {
$uuid = '392eab92-35c2-4625-872d-a9dab4da008e';
$entity = $this->getMock('Drupal\Core\Entity\EntityInterface');
$entity->expects($this->once())
->method('id')
->will($this->returnValue(1));
$this->entityManager->expects($this->once())
->method('loadEntityByUuid')
->with('test_type', $uuid)
->will($this->returnValue($entity));
$normalizer = $this->getMock('Drupal\serialization\EntityResolver\UuidReferenceInterface');
$normalizer->expects($this->once())
->method('getUuid')
->with([])
->will($this->returnValue($uuid));
$this->assertSame(1, $this->resolver->resolve($normalizer, [], 'test_type'));
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\Tests\serialization\Unit\EventSubscriber;
use Drupal\serialization\Encoder\JsonEncoder;
use Drupal\serialization\EventSubscriber\DefaultExceptionSubscriber;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Serializer\Serializer;
/**
* @coversDefaultClass \Drupal\serialization\EventSubscriber\DefaultExceptionSubscriber
* @group serialization
*/
class DefaultExceptionSubscriberTest extends UnitTestCase {
/**
* @covers ::on4xx
*/
public function testOn4xx() {
$kernel = $this->prophesize(HttpKernelInterface::class);
$request = Request::create('/test');
$request->setRequestFormat('json');
$e = new MethodNotAllowedHttpException(['POST', 'PUT'], 'test message');
$event = new GetResponseForExceptionEvent($kernel->reveal(), $request, 'GET', $e);
$subscriber = new DefaultExceptionSubscriber(new Serializer([], [new JsonEncoder()]), []);
$subscriber->on4xx($event);
$response = $event->getResponse();
$this->assertInstanceOf(Response::class, $response);
$this->assertEquals('{"message":"test message"}', $response->getContent());
$this->assertEquals(405, $response->getStatusCode());
$this->assertEquals('POST, PUT', $response->headers->get('Allow'));
$this->assertEquals('application/json', $response->headers->get('Content-Type'));
}
}

View file

@ -0,0 +1,121 @@
<?php
/**
* @file
* Contains \Drupal\Tests\serialization\Unit\Normalizer\ComplexDataNormalizerTest.
*/
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\serialization\Normalizer\ComplexDataNormalizer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Serializer\Serializer;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\ComplexDataNormalizer
* @group serialization
*/
class ComplexDataNormalizerTest extends UnitTestCase {
use InternalTypedDataTestTrait;
/**
* Test format string.
*
* @var string
*/
const TEST_FORMAT = 'test_format';
/**
* The Complex data normalizer under test.
*
* @var \Drupal\serialization\Normalizer\ComplexDataNormalizer
*/
protected $normalizer;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->normalizer = new ComplexDataNormalizer();
}
/**
* @covers ::supportsNormalization
*/
public function testSupportsNormalization() {
$complex_data = $this->prophesize(ComplexDataInterface::class)->reveal();
$this->assertTrue($this->normalizer->supportsNormalization($complex_data));
// Also test that an object not implementing ComplexDataInterface fails.
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
/**
* Test normalizing complex data.
*
* @covers ::normalize
*/
public function testNormalizeComplexData() {
$serializer_prophecy = $this->prophesize(Serializer::class);
$non_internal_property = $this->getTypedDataProperty(FALSE);
$serializer_prophecy->normalize($non_internal_property, static::TEST_FORMAT, [])
->willReturn('A-normalized')
->shouldBeCalled();
$this->normalizer->setSerializer($serializer_prophecy->reveal());
$complex_data = $this->prophesize(ComplexDataInterface::class);
$complex_data->getProperties(TRUE)
->willReturn([
'prop:a' => $non_internal_property,
'prop:internal' => $this->getTypedDataProperty(TRUE),
])
->shouldBeCalled();
$normalized = $this->normalizer->normalize($complex_data->reveal(), static::TEST_FORMAT);
$this->assertEquals(['prop:a' => 'A-normalized'], $normalized);
}
/**
* Test normalize() where $object does not implement ComplexDataInterface.
*
* Normalizers extending ComplexDataNormalizer may have a different supported
* class.
*
* @covers ::normalize
*/
public function testNormalizeNonComplex() {
$normalizer = new TestExtendedNormalizer();
$serialization_context = ['test' => 'test'];
$serializer_prophecy = $this->prophesize(Serializer::class);
$serializer_prophecy->normalize('A', static::TEST_FORMAT, $serialization_context)
->willReturn('A-normalized')
->shouldBeCalled();
$serializer_prophecy->normalize('B', static::TEST_FORMAT, $serialization_context)
->willReturn('B-normalized')
->shouldBeCalled();
$normalizer->setSerializer($serializer_prophecy->reveal());
$stdClass = new \stdClass();
$stdClass->a = 'A';
$stdClass->b = 'B';
$normalized = $normalizer->normalize($stdClass, static::TEST_FORMAT, $serialization_context);
$this->assertEquals(['a' => 'A-normalized', 'b' => 'B-normalized'], $normalized);
}
}
/**
* Test normalizer with a different supported class.
*/
class TestExtendedNormalizer extends ComplexDataNormalizer {
protected $supportedInterfaceOrClass = \stdClass::class;
}

View file

@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\serialization\Normalizer\ConfigEntityNormalizer;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\ConfigEntityNormalizer
* @group serialization
*/
class ConfigEntityNormalizerTest extends UnitTestCase {
/**
* Tests the normalize() method.
*
* @covers ::normalize
*/
public function testNormalize() {
$test_export_properties = [
'test' => 'test',
'_core' => [
'default_config_hash' => $this->randomMachineName(),
$this->randomMachineName() => 'some random key',
],
];
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$normalizer = new ConfigEntityNormalizer($entity_manager);
$config_entity = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityInterface');
$config_entity->expects($this->once())
->method('toArray')
->will($this->returnValue($test_export_properties));
$this->assertSame(['test' => 'test'], $normalizer->normalize($config_entity));
}
/**
* @covers ::denormalize
*/
public function testDenormalize() {
$test_value = $this->randomMachineName();
$data = [
'test' => $test_value,
'_core' => [
'default_config_hash' => $this->randomMachineName(),
$this->randomMachineName() => 'some random key',
],
];
$expected_storage_data = [
'test' => $test_value,
];
// Mock of the entity storage, to test our expectation that the '_core' key
// never makes it to that point, thanks to the denormalizer omitting it.
$entity_storage = $this->prophesize(EntityStorageInterface::class);
$entity_storage->create($expected_storage_data)
->shouldBeCalled()
->will(function ($args) {
$entity = new \stdClass();
$entity->received_data = $args[0];
return $entity;
});
// Stubs for the denormalizer going from entity manager to entity storage.
$entity_type_id = $this->randomMachineName();
$entity_type_class = $this->randomMachineName();
$entity_manager = $this->prophesize(EntityManagerInterface::class);
$entity_manager->getEntityTypeFromClass($entity_type_class)
->willReturn($entity_type_id);
$entity_manager->getDefinition($entity_type_id, FALSE)
->willReturn($this->prophesize(ConfigEntityTypeInterface::class)->reveal());
$entity_manager->getStorage($entity_type_id)
->willReturn($entity_storage->reveal());
$normalizer = new ConfigEntityNormalizer($entity_manager->reveal());
// Verify the denormalizer still works correctly: the mock above creates an
// artificial entity object containing exactly the data it received. It also
// should still set _restSubmittedFields correctly.
$expected_denormalization = (object) [
'_restSubmittedFields' => [
'test',
],
'received_data' => [
'test' => $test_value,
],
];
$this->assertEquals($expected_denormalization, $normalizer->denormalize($data, $entity_type_class, 'json'));
}
}

View file

@ -0,0 +1,171 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\serialization\Normalizer\ContentEntityNormalizer;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\ContentEntityNormalizer
* @group serialization
*/
class ContentEntityNormalizerTest extends UnitTestCase {
/**
* The mock entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* The mock serializer.
*
* @var \Symfony\Component\Serializer\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $serializer;
/**
* The normalizer under test.
*
* @var \Drupal\serialization\Normalizer\ContentEntityNormalizer
*/
protected $contentEntityNormalizer;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->contentEntityNormalizer = new ContentEntityNormalizer($this->entityManager);
$this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')
->disableOriginalConstructor()
->setMethods(['normalize'])
->getMock();
$this->contentEntityNormalizer->setSerializer($this->serializer);
}
/**
* @covers ::supportsNormalization
*/
public function testSupportsNormalization() {
$content_mock = $this->getMock('Drupal\Core\Entity\ContentEntityInterface');
$config_mock = $this->getMock('Drupal\Core\Entity\ConfigEntityInterface');
$this->assertTrue($this->contentEntityNormalizer->supportsNormalization($content_mock));
$this->assertFalse($this->contentEntityNormalizer->supportsNormalization($config_mock));
}
/**
* Tests the normalize() method.
*
* @covers ::normalize
*/
public function testNormalize() {
$this->serializer->expects($this->any())
->method('normalize')
->with($this->containsOnlyInstancesOf('Drupal\Core\Field\FieldItemListInterface'), 'test_format', ['account' => NULL])
->will($this->returnValue('test'));
$definitions = [
'field_accessible_external' => $this->createMockFieldListItem(TRUE, FALSE),
'field_non-accessible_external' => $this->createMockFieldListItem(FALSE, FALSE),
'field_accessible_internal' => $this->createMockFieldListItem(TRUE, TRUE),
'field_non-accessible_internal' => $this->createMockFieldListItem(FALSE, TRUE),
];
$content_entity_mock = $this->createMockForContentEntity($definitions);
$normalized = $this->contentEntityNormalizer->normalize($content_entity_mock, 'test_format');
$this->assertArrayHasKey('field_accessible_external', $normalized);
$this->assertEquals('test', $normalized['field_accessible_external']);
$this->assertArrayNotHasKey('field_non-accessible_external', $normalized);
$this->assertArrayNotHasKey('field_accessible_internal', $normalized);
$this->assertArrayNotHasKey('field_non-accessible_internal', $normalized);
}
/**
* Tests the normalize() method with account context passed.
*
* @covers ::normalize
*/
public function testNormalizeWithAccountContext() {
$mock_account = $this->getMock('Drupal\Core\Session\AccountInterface');
$context = [
'account' => $mock_account,
];
$this->serializer->expects($this->any())
->method('normalize')
->with($this->containsOnlyInstancesOf('Drupal\Core\Field\FieldItemListInterface'), 'test_format', $context)
->will($this->returnValue('test'));
// The mock account should get passed directly into the access() method on
// field items from $context['account'].
$definitions = [
'field_1' => $this->createMockFieldListItem(TRUE, FALSE, $mock_account),
'field_2' => $this->createMockFieldListItem(FALSE, FALSE, $mock_account),
];
$content_entity_mock = $this->createMockForContentEntity($definitions);
$normalized = $this->contentEntityNormalizer->normalize($content_entity_mock, 'test_format', $context);
$this->assertArrayHasKey('field_1', $normalized);
$this->assertEquals('test', $normalized['field_1']);
$this->assertArrayNotHasKey('field_2', $normalized);
}
/**
* Creates a mock content entity.
*
* @param $definitions
*
* @return \PHPUnit_Framework_MockObject_MockObject
*/
public function createMockForContentEntity($definitions) {
$content_entity_mock = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
->disableOriginalConstructor()
->setMethods(['getTypedData'])
->getMockForAbstractClass();
$typed_data = $this->prophesize(ComplexDataInterface::class);
$typed_data->getProperties(TRUE)
->willReturn($definitions)
->shouldBeCalled();
$content_entity_mock->expects($this->any())
->method('getTypedData')
->will($this->returnValue($typed_data->reveal()));
return $content_entity_mock;
}
/**
* Creates a mock field list item.
*
* @param bool $access
* @param bool $internal
* @param \Drupal\Core\Session\AccountInterface $user_context
*
* @return \Drupal\Core\Field\FieldItemListInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected function createMockFieldListItem($access, $internal, AccountInterface $user_context = NULL) {
$data_definition = $this->prophesize(DataDefinitionInterface::class);
$mock = $this->getMock('Drupal\Core\Field\FieldItemListInterface');
$mock->expects($this->once())
->method('getDataDefinition')
->will($this->returnValue($data_definition->reveal()));
$data_definition->isInternal()
->willReturn($internal)
->shouldBeCalled();
if (!$internal) {
$mock->expects($this->once())
->method('access')
->with('view', $user_context)
->will($this->returnValue($access));
}
return $mock;
}
}

View file

@ -0,0 +1,408 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\serialization\Normalizer\EntityNormalizer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\EntityNormalizer
* @group serialization
*/
class EntityNormalizerTest extends UnitTestCase {
/**
* The mock entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* The mock serializer.
*
* @var \Symfony\Component\Serializer\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $serializer;
/**
* The entity normalizer.
*
* @var \Drupal\serialization\Normalizer\EntityNormalizer
*/
protected $entityNormalizer;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->entityNormalizer = new EntityNormalizer($this->entityManager);
}
/**
* Tests the normalize() method.
*
* @covers ::normalize
*/
public function testNormalize() {
$list_item_1 = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$list_item_2 = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$definitions = [
'field_1' => $list_item_1,
'field_2' => $list_item_2,
];
$content_entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
->disableOriginalConstructor()
->setMethods(['getFields'])
->getMockForAbstractClass();
$content_entity->expects($this->once())
->method('getFields')
->will($this->returnValue($definitions));
$serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')
->disableOriginalConstructor()
->setMethods(['normalize'])
->getMock();
$serializer->expects($this->at(0))
->method('normalize')
->with($list_item_1, 'test_format');
$serializer->expects($this->at(1))
->method('normalize')
->with($list_item_2, 'test_format');
$this->entityNormalizer->setSerializer($serializer);
$this->entityNormalizer->normalize($content_entity, 'test_format');
}
/**
* Tests the denormalize() method with no entity type provided in context.
*
* @covers ::denormalize
*/
public function testDenormalizeWithNoEntityType() {
$this->setExpectedException(UnexpectedValueException::class);
$this->entityNormalizer->denormalize([], 'Drupal\Core\Entity\ContentEntityBase');
}
/**
* Tests the denormalize method with a bundle property.
*
* @covers ::denormalize
*/
public function testDenormalizeWithValidBundle() {
$test_data = [
'key_1' => 'value_1',
'key_2' => 'value_2',
'test_type' => [
['name' => 'test_bundle'],
],
];
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type->expects($this->once())
->method('id')
->willReturn('test');
$entity_type->expects($this->once())
->method('hasKey')
->with('bundle')
->will($this->returnValue(TRUE));
$entity_type->expects($this->once())
->method('getKey')
->with('bundle')
->will($this->returnValue('test_type'));
$entity_type->expects($this->once())
->method('entityClassImplements')
->with(FieldableEntityInterface::class)
->willReturn(TRUE);
$entity_type->expects($this->once())
->method('getBundleEntityType')
->will($this->returnValue('test_bundle'));
$entity_type_storage_definition = $this->getmock('Drupal\Core\Field\FieldStorageDefinitionInterface');
$entity_type_storage_definition->expects($this->once())
->method('getMainPropertyName')
->will($this->returnValue('name'));
$entity_type_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
$entity_type_definition->expects($this->once())
->method('getFieldStorageDefinition')
->will($this->returnValue($entity_type_storage_definition));
$base_definitions = [
'test_type' => $entity_type_definition,
];
$this->entityManager->expects($this->at(0))
->method('getDefinition')
->with('test')
->will($this->returnValue($entity_type));
$this->entityManager->expects($this->at(1))
->method('getBaseFieldDefinitions')
->with('test')
->will($this->returnValue($base_definitions));
$entity_query_mock = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
$entity_query_mock->expects($this->once())
->method('execute')
->will($this->returnValue(['test_bundle' => 'test_bundle']));
$entity_type_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$entity_type_storage->expects($this->once())
->method('getQuery')
->will($this->returnValue($entity_query_mock));
$this->entityManager->expects($this->at(2))
->method('getStorage')
->with('test_bundle')
->will($this->returnValue($entity_type_storage));
$key_1 = $this->getMock(FieldItemListInterface::class);
$key_2 = $this->getMock(FieldItemListInterface::class);
$entity = $this->getMock(FieldableEntityInterface::class);
$entity->expects($this->at(0))
->method('get')
->with('key_1')
->willReturn($key_1);
$entity->expects($this->at(1))
->method('get')
->with('key_2')
->willReturn($key_2);
$storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
// Create should only be called with the bundle property at first.
$expected_test_data = [
'test_type' => 'test_bundle',
];
$storage->expects($this->once())
->method('create')
->with($expected_test_data)
->will($this->returnValue($entity));
$this->entityManager->expects($this->at(3))
->method('getStorage')
->with('test')
->will($this->returnValue($storage));
// Setup expectations for the serializer. This will be called for each field
// item.
$serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')
->disableOriginalConstructor()
->setMethods(['denormalize'])
->getMock();
$serializer->expects($this->at(0))
->method('denormalize')
->with('value_1', get_class($key_1), NULL, ['target_instance' => $key_1, 'entity_type' => 'test']);
$serializer->expects($this->at(1))
->method('denormalize')
->with('value_2', get_class($key_2), NULL, ['target_instance' => $key_2, 'entity_type' => 'test']);
$this->entityNormalizer->setSerializer($serializer);
$this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']));
}
/**
* Tests the denormalize method with a bundle property.
*
* @covers ::denormalize
*/
public function testDenormalizeWithInvalidBundle() {
$test_data = [
'key_1' => 'value_1',
'key_2' => 'value_2',
'test_type' => [
['name' => 'test_bundle'],
],
];
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type->expects($this->once())
->method('id')
->willReturn('test');
$entity_type->expects($this->once())
->method('hasKey')
->with('bundle')
->will($this->returnValue(TRUE));
$entity_type->expects($this->once())
->method('getKey')
->with('bundle')
->will($this->returnValue('test_type'));
$entity_type->expects($this->once())
->method('entityClassImplements')
->with(FieldableEntityInterface::class)
->willReturn(TRUE);
$entity_type->expects($this->once())
->method('getBundleEntityType')
->will($this->returnValue('test_bundle'));
$entity_type_storage_definition = $this->getmock('Drupal\Core\Field\FieldStorageDefinitionInterface');
$entity_type_storage_definition->expects($this->once())
->method('getMainPropertyName')
->will($this->returnValue('name'));
$entity_type_definition = $this->getMock('Drupal\Core\Field\FieldDefinitionInterface');
$entity_type_definition->expects($this->once())
->method('getFieldStorageDefinition')
->will($this->returnValue($entity_type_storage_definition));
$base_definitions = [
'test_type' => $entity_type_definition,
];
$this->entityManager->expects($this->at(0))
->method('getDefinition')
->with('test')
->will($this->returnValue($entity_type));
$this->entityManager->expects($this->at(1))
->method('getBaseFieldDefinitions')
->with('test')
->will($this->returnValue($base_definitions));
$entity_query_mock = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
$entity_query_mock->expects($this->once())
->method('execute')
->will($this->returnValue(['test_bundle_other' => 'test_bundle_other']));
$entity_type_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$entity_type_storage->expects($this->once())
->method('getQuery')
->will($this->returnValue($entity_query_mock));
$this->entityManager->expects($this->at(2))
->method('getStorage')
->with('test_bundle')
->will($this->returnValue($entity_type_storage));
$this->setExpectedException(UnexpectedValueException::class);
$this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']);
}
/**
* Tests the denormalize method with no bundle defined.
*
* @covers ::denormalize
*/
public function testDenormalizeWithNoBundle() {
$test_data = [
'key_1' => 'value_1',
'key_2' => 'value_2',
];
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type->expects($this->once())
->method('entityClassImplements')
->with(FieldableEntityInterface::class)
->willReturn(TRUE);
$entity_type->expects($this->once())
->method('hasKey')
->with('bundle')
->will($this->returnValue(FALSE));
$entity_type->expects($this->never())
->method('getKey');
$this->entityManager->expects($this->once())
->method('getDefinition')
->with('test')
->will($this->returnValue($entity_type));
$key_1 = $this->getMock(FieldItemListInterface::class);
$key_2 = $this->getMock(FieldItemListInterface::class);
$entity = $this->getMock(FieldableEntityInterface::class);
$entity->expects($this->at(0))
->method('get')
->with('key_1')
->willReturn($key_1);
$entity->expects($this->at(1))
->method('get')
->with('key_2')
->willReturn($key_2);
$storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$storage->expects($this->once())
->method('create')
->with([])
->will($this->returnValue($entity));
$this->entityManager->expects($this->once())
->method('getStorage')
->with('test')
->will($this->returnValue($storage));
$this->entityManager->expects($this->never())
->method('getBaseFieldDefinitions');
// Setup expectations for the serializer. This will be called for each field
// item.
$serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')
->disableOriginalConstructor()
->setMethods(['denormalize'])
->getMock();
$serializer->expects($this->at(0))
->method('denormalize')
->with('value_1', get_class($key_1), NULL, ['target_instance' => $key_1, 'entity_type' => 'test']);
$serializer->expects($this->at(1))
->method('denormalize')
->with('value_2', get_class($key_2), NULL, ['target_instance' => $key_2, 'entity_type' => 'test']);
$this->entityNormalizer->setSerializer($serializer);
$this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']));
}
/**
* Tests the denormalize method with no bundle defined.
*
* @covers ::denormalize
*/
public function testDenormalizeWithNoFieldableEntityType() {
$test_data = [
'key_1' => 'value_1',
'key_2' => 'value_2',
];
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type->expects($this->once())
->method('entityClassImplements')
->with(FieldableEntityInterface::class)
->willReturn(FALSE);
$entity_type->expects($this->never())
->method('getKey');
$this->entityManager->expects($this->once())
->method('getDefinition')
->with('test')
->will($this->returnValue($entity_type));
$storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
$storage->expects($this->once())
->method('create')
->with($test_data)
->will($this->returnValue($this->getMock('Drupal\Core\Entity\EntityInterface')));
$this->entityManager->expects($this->once())
->method('getStorage')
->with('test')
->will($this->returnValue($storage));
$this->entityManager->expects($this->never())
->method('getBaseFieldDefinitions');
$this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']));
}
}

View file

@ -0,0 +1,404 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\Type\IntegerInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\locale\StringInterface;
use Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Serializer;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer
* @group serialization
*/
class EntityReferenceFieldItemNormalizerTest extends UnitTestCase {
use InternalTypedDataTestTrait;
/**
* The mock serializer.
*
* @var \Symfony\Component\Serializer\SerializerInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $serializer;
/**
* The normalizer under test.
*
* @var \Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer
*/
protected $normalizer;
/**
* The mock field item.
*
* @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem|\Prophecy\Prophecy\ObjectProphecy
*/
protected $fieldItem;
/**
* The mock entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $entityRepository;
/**
* The mock field definition.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $fieldDefinition;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityRepository = $this->prophesize(EntityRepositoryInterface::class);
$this->normalizer = new EntityReferenceFieldItemNormalizer($this->entityRepository->reveal());
$this->serializer = $this->prophesize(Serializer::class);
// Set up the serializer to return an entity property.
$this->serializer->normalize(Argument::cetera())
->willReturn('test');
$this->normalizer->setSerializer($this->serializer->reveal());
$this->fieldItem = $this->prophesize(EntityReferenceItem::class);
$this->fieldItem->getIterator()
->willReturn(new \ArrayIterator(['target_id' => []]));
$this->fieldDefinition = $this->prophesize(FieldDefinitionInterface::class);
}
/**
* @covers ::supportsNormalization
*/
public function testSupportsNormalization() {
$this->assertTrue($this->normalizer->supportsNormalization($this->fieldItem->reveal()));
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
/**
* @covers ::supportsDenormalization
*/
public function testSupportsDenormalization() {
$this->assertTrue($this->normalizer->supportsDenormalization([], EntityReferenceItem::class));
$this->assertFalse($this->normalizer->supportsDenormalization([], FieldItemInterface::class));
}
/**
* @covers ::normalize
*/
public function testNormalize() {
$test_url = '/test/100';
$entity = $this->prophesize(EntityInterface::class);
$entity->url('canonical')
->willReturn($test_url)
->shouldBeCalled();
$entity->uuid()
->willReturn('080e3add-f9d5-41ac-9821-eea55b7b42fb')
->shouldBeCalled();
$entity->getEntityTypeId()
->willReturn('test_type')
->shouldBeCalled();
$entity_reference = $this->prophesize(TypedDataInterface::class);
$entity_reference->getValue()
->willReturn($entity->reveal())
->shouldBeCalled();
$field_definition = $this->prophesize(FieldDefinitionInterface::class);
$field_definition->getSetting('target_type')
->willReturn('test_type');
$this->fieldItem->getFieldDefinition()
->willReturn($field_definition->reveal());
$this->fieldItem->get('entity')
->willReturn($entity_reference)
->shouldBeCalled();
$this->fieldItem->getProperties(TRUE)
->willReturn(['target_id' => $this->getTypedDataProperty(FALSE)])
->shouldBeCalled();
$normalized = $this->normalizer->normalize($this->fieldItem->reveal());
$expected = [
'target_id' => 'test',
'target_type' => 'test_type',
'target_uuid' => '080e3add-f9d5-41ac-9821-eea55b7b42fb',
'url' => $test_url,
];
$this->assertSame($expected, $normalized);
}
/**
* @covers ::normalize
*/
public function testNormalizeWithEmptyTaxonomyTermReference() {
// Override the serializer prophecy from setUp() to return a zero value.
$this->serializer = $this->prophesize(Serializer::class);
// Set up the serializer to return an entity property.
$this->serializer->normalize(Argument::cetera())
->willReturn(0);
$this->normalizer->setSerializer($this->serializer->reveal());
$entity_reference = $this->prophesize(TypedDataInterface::class);
$entity_reference->getValue()
->willReturn(NULL)
->shouldBeCalled();
$field_definition = $this->prophesize(FieldDefinitionInterface::class);
$field_definition->getSetting('target_type')
->willReturn('taxonomy_term');
$this->fieldItem->getFieldDefinition()
->willReturn($field_definition->reveal());
$this->fieldItem->get('entity')
->willReturn($entity_reference)
->shouldBeCalled();
$this->fieldItem->getProperties(TRUE)
->willReturn(['target_id' => $this->getTypedDataProperty(FALSE)])
->shouldBeCalled();
$normalized = $this->normalizer->normalize($this->fieldItem->reveal());
$expected = [
'target_id' => NULL,
];
$this->assertSame($expected, $normalized);
}
/**
* @covers ::normalize
*/
public function testNormalizeWithNoEntity() {
$entity_reference = $this->prophesize(TypedDataInterface::class);
$entity_reference->getValue()
->willReturn(NULL)
->shouldBeCalled();
$field_definition = $this->prophesize(FieldDefinitionInterface::class);
$field_definition->getSetting('target_type')
->willReturn('test_type');
$this->fieldItem->getFieldDefinition()
->willReturn($field_definition->reveal());
$this->fieldItem->get('entity')
->willReturn($entity_reference->reveal())
->shouldBeCalled();
$this->fieldItem->getProperties(TRUE)
->willReturn(['target_id' => $this->getTypedDataProperty(FALSE)])
->shouldBeCalled();
$normalized = $this->normalizer->normalize($this->fieldItem->reveal());
$expected = [
'target_id' => 'test',
];
$this->assertSame($expected, $normalized);
}
/**
* @covers ::denormalize
*/
public function testDenormalizeWithTypeAndUuid() {
$data = [
'target_id' => 'test',
'target_type' => 'test_type',
'target_uuid' => '080e3add-f9d5-41ac-9821-eea55b7b42fb',
];
$entity = $this->prophesize(FieldableEntityInterface::class);
$entity->id()
->willReturn('test')
->shouldBeCalled();
$this->entityRepository
->loadEntityByUuid($data['target_type'], $data['target_uuid'])
->willReturn($entity)
->shouldBeCalled();
$this->fieldItem->getProperties()->willReturn([
'target_id' => $this->prophesize(IntegerInterface::class),
]);
$this->fieldItem->setValue(['target_id' => 'test'])->shouldBeCalled();
$this->assertDenormalize($data);
}
/**
* @covers ::denormalize
*/
public function testDenormalizeWithUuidWithoutType() {
$data = [
'target_id' => 'test',
'target_uuid' => '080e3add-f9d5-41ac-9821-eea55b7b42fb',
];
$entity = $this->prophesize(FieldableEntityInterface::class);
$entity->id()
->willReturn('test')
->shouldBeCalled();
$this->entityRepository
->loadEntityByUuid('test_type', $data['target_uuid'])
->willReturn($entity)
->shouldBeCalled();
$this->fieldItem->getProperties()->willReturn([
'target_id' => $this->prophesize(IntegerInterface::class),
]);
$this->fieldItem->setValue(['target_id' => 'test'])->shouldBeCalled();
$this->assertDenormalize($data);
}
/**
* @covers ::denormalize
*/
public function testDenormalizeWithUuidWithIncorrectType() {
$this->setExpectedException(UnexpectedValueException::class, 'The field "field_reference" property "target_type" must be set to "test_type" or omitted.');
$data = [
'target_id' => 'test',
'target_type' => 'wrong_type',
'target_uuid' => '080e3add-f9d5-41ac-9821-eea55b7b42fb',
];
$this->fieldDefinition
->getName()
->willReturn('field_reference')
->shouldBeCalled();
$this->assertDenormalize($data);
}
/**
* @covers ::denormalize
*/
public function testDenormalizeWithTypeWithIncorrectUuid() {
$this->setExpectedException(InvalidArgumentException::class, 'No "test_type" entity found with UUID "unique-but-none-non-existent" for field "field_reference"');
$data = [
'target_id' => 'test',
'target_type' => 'test_type',
'target_uuid' => 'unique-but-none-non-existent',
];
$this->entityRepository
->loadEntityByUuid($data['target_type'], $data['target_uuid'])
->willReturn(NULL)
->shouldBeCalled();
$this->fieldItem
->getName()
->willReturn('field_reference')
->shouldBeCalled();
$this->assertDenormalize($data);
}
/**
* @covers ::denormalize
*/
public function testDenormalizeWithEmtpyUuid() {
$this->setExpectedException(InvalidArgumentException::class, 'If provided "target_uuid" cannot be empty for field "test_type".');
$data = [
'target_id' => 'test',
'target_type' => 'test_type',
'target_uuid' => '',
];
$this->fieldItem
->getName()
->willReturn('field_reference')
->shouldBeCalled();
$this->assertDenormalize($data);
}
/**
* @covers ::denormalize
*/
public function testDenormalizeWithId() {
$data = [
'target_id' => 'test',
];
$this->fieldItem->setValue($data)->shouldBeCalled();
$this->assertDenormalize($data);
}
/**
* Asserts denormalization process is correct for give data.
*
* @param array $data
* The data to denormalize.
*/
protected function assertDenormalize(array $data) {
$this->fieldItem->getParent()
->willReturn($this->prophesize(FieldItemListInterface::class)->reveal());
$this->fieldItem->getFieldDefinition()->willReturn($this->fieldDefinition->reveal());
if (!empty($data['target_uuid'])) {
$this->fieldDefinition
->getSetting('target_type')
->willReturn('test_type')
->shouldBeCalled();
}
$context = ['target_instance' => $this->fieldItem->reveal()];
$denormalized = $this->normalizer->denormalize($data, EntityReferenceItem::class, 'json', $context);
$this->assertSame($context['target_instance'], $denormalized);
}
/**
* @covers ::constructValue
*/
public function testConstructValueProperties() {
$data = [
'target_id' => 'test',
'target_type' => 'test_type',
'target_uuid' => '080e3add-f9d5-41ac-9821-eea55b7b42fb',
'extra_property' => 'extra_value',
];
$entity = $this->prophesize(FieldableEntityInterface::class);
$entity->id()
->willReturn('test')
->shouldBeCalled();
$this->entityRepository
->loadEntityByUuid($data['target_type'], $data['target_uuid'])
->willReturn($entity)
->shouldBeCalled();
$this->fieldItem->getProperties()->willReturn([
'target_id' => $this->prophesize(IntegerInterface::class),
'extra_property' => $this->prophesize(StringInterface::class),
]);
$this->fieldItem->setValue([
'target_id' => 'test',
'extra_property' => 'extra_value',
])->shouldBeCalled();
$this->assertDenormalize($data);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Trait that provides mocked typed data objects.
*/
trait InternalTypedDataTestTrait {
/**
* Gets a typed data property.
*
* @param bool $internal
* Whether the typed data property is internal.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* The typed data property.
*/
protected function getTypedDataProperty($internal = TRUE) {
$definition = $this->prophesize(DataDefinitionInterface::class);
$definition->isInternal()
->willReturn($internal)
->shouldBeCalled();
$definition = $definition->reveal();
$property = $this->prophesize(TypedDataInterface::class);
$property->getDataDefinition()
->willReturn($definition)
->shouldBeCalled();
return $property->reveal();
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\serialization\Normalizer\ListNormalizer;
use Drupal\Core\TypedData\Plugin\DataType\ItemList;
use Symfony\Component\Serializer\Serializer;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\ListNormalizer
* @group serialization
*/
class ListNormalizerTest extends UnitTestCase {
/**
* The ListNormalizer instance.
*
* @var \Drupal\serialization\Normalizer\ListNormalizer
*/
protected $normalizer;
/**
* The mock list instance.
*
* @var \Drupal\Core\TypedData\ListInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $list;
/**
* The expected list values to use for testing.
*
* @var array
*/
protected $expectedListValues = ['test', 'test', 'test'];
/**
* The mocked typed data.
*
* @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\TypedData\TypedDataInterface
*/
protected $typedData;
protected function setUp() {
// Mock the TypedDataManager to return a TypedDataInterface mock.
$this->typedData = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$typed_data_manager = $this->getMock(TypedDataManagerInterface::class);
$typed_data_manager->expects($this->any())
->method('getPropertyInstance')
->will($this->returnValue($this->typedData));
// Set up a mock container as ItemList() will call for the 'typed_data_manager'
// service.
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')
->setMethods(['get'])
->getMock();
$container->expects($this->any())
->method('get')
->with($this->equalTo('typed_data_manager'))
->will($this->returnValue($typed_data_manager));
\Drupal::setContainer($container);
$this->normalizer = new ListNormalizer();
$this->list = new ItemList(new DataDefinition());
$this->list->setValue($this->expectedListValues);
}
/**
* Tests the supportsNormalization() method.
*/
public function testSupportsNormalization() {
$this->assertTrue($this->normalizer->supportsNormalization($this->list));
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
/**
* Tests the normalize() method.
*/
public function testNormalize() {
$serializer = $this->prophesize(Serializer::class);
$serializer->normalize($this->typedData, 'json', ['mu' => 'nu'])
->shouldBeCalledTimes(3)
->willReturn('test');
$this->normalizer->setSerializer($serializer->reveal());
$normalized = $this->normalizer->normalize($this->list, 'json', ['mu' => 'nu']);
$this->assertEquals($this->expectedListValues, $normalized);
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Contains \Drupal\Tests\serialization\Unit\Normalizer\NormalizerBaseTest.
*/
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Tests\UnitTestCase;
use Drupal\serialization\Normalizer\NormalizerBase;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\NormalizerBase
* @group serialization
*/
class NormalizerBaseTest extends UnitTestCase {
/**
* Tests the supportsNormalization method.
*
* @dataProvider providerTestSupportsNormalization
*
* @param bool $expected_return
* The expected boolean return value from supportNormalization.
* @param mixed $data
* The data passed to supportsNormalization.
* @param string $supported_interface_or_class
* (optional) The supported interface or class to set on the normalizer.
*/
public function testSupportsNormalization($expected_return, $data, $supported_interface_or_class = NULL) {
$normalizer_base = $this->getMockForAbstractClass('Drupal\Tests\serialization\Unit\Normalizer\TestNormalizerBase');
if (isset($supported_interface_or_class)) {
$normalizer_base->setSupportedInterfaceOrClass($supported_interface_or_class);
}
$this->assertSame($expected_return, $normalizer_base->supportsNormalization($data));
}
/**
* Data provider for testSupportsNormalization.
*
* @return array
* An array of provider data for testSupportsNormalization.
*/
public function providerTestSupportsNormalization() {
return [
// Something that is not an object should return FALSE immediately.
[FALSE, []],
// An object with no class set should return FALSE.
[FALSE, new \stdClass()],
// Set a supported Class.
[TRUE, new \stdClass(), 'stdClass'],
// Set a supported interface.
[TRUE, new \RecursiveArrayIterator(), 'RecursiveIterator'],
// Set a different class.
[FALSE, new \stdClass(), 'ArrayIterator'],
// Set a different interface.
[FALSE, new \stdClass(), 'RecursiveIterator'],
];
}
}
/**
* Test class for NormalizerBase.
*/
abstract class TestNormalizerBase extends NormalizerBase {
/**
* Sets the protected supportedInterfaceOrClass property.
*
* @param string $supported_interface_or_class
* The class name to set.
*/
public function setSupportedInterfaceOrClass($supported_interface_or_class) {
$this->supportedInterfaceOrClass = $supported_interface_or_class;
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\serialization\Normalizer\NullNormalizer;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\NullNormalizer
* @group serialization
*/
class NullNormalizerTest extends UnitTestCase {
/**
* The NullNormalizer instance.
*
* @var \Drupal\serialization\Normalizer\NullNormalizer
*/
protected $normalizer;
/**
* The interface to use in testing.
*
* @var string
*/
protected $interface = 'Drupal\Core\TypedData\TypedDataInterface';
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->normalizer = new NullNormalizer($this->interface);
}
/**
* @covers ::__construct
* @covers ::supportsNormalization
*/
public function testSupportsNormalization() {
$mock = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$this->assertTrue($this->normalizer->supportsNormalization($mock));
// Also test that an object not implementing TypedDataInterface fails.
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
/**
* @covers ::normalize
*/
public function testNormalize() {
$mock = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
$this->assertNull($this->normalizer->normalize($mock));
}
}

View file

@ -0,0 +1,101 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\Plugin\DataType\BooleanData;
use Drupal\Core\TypedData\Plugin\DataType\IntegerData;
use Drupal\Core\TypedData\Plugin\DataType\StringData;
use Drupal\Tests\UnitTestCase;
use Drupal\serialization\Normalizer\PrimitiveDataNormalizer;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\PrimitiveDataNormalizer
* @group serialization
*/
class PrimitiveDataNormalizerTest extends UnitTestCase {
/**
* The TypedDataNormalizer instance.
*
* @var \Drupal\serialization\Normalizer\TypedDataNormalizer
*/
protected $normalizer;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->normalizer = new PrimitiveDataNormalizer();
}
/**
* @covers ::supportsNormalization
* @dataProvider dataProviderPrimitiveData
*/
public function testSupportsNormalization($primitive_data, $expected) {
$this->assertTrue($this->normalizer->supportsNormalization($primitive_data));
}
/**
* @covers ::supportsNormalization
*/
public function testSupportsNormalizationFail() {
// Test that an object not implementing PrimitiveInterface fails.
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
/**
* @covers ::normalize
* @dataProvider dataProviderPrimitiveData
*/
public function testNormalize($primitive_data, $expected) {
$this->assertSame($expected, $this->normalizer->normalize($primitive_data));
}
/**
* Data provider for testNormalize().
*/
public function dataProviderPrimitiveData() {
$data = [];
$definition = DataDefinition::createFromDataType('string');
$string = new StringData($definition, 'string');
$string->setValue('test');
$data['string'] = [$string, 'test'];
$definition = DataDefinition::createFromDataType('string');
$string = new StringData($definition, 'string');
$string->setValue(NULL);
$data['string-null'] = [$string, NULL];
$definition = DataDefinition::createFromDataType('integer');
$integer = new IntegerData($definition, 'integer');
$integer->setValue(5);
$data['integer'] = [$integer, 5];
$definition = DataDefinition::createFromDataType('integer');
$integer = new IntegerData($definition, 'integer');
$integer->setValue(NULL);
$data['integer-null'] = [$integer, NULL];
$definition = DataDefinition::createFromDataType('boolean');
$boolean = new BooleanData($definition, 'boolean');
$boolean->setValue(TRUE);
$data['boolean'] = [$boolean, TRUE];
$definition = DataDefinition::createFromDataType('boolean');
$boolean = new BooleanData($definition, 'boolean');
$boolean->setValue(NULL);
$data['boolean-null'] = [$boolean, NULL];
return $data;
}
}

View file

@ -0,0 +1,168 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Core\Field\Plugin\Field\FieldType\CreatedItem;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem;
use Drupal\serialization\Normalizer\TimestampItemNormalizer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Serializer;
/**
* Tests that entities can be serialized to supported core formats.
*
* @group serialization
* @coversDefaultClass \Drupal\serialization\Normalizer\TimestampItemNormalizer
*/
class TimestampItemNormalizerTest extends UnitTestCase {
use InternalTypedDataTestTrait;
/**
* @var \Drupal\serialization\Normalizer\TimestampItemNormalizer
*/
protected $normalizer;
/**
* The test TimestampItem.
*
* @var \Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem
*/
protected $item;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->normalizer = new TimestampItemNormalizer();
}
/**
* @covers ::supportsNormalization
*/
public function testSupportsNormalization() {
$timestamp_item = $this->createTimestampItemProphecy();
$this->assertTrue($this->normalizer->supportsNormalization($timestamp_item->reveal()));
$entity_ref_item = $this->prophesize(EntityReferenceItem::class);
$this->assertFalse($this->normalizer->supportsNormalization($entity_ref_item->reveal()));
}
/**
* @covers ::supportsDenormalization
*/
public function testSupportsDenormalization() {
$timestamp_item = $this->createTimestampItemProphecy();
$this->assertTrue($this->normalizer->supportsDenormalization($timestamp_item->reveal(), TimestampItem::class));
// CreatedItem extends regular TimestampItem.
$timestamp_item = $this->prophesize(CreatedItem::class);
$this->assertTrue($this->normalizer->supportsDenormalization($timestamp_item->reveal(), TimestampItem::class));
$entity_ref_item = $this->prophesize(EntityReferenceItem::class);
$this->assertFalse($this->normalizer->supportsNormalization($entity_ref_item->reveal(), TimestampItem::class));
}
/**
* Tests the normalize function.
*
* @covers ::normalize
*/
public function testNormalize() {
$expected = ['value' => '2016-11-06T09:02:00+00:00', 'format' => \DateTime::RFC3339];
$timestamp_item = $this->createTimestampItemProphecy();
$timestamp_item->getIterator()
->willReturn(new \ArrayIterator(['value' => 1478422920]));
$value_property = $this->getTypedDataProperty(FALSE);
$timestamp_item->getProperties(TRUE)
->willReturn(['value' => $value_property])
->shouldBeCalled();
$serializer_prophecy = $this->prophesize(Serializer::class);
$serializer_prophecy->normalize($value_property, NULL, [])
->willReturn(1478422920)
->shouldBeCalled();
$this->normalizer->setSerializer($serializer_prophecy->reveal());
$normalized = $this->normalizer->normalize($timestamp_item->reveal());
$this->assertSame($expected, $normalized);
}
/**
* Tests the denormalize function with good data.
*
* @covers ::denormalize
* @dataProvider providerTestDenormalizeValidFormats
*/
public function testDenormalizeValidFormats($value, $expected) {
$normalized = ['value' => $value];
$timestamp_item = $this->createTimestampItemProphecy();
// The field item should be set with the expected timestamp.
$timestamp_item->setValue(['value' => $expected])
->shouldBeCalled();
$context = ['target_instance' => $timestamp_item->reveal()];
$denormalized = $this->normalizer->denormalize($normalized, TimestampItem::class, NULL, $context);
$this->assertTrue($denormalized instanceof TimestampItem);
}
/**
* Data provider for testDenormalizeValidFormats.
*
* @return array
*/
public function providerTestDenormalizeValidFormats() {
$expected_stamp = 1478422920;
$data = [];
$data['U'] = [$expected_stamp, $expected_stamp];
$data['RFC3339'] = ['2016-11-06T09:02:00+00:00', $expected_stamp];
$data['RFC3339 +0100'] = ['2016-11-06T09:02:00+01:00', $expected_stamp - 1 * 3600];
$data['RFC3339 -0600'] = ['2016-11-06T09:02:00-06:00', $expected_stamp + 6 * 3600];
$data['ISO8601'] = ['2016-11-06T09:02:00+0000', $expected_stamp];
$data['ISO8601 +0100'] = ['2016-11-06T09:02:00+0100', $expected_stamp - 1 * 3600];
$data['ISO8601 -0600'] = ['2016-11-06T09:02:00-0600', $expected_stamp + 6 * 3600];
return $data;
}
/**
* Tests the denormalize function with bad data.
*
* @covers ::denormalize
*/
public function testDenormalizeException() {
$this->setExpectedException(UnexpectedValueException::class, 'The specified date "2016/11/06 09:02am GMT" is not in an accepted format: "U" (UNIX timestamp), "Y-m-d\TH:i:sO" (ISO 8601), "Y-m-d\TH:i:sP" (RFC 3339).');
$context = ['target_instance' => $this->createTimestampItemProphecy()->reveal()];
$normalized = ['value' => '2016/11/06 09:02am GMT'];
$this->normalizer->denormalize($normalized, TimestampItem::class, NULL, $context);
}
/**
* Creates a TimestampItem prophecy.
*
* @return \Prophecy\Prophecy\ObjectProphecy|\Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem
*/
protected function createTimestampItemProphecy() {
$timestamp_item = $this->prophesize(TimestampItem::class);
$timestamp_item->getParent()
->willReturn(TRUE);
return $timestamp_item;
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Drupal\Tests\serialization\Unit\Normalizer;
use Drupal\Tests\UnitTestCase;
use Drupal\serialization\Normalizer\TypedDataNormalizer;
/**
* @coversDefaultClass \Drupal\serialization\Normalizer\TypedDataNormalizer
* @group serialization
*/
class TypedDataNormalizerTest extends UnitTestCase {
/**
* The TypedDataNormalizer instance.
*
* @var \Drupal\serialization\Normalizer\TypedDataNormalizer
*/
protected $normalizer;
/**
* The mock typed data instance.
*
* @var \Drupal\Core\TypedData\TypedDataInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $typedData;
protected function setUp() {
$this->normalizer = new TypedDataNormalizer();
$this->typedData = $this->getMock('Drupal\Core\TypedData\TypedDataInterface');
}
/**
* Tests the supportsNormalization() method.
*/
public function testSupportsNormalization() {
$this->assertTrue($this->normalizer->supportsNormalization($this->typedData));
// Also test that an object not implementing TypedDataInterface fails.
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
}
/**
* Tests the normalize() method.
*/
public function testNormalize() {
$this->typedData->expects($this->once())
->method('getValue')
->will($this->returnValue('test'));
$this->assertEquals('test', $this->normalizer->normalize($this->typedData));
}
}