Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
Reference in a new issue