<?php
declare(strict_types=1);
namespace JMS\Serializer;
use JMS\Serializer\ContextFactory\DefaultDeserializationContextFactory;
use JMS\Serializer\ContextFactory\DefaultSerializationContextFactory;
use JMS\Serializer\ContextFactory\DeserializationContextFactoryInterface;
use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface;
use JMS\Serializer\Exception\InvalidArgumentException;
use JMS\Serializer\Exception\RuntimeException;
use JMS\Serializer\Exception\UnsupportedFormatException;
use JMS\Serializer\GraphNavigator\Factory\GraphNavigatorFactoryInterface;
use JMS\Serializer\Type\Parser;
use JMS\Serializer\Type\ParserInterface;
use JMS\Serializer\Visitor\Factory\DeserializationVisitorFactory;
use JMS\Serializer\Visitor\Factory\SerializationVisitorFactory;
use Metadata\MetadataFactoryInterface;
/**
* Serializer Implementation.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
final class Serializer implements SerializerInterface, ArrayTransformerInterface
{
/**
* @var MetadataFactoryInterface
*/
private $factory;
/**
* @var ParserInterface
*/
private $typeParser;
/**
* @var SerializationVisitorFactory[]
*/
private $serializationVisitors;
/**
* @var DeserializationVisitorFactory[]
*/
private $deserializationVisitors;
/**
* @var SerializationContextFactoryInterface
*/
private $serializationContextFactory;
/**
* @var DeserializationContextFactoryInterface
*/
private $deserializationContextFactory;
/**
* @var GraphNavigatorFactoryInterface[]
*/
private $graphNavigators;
/**
* @param GraphNavigatorFactoryInterface[] $graphNavigators
* @param SerializationVisitorFactory[] $serializationVisitors
* @param DeserializationVisitorFactory[] $deserializationVisitors
*/
public function __construct(
MetadataFactoryInterface $factory,
array $graphNavigators,
array $serializationVisitors,
array $deserializationVisitors,
?SerializationContextFactoryInterface $serializationContextFactory = null,
?DeserializationContextFactoryInterface $deserializationContextFactory = null,
?ParserInterface $typeParser = null
) {
$this->factory = $factory;
$this->graphNavigators = $graphNavigators;
$this->serializationVisitors = $serializationVisitors;
$this->deserializationVisitors = $deserializationVisitors;
$this->typeParser = $typeParser ?? new Parser();
$this->serializationContextFactory = $serializationContextFactory ?: new DefaultSerializationContextFactory();
$this->deserializationContextFactory = $deserializationContextFactory ?: new DefaultDeserializationContextFactory();
}
/**
* Parses a direction string to one of the direction constants.
*/
public static function parseDirection(string $dirStr): int
{
switch (strtolower($dirStr)) {
case 'serialization':
return GraphNavigatorInterface::DIRECTION_SERIALIZATION;
case 'deserialization':
return GraphNavigatorInterface::DIRECTION_DESERIALIZATION;
default:
throw new InvalidArgumentException(sprintf('The direction "%s" does not exist.', $dirStr));
}
}
private function findInitialType(?string $type, SerializationContext $context): ?string
{
if (null !== $type) {
return $type;
} elseif ($context->hasAttribute('initial_type')) {
return $context->getAttribute('initial_type');
}
return null;
}
private function getNavigator(int $direction): GraphNavigatorInterface
{
if (!isset($this->graphNavigators[$direction])) {
throw new RuntimeException(
sprintf(
'Can not find a graph navigator for the direction "%s".',
GraphNavigatorInterface::DIRECTION_SERIALIZATION === $direction ? 'serialization' : 'deserialization'
)
);
}
return $this->graphNavigators[$direction]->getGraphNavigator();
}
private function getVisitor(int $direction, string $format): VisitorInterface
{
$factories = GraphNavigatorInterface::DIRECTION_SERIALIZATION === $direction
? $this->serializationVisitors
: $this->deserializationVisitors;
if (!isset($factories[$format])) {
throw new UnsupportedFormatException(
sprintf(
'The format "%s" is not supported for %s.',
$format,
GraphNavigatorInterface::DIRECTION_SERIALIZATION === $direction ? 'serialization' : 'deserialization'
)
);
}
return $factories[$format]->getVisitor();
}
/**
* {@InheritDoc}
*/
public function serialize($data, string $format, ?SerializationContext $context = null, ?string $type = null): string
{
if (null === $context) {
$context = $this->serializationContextFactory->createSerializationContext();
}
$visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_SERIALIZATION, $format);
$navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_SERIALIZATION);
$type = $this->findInitialType($type, $context);
$result = $this->visit($navigator, $visitor, $context, $data, $format, $type);
$context->close();
return $visitor->getResult($result);
}
/**
* {@InheritDoc}
*/
public function deserialize(string $data, string $type, string $format, ?DeserializationContext $context = null)
{
if (null === $context) {
$context = $this->deserializationContextFactory->createDeserializationContext();
}
$visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, $format);
$navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_DESERIALIZATION);
$result = $this->visit($navigator, $visitor, $context, $data, $format, $type);
$context->close();
return $visitor->getResult($result);
}
/**
* {@InheritDoc}
*/
public function toArray($data, ?SerializationContext $context = null, ?string $type = null): array
{
if (null === $context) {
$context = $this->serializationContextFactory->createSerializationContext();
}
$visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_SERIALIZATION, 'json');
$navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_SERIALIZATION);
$type = $this->findInitialType($type, $context);
$result = $this->visit($navigator, $visitor, $context, $data, 'json', $type);
$result = $this->convertArrayObjects($result);
if (!\is_array($result)) {
throw new RuntimeException(sprintf(
'The input data of type "%s" did not convert to an array, but got a result of type "%s".',
\is_object($data) ? \get_class($data) : \gettype($data),
\is_object($result) ? \get_class($result) : \gettype($result)
));
}
return $result;
}
/**
* {@InheritDoc}
*/
public function fromArray(array $data, string $type, ?DeserializationContext $context = null)
{
if (null === $context) {
$context = $this->deserializationContextFactory->createDeserializationContext();
}
$visitor = $this->getVisitor(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 'json');
$navigator = $this->getNavigator(GraphNavigatorInterface::DIRECTION_DESERIALIZATION);
return $this->visit($navigator, $visitor, $context, $data, 'json', $type, false);
}
/**
* @param mixed $data
*
* @return mixed
*/
private function visit(GraphNavigatorInterface $navigator, VisitorInterface $visitor, Context $context, $data, string $format, ?string $type = null, bool $prepare = true)
{
$context->initialize(
$format,
$visitor,
$navigator,
$this->factory
);
$visitor->setNavigator($navigator);
$navigator->initialize($visitor, $context);
if ($prepare) {
$data = $visitor->prepare($data);
}
if (null !== $type) {
$type = $this->typeParser->parse($type);
}
return $navigator->accept($data, $type);
}
/**
* @param mixed $data
*
* @return mixed
*/
private function convertArrayObjects($data)
{
if ($data instanceof \ArrayObject || $data instanceof \stdClass) {
$data = (array) $data;
}
if (\is_array($data)) {
foreach ($data as $k => $v) {
$data[$k] = $this->convertArrayObjects($v);
}
}
return $data;
}
}