Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

3
vendor/symfony/serializer/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Annotation;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
/**
* Annotation class for @Groups().
*
* @Annotation
* @Target({"PROPERTY", "METHOD"})
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class Groups
{
/**
* @var string[]
*/
private $groups;
/**
* @throws InvalidArgumentException
*/
public function __construct(array $data)
{
if (!isset($data['value']) || !$data['value']) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', \get_class($this)));
}
$value = (array) $data['value'];
foreach ($value as $group) {
if (!\is_string($group)) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a string or an array of strings.', \get_class($this)));
}
}
$this->groups = $value;
}
/**
* Gets groups.
*
* @return string[]
*/
public function getGroups()
{
return $this->groups;
}
}

View file

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Annotation;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
/**
* Annotation class for @MaxDepth().
*
* @Annotation
* @Target({"PROPERTY", "METHOD"})
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class MaxDepth
{
/**
* @var int
*/
private $maxDepth;
public function __construct(array $data)
{
if (!isset($data['value'])) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', \get_class($this)));
}
if (!\is_int($data['value']) || $data['value'] <= 0) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', \get_class($this)));
}
$this->maxDepth = $data['value'];
}
public function getMaxDepth()
{
return $this->maxDepth;
}
}

131
vendor/symfony/serializer/CHANGELOG.md vendored Normal file
View file

@ -0,0 +1,131 @@
CHANGELOG
=========
3.4.0
-----
* added `AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT` context option
to disable throwing an `UnexpectedValueException` on a type mismatch
* added support for serializing `DateInterval` objects
* added getter for extra attributes in `ExtraAttributesException`
* improved `CsvEncoder` to handle variable nested structures
* CSV headers can be passed to the `CsvEncoder` via the `csv_headers` serialization context variable
* added `$context` when checking for encoding, decoding and normalizing in `Serializer`
3.3.0
-----
* added `SerializerPass`
3.1.0
-----
* added support for serializing objects that implement `JsonSerializable`
* added the `DenormalizerAwareTrait` and `NormalizerAwareTrait` traits to
support normalizer/denormalizer awareness
* added the `DenormalizerAwareInterface` and `NormalizerAwareInterface`
interfaces to support normalizer/denormalizer awareness
* added a PSR-6 compatible adapter for caching metadata
* added a `MaxDepth` option to limit the depth of the object graph when
serializing objects
* added support for serializing `SplFileInfo` objects
* added support for serializing objects that implement `DateTimeInterface`
* added `AbstractObjectNormalizer` as a base class for normalizers that deal
with objects
* added support to relation deserialization
2.7.0
-----
* added support for serialization and deserialization groups including
annotations, XML and YAML mapping.
* added `AbstractNormalizer` to factorise code and ease normalizers development
* added circular references handling for `PropertyNormalizer`
* added support for a context key called `object_to_populate` in `AbstractNormalizer`
to reuse existing objects in the deserialization process
* added `NameConverterInterface` and `CamelCaseToSnakeCaseNameConverter`
* [DEPRECATION] `GetSetMethodNormalizer::setCamelizedAttributes()` and
`PropertyNormalizer::setCamelizedAttributes()` are replaced by
`CamelCaseToSnakeCaseNameConverter`
* [DEPRECATION] the `Exception` interface has been renamed to `ExceptionInterface`
* added `ObjectNormalizer` leveraging the `PropertyAccess` component to normalize
objects containing both properties and getters / setters / issers / hassers methods.
* added `xml_type_cast_attributes` context option for allowing users to opt-out of typecasting
xml attributes.
2.6.0
-----
* added a new serializer: `PropertyNormalizer`. Like `GetSetMethodNormalizer`,
this normalizer will map an object's properties to an array.
* added circular references handling for `GetSetMethodNormalizer`
2.5.0
-----
* added support for `is.*` getters in `GetSetMethodNormalizer`
2.4.0
-----
* added `$context` support for XMLEncoder.
* [DEPRECATION] JsonEncode and JsonDecode where modified to throw
an exception if error found. No need for get*Error() functions
2.3.0
-----
* added `GetSetMethodNormalizer::setCamelizedAttributes` to allow calling
camel cased methods for underscored properties
2.2.0
-----
* [BC BREAK] All Serializer, Normalizer and Encoder interfaces have been
modified to include an optional `$context` array parameter.
* The XML Root name can now be configured with the `xml_root_name`
parameter in the context option to the `XmlEncoder`.
* Options to `json_encode` and `json_decode` can be passed through
the context options of `JsonEncode` and `JsonDecode` encoder/decoders.
2.1.0
-----
* added DecoderInterface::supportsDecoding(),
EncoderInterface::supportsEncoding()
* removed NormalizableInterface::denormalize(),
NormalizerInterface::denormalize(),
NormalizerInterface::supportsDenormalization()
* removed normalize() denormalize() encode() decode() supportsSerialization()
supportsDeserialization() supportsEncoding() supportsDecoding()
getEncoder() from SerializerInterface
* Serializer now implements NormalizerInterface, DenormalizerInterface,
EncoderInterface, DecoderInterface in addition to SerializerInterface
* added DenormalizableInterface and DenormalizerInterface
* [BC BREAK] changed `GetSetMethodNormalizer`'s key names from all lowercased
to camelCased (e.g. `mypropertyvalue` to `myPropertyValue`)
* [BC BREAK] convert the `item` XML tag to an array
``` xml
<?xml version="1.0"?>
<response>
<item><title><![CDATA[title1]]></title></item><item><title><![CDATA[title2]]></title></item>
</response>
```
Before:
Array()
After:
Array(
[item] => Array(
[0] => Array(
[title] => title1
)
[1] => Array(
[title] => title2
)
)
)

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as
* encoders and normalizers to the "serializer" service.
*
* @author Javier Lopez <f12loalf@gmail.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class SerializerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
private $serializerService;
private $normalizerTag;
private $encoderTag;
public function __construct($serializerService = 'serializer', $normalizerTag = 'serializer.normalizer', $encoderTag = 'serializer.encoder')
{
$this->serializerService = $serializerService;
$this->normalizerTag = $normalizerTag;
$this->encoderTag = $encoderTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->serializerService)) {
return;
}
if (!$normalizers = $this->findAndSortTaggedServices($this->normalizerTag, $container)) {
throw new RuntimeException(sprintf('You must tag at least one service as "%s" to use the "%s" service.', $this->normalizerTag, $this->serializerService));
}
$serializerDefinition = $container->getDefinition($this->serializerService);
$serializerDefinition->replaceArgument(0, $normalizers);
if (!$encoders = $this->findAndSortTaggedServices($this->encoderTag, $container)) {
throw new RuntimeException(sprintf('You must tag at least one service as "%s" to use the "%s" service.', $this->encoderTag, $this->serializerService));
}
$serializerDefinition->replaceArgument(1, $encoders);
}
}

View file

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\RuntimeException;
/**
* Decoder delegating the decoding to a chain of decoders.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
*
* @final since version 3.3.
*/
class ChainDecoder implements DecoderInterface /*, ContextAwareDecoderInterface*/
{
protected $decoders = array();
protected $decoderByFormat = array();
public function __construct(array $decoders = array())
{
$this->decoders = $decoders;
}
/**
* {@inheritdoc}
*/
final public function decode($data, $format, array $context = array())
{
return $this->getDecoder($format, $context)->decode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format/*, array $context = array()*/)
{
$context = \func_num_args() > 1 ? func_get_arg(1) : array();
try {
$this->getDecoder($format, $context);
} catch (RuntimeException $e) {
return false;
}
return true;
}
/**
* Gets the decoder supporting the format.
*
* @param string $format
* @param array $context
*
* @return DecoderInterface
*
* @throws RuntimeException if no decoder is found
*/
private function getDecoder($format, array $context)
{
if (isset($this->decoderByFormat[$format])
&& isset($this->decoders[$this->decoderByFormat[$format]])
) {
return $this->decoders[$this->decoderByFormat[$format]];
}
foreach ($this->decoders as $i => $decoder) {
if ($decoder->supportsDecoding($format, $context)) {
$this->decoderByFormat[$format] = $i;
return $decoder;
}
}
throw new RuntimeException(sprintf('No decoder found for format "%s".', $format));
}
}

View file

@ -0,0 +1,111 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\RuntimeException;
/**
* Encoder delegating the decoding to a chain of encoders.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
*
* @final since version 3.3.
*/
class ChainEncoder implements EncoderInterface /*, ContextAwareEncoderInterface*/
{
protected $encoders = array();
protected $encoderByFormat = array();
public function __construct(array $encoders = array())
{
$this->encoders = $encoders;
}
/**
* {@inheritdoc}
*/
final public function encode($data, $format, array $context = array())
{
return $this->getEncoder($format, $context)->encode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format/*, array $context = array()*/)
{
$context = \func_num_args() > 1 ? func_get_arg(1) : array();
try {
$this->getEncoder($format, $context);
} catch (RuntimeException $e) {
return false;
}
return true;
}
/**
* Checks whether the normalization is needed for the given format.
*
* @param string $format
* @param array $context
*
* @return bool
*/
public function needsNormalization($format/*, array $context = array()*/)
{
$context = \func_num_args() > 1 ? func_get_arg(1) : array();
$encoder = $this->getEncoder($format, $context);
if (!$encoder instanceof NormalizationAwareInterface) {
return true;
}
if ($encoder instanceof self) {
return $encoder->needsNormalization($format, $context);
}
return false;
}
/**
* Gets the encoder supporting the format.
*
* @param string $format
* @param array $context
*
* @return EncoderInterface
*
* @throws RuntimeException if no encoder is found
*/
private function getEncoder($format, array $context)
{
if (isset($this->encoderByFormat[$format])
&& isset($this->encoders[$this->encoderByFormat[$format]])
) {
return $this->encoders[$this->encoderByFormat[$format]];
}
foreach ($this->encoders as $i => $encoder) {
if ($encoder->supportsEncoding($format, $context)) {
$this->encoderByFormat[$format] = $i;
return $encoder;
}
}
throw new RuntimeException(sprintf('No encoder found for format "%s".', $format));
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
/**
* Adds the support of an extra $context parameter for the supportsDecoding method.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface ContextAwareDecoderInterface extends DecoderInterface
{
/**
* {@inheritdoc}
*
* @param array $context options that decoders have access to
*/
public function supportsDecoding($format, array $context = array());
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
/**
* Adds the support of an extra $context parameter for the supportsEncoding method.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface ContextAwareEncoderInterface extends EncoderInterface
{
/**
* {@inheritdoc}
*
* @param array $context options that encoders have access to
*/
public function supportsEncoding($format, array $context = array());
}

View file

@ -0,0 +1,247 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
/**
* Encodes CSV data.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Oliver Hoff <oliver@hofff.com>
*/
class CsvEncoder implements EncoderInterface, DecoderInterface
{
const FORMAT = 'csv';
const DELIMITER_KEY = 'csv_delimiter';
const ENCLOSURE_KEY = 'csv_enclosure';
const ESCAPE_CHAR_KEY = 'csv_escape_char';
const KEY_SEPARATOR_KEY = 'csv_key_separator';
const HEADERS_KEY = 'csv_headers';
private $delimiter;
private $enclosure;
private $escapeChar;
private $keySeparator;
/**
* @param string $delimiter
* @param string $enclosure
* @param string $escapeChar
* @param string $keySeparator
*/
public function __construct($delimiter = ',', $enclosure = '"', $escapeChar = '\\', $keySeparator = '.')
{
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
$this->escapeChar = $escapeChar;
$this->keySeparator = $keySeparator;
}
/**
* {@inheritdoc}
*/
public function encode($data, $format, array $context = array())
{
$handle = fopen('php://temp,', 'w+');
if (!\is_array($data)) {
$data = array(array($data));
} elseif (empty($data)) {
$data = array(array());
} else {
// Sequential arrays of arrays are considered as collections
$i = 0;
foreach ($data as $key => $value) {
if ($i !== $key || !\is_array($value)) {
$data = array($data);
break;
}
++$i;
}
}
list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers) = $this->getCsvOptions($context);
foreach ($data as &$value) {
$flattened = array();
$this->flatten($value, $flattened, $keySeparator);
$value = $flattened;
}
unset($value);
$headers = array_merge(array_values($headers), array_diff($this->extractHeaders($data), $headers));
fputcsv($handle, $headers, $delimiter, $enclosure, $escapeChar);
$headers = array_fill_keys($headers, '');
foreach ($data as $row) {
fputcsv($handle, array_replace($headers, $row), $delimiter, $enclosure, $escapeChar);
}
rewind($handle);
$value = stream_get_contents($handle);
fclose($handle);
return $value;
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format)
{
return self::FORMAT === $format;
}
/**
* {@inheritdoc}
*/
public function decode($data, $format, array $context = array())
{
$handle = fopen('php://temp', 'r+');
fwrite($handle, $data);
rewind($handle);
$headers = null;
$nbHeaders = 0;
$headerCount = array();
$result = array();
list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
while (false !== ($cols = fgetcsv($handle, 0, $delimiter, $enclosure, $escapeChar))) {
$nbCols = \count($cols);
if (null === $headers) {
$nbHeaders = $nbCols;
foreach ($cols as $col) {
$header = explode($keySeparator, $col);
$headers[] = $header;
$headerCount[] = \count($header);
}
continue;
}
$item = array();
for ($i = 0; ($i < $nbCols) && ($i < $nbHeaders); ++$i) {
$depth = $headerCount[$i];
$arr = &$item;
for ($j = 0; $j < $depth; ++$j) {
// Handle nested arrays
if ($j === ($depth - 1)) {
$arr[$headers[$i][$j]] = $cols[$i];
continue;
}
if (!isset($arr[$headers[$i][$j]])) {
$arr[$headers[$i][$j]] = array();
}
$arr = &$arr[$headers[$i][$j]];
}
}
$result[] = $item;
}
fclose($handle);
if (empty($result) || isset($result[1])) {
return $result;
}
// If there is only one data line in the document, return it (the line), the result is not considered as a collection
return $result[0];
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format)
{
return self::FORMAT === $format;
}
/**
* Flattens an array and generates keys including the path.
*
* @param array $array
* @param array $result
* @param string $keySeparator
* @param string $parentKey
*/
private function flatten(array $array, array &$result, $keySeparator, $parentKey = '')
{
foreach ($array as $key => $value) {
if (\is_array($value)) {
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator);
} else {
$result[$parentKey.$key] = $value;
}
}
}
private function getCsvOptions(array $context)
{
$delimiter = isset($context[self::DELIMITER_KEY]) ? $context[self::DELIMITER_KEY] : $this->delimiter;
$enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure;
$escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
$keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
$headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array();
if (!\is_array($headers)) {
throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, \gettype($headers)));
}
return array($delimiter, $enclosure, $escapeChar, $keySeparator, $headers);
}
/**
* @return string[]
*/
private function extractHeaders(array $data)
{
$headers = array();
$flippedHeaders = array();
foreach ($data as $row) {
$previousHeader = null;
foreach ($row as $header => $_) {
if (isset($flippedHeaders[$header])) {
$previousHeader = $header;
continue;
}
if (null === $previousHeader) {
$n = \count($headers);
} else {
$n = $flippedHeaders[$previousHeader] + 1;
for ($j = \count($headers); $j > $n; --$j) {
++$flippedHeaders[$headers[$j] = $headers[$j - 1]];
}
}
$headers[$n] = $header;
$flippedHeaders[$header] = $n;
$previousHeader = $header;
}
}
return $headers;
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Defines the interface of decoders.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface DecoderInterface
{
/**
* Decodes a string into PHP data.
*
* @param string $data Data to decode
* @param string $format Format name
* @param array $context Options that decoders have access to
*
* The format parameter specifies which format the data is in; valid values
* depend on the specific implementation. Authors implementing this interface
* are encouraged to document which formats they support in a non-inherited
* phpdoc comment.
*
* @return mixed
*
* @throws UnexpectedValueException
*/
public function decode($data, $format, array $context = array());
/**
* Checks whether the deserializer can decode from given format.
*
* @param string $format Format name
*
* @return bool
*/
public function supportsDecoding($format);
}

View file

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Defines the interface of encoders.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface EncoderInterface
{
/**
* Encodes data into the given format.
*
* @param mixed $data Data to encode
* @param string $format Format name
* @param array $context Options that normalizers/encoders have access to
*
* @return string|int|float|bool
*
* @throws UnexpectedValueException
*/
public function encode($data, $format, array $context = array());
/**
* Checks whether the serializer can encode to given format.
*
* @param string $format Format name
*
* @return bool
*/
public function supportsEncoding($format);
}

View file

@ -0,0 +1,107 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
/**
* Decodes JSON data.
*
* @author Sander Coolen <sander@jibber.nl>
*/
class JsonDecode implements DecoderInterface
{
protected $serializer;
private $associative;
private $recursionDepth;
/**
* Constructs a new JsonDecode instance.
*
* @param bool $associative True to return the result associative array, false for a nested stdClass hierarchy
* @param int $depth Specifies the recursion depth
*/
public function __construct($associative = false, $depth = 512)
{
$this->associative = $associative;
$this->recursionDepth = (int) $depth;
}
/**
* Decodes data.
*
* @param string $data The encoded JSON string to decode
* @param string $format Must be set to JsonEncoder::FORMAT
* @param array $context An optional set of options for the JSON decoder; see below
*
* The $context array is a simple key=>value array, with the following supported keys:
*
* json_decode_associative: boolean
* If true, returns the object as associative array.
* If false, returns the object as nested stdClass
* If not specified, this method will use the default set in JsonDecode::__construct
*
* json_decode_recursion_depth: integer
* Specifies the maximum recursion depth
* If not specified, this method will use the default set in JsonDecode::__construct
*
* json_decode_options: integer
* Specifies additional options as per documentation for json_decode. Only supported with PHP 5.4.0 and higher
*
* @return mixed
*
* @throws NotEncodableValueException
*
* @see http://php.net/json_decode json_decode
*/
public function decode($data, $format, array $context = array())
{
$context = $this->resolveContext($context);
$associative = $context['json_decode_associative'];
$recursionDepth = $context['json_decode_recursion_depth'];
$options = $context['json_decode_options'];
$decodedData = json_decode($data, $associative, $recursionDepth, $options);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new NotEncodableValueException(json_last_error_msg());
}
return $decodedData;
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format)
{
return JsonEncoder::FORMAT === $format;
}
/**
* Merges the default options of the Json Decoder with the passed context.
*
* @return array
*/
private function resolveContext(array $context)
{
$defaultOptions = array(
'json_decode_associative' => $this->associative,
'json_decode_recursion_depth' => $this->recursionDepth,
'json_decode_options' => 0,
);
return array_merge($defaultOptions, $context);
}
}

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
/**
* Encodes JSON data.
*
* @author Sander Coolen <sander@jibber.nl>
*/
class JsonEncode implements EncoderInterface
{
private $options;
public function __construct($bitmask = 0)
{
$this->options = $bitmask;
}
/**
* Encodes PHP data to a JSON string.
*
* {@inheritdoc}
*/
public function encode($data, $format, array $context = array())
{
$context = $this->resolveContext($context);
$encodedJson = json_encode($data, $context['json_encode_options']);
if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($context['json_encode_options'] & JSON_PARTIAL_OUTPUT_ON_ERROR))) {
throw new NotEncodableValueException(json_last_error_msg());
}
return $encodedJson;
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format)
{
return JsonEncoder::FORMAT === $format;
}
/**
* Merge default json encode options with context.
*
* @return array
*/
private function resolveContext(array $context = array())
{
return array_merge(array('json_encode_options' => $this->options), $context);
}
}

View file

@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
/**
* Encodes JSON data.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class JsonEncoder implements EncoderInterface, DecoderInterface
{
const FORMAT = 'json';
protected $encodingImpl;
protected $decodingImpl;
public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null)
{
$this->encodingImpl = $encodingImpl ?: new JsonEncode();
$this->decodingImpl = $decodingImpl ?: new JsonDecode(true);
}
/**
* {@inheritdoc}
*/
public function encode($data, $format, array $context = array())
{
return $this->encodingImpl->encode($data, self::FORMAT, $context);
}
/**
* {@inheritdoc}
*/
public function decode($data, $format, array $context = array())
{
return $this->decodingImpl->decode($data, self::FORMAT, $context);
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format)
{
return self::FORMAT === $format;
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format)
{
return self::FORMAT === $format;
}
}

View file

@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
/**
* Defines the interface of encoders that will normalize data themselves.
*
* Implementing this interface essentially just tells the Serializer that the
* data should not be pre-normalized before being passed to this Encoder.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface NormalizationAwareInterface
{
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
/**
* SerializerAware Encoder implementation.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @deprecated since version 3.2, to be removed in 4.0. Use the SerializerAwareTrait instead.
*/
abstract class SerializerAwareEncoder implements SerializerAwareInterface
{
use SerializerAwareTrait;
}

View file

@ -0,0 +1,567 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
/**
* Encodes XML data.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author John Wards <jwards@whiteoctober.co.uk>
* @author Fabian Vogler <fabian@equivalence.ch>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, DecoderInterface, NormalizationAwareInterface
{
const FORMAT = 'xml';
/**
* @var \DOMDocument
*/
private $dom;
private $format;
private $context;
private $rootNodeName = 'response';
private $loadOptions;
/**
* Construct new XmlEncoder and allow to change the root node element name.
*
* @param string $rootNodeName
* @param int|null $loadOptions A bit field of LIBXML_* constants
*/
public function __construct($rootNodeName = 'response', $loadOptions = null)
{
$this->rootNodeName = $rootNodeName;
$this->loadOptions = null !== $loadOptions ? $loadOptions : LIBXML_NONET | LIBXML_NOBLANKS;
}
/**
* {@inheritdoc}
*/
public function encode($data, $format, array $context = array())
{
if ($data instanceof \DOMDocument) {
return $data->saveXML();
}
$xmlRootNodeName = $this->resolveXmlRootName($context);
$this->dom = $this->createDomDocument($context);
$this->format = $format;
$this->context = $context;
if (null !== $data && !is_scalar($data)) {
$root = $this->dom->createElement($xmlRootNodeName);
$this->dom->appendChild($root);
$this->buildXml($root, $data, $xmlRootNodeName);
} else {
$this->appendNode($this->dom, $data, $xmlRootNodeName);
}
return $this->dom->saveXML();
}
/**
* {@inheritdoc}
*/
public function decode($data, $format, array $context = array())
{
if ('' === trim($data)) {
throw new NotEncodableValueException('Invalid XML data, it can not be empty.');
}
$internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true);
libxml_clear_errors();
$dom = new \DOMDocument();
$dom->loadXML($data, $this->loadOptions);
libxml_use_internal_errors($internalErrors);
libxml_disable_entity_loader($disableEntities);
if ($error = libxml_get_last_error()) {
libxml_clear_errors();
throw new NotEncodableValueException($error->message);
}
$rootNode = null;
foreach ($dom->childNodes as $child) {
if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
throw new NotEncodableValueException('Document types are not allowed.');
}
if (!$rootNode && XML_PI_NODE !== $child->nodeType) {
$rootNode = $child;
}
}
// todo: throw an exception if the root node name is not correctly configured (bc)
if ($rootNode->hasChildNodes()) {
$xpath = new \DOMXPath($dom);
$data = array();
foreach ($xpath->query('namespace::*', $dom->documentElement) as $nsNode) {
$data['@'.$nsNode->nodeName] = $nsNode->nodeValue;
}
unset($data['@xmlns:xml']);
if (empty($data)) {
return $this->parseXml($rootNode, $context);
}
return array_merge($data, (array) $this->parseXml($rootNode, $context));
}
if (!$rootNode->hasAttributes()) {
return $rootNode->nodeValue;
}
$data = array();
foreach ($rootNode->attributes as $attrKey => $attr) {
$data['@'.$attrKey] = $attr->nodeValue;
}
$data['#'] = $rootNode->nodeValue;
return $data;
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format)
{
return self::FORMAT === $format;
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format)
{
return self::FORMAT === $format;
}
/**
* Sets the root node name.
*
* @param string $name Root node name
*/
public function setRootNodeName($name)
{
$this->rootNodeName = $name;
}
/**
* Returns the root node name.
*
* @return string
*/
public function getRootNodeName()
{
return $this->rootNodeName;
}
/**
* @param \DOMNode $node
* @param string $val
*
* @return bool
*/
final protected function appendXMLString(\DOMNode $node, $val)
{
if (\strlen($val) > 0) {
$frag = $this->dom->createDocumentFragment();
$frag->appendXML($val);
$node->appendChild($frag);
return true;
}
return false;
}
/**
* @param \DOMNode $node
* @param string $val
*
* @return bool
*/
final protected function appendText(\DOMNode $node, $val)
{
$nodeText = $this->dom->createTextNode($val);
$node->appendChild($nodeText);
return true;
}
/**
* @param \DOMNode $node
* @param string $val
*
* @return bool
*/
final protected function appendCData(\DOMNode $node, $val)
{
$nodeText = $this->dom->createCDATASection($val);
$node->appendChild($nodeText);
return true;
}
/**
* @param \DOMNode $node
* @param \DOMDocumentFragment $fragment
*
* @return bool
*/
final protected function appendDocumentFragment(\DOMNode $node, $fragment)
{
if ($fragment instanceof \DOMDocumentFragment) {
$node->appendChild($fragment);
return true;
}
return false;
}
/**
* Checks the name is a valid xml element name.
*
* @param string $name
*
* @return bool
*/
final protected function isElementNameValid($name)
{
return $name &&
false === strpos($name, ' ') &&
preg_match('#^[\pL_][\pL0-9._:-]*$#ui', $name);
}
/**
* Parse the input DOMNode into an array or a string.
*
* @return array|string
*/
private function parseXml(\DOMNode $node, array $context = array())
{
$data = $this->parseXmlAttributes($node, $context);
$value = $this->parseXmlValue($node, $context);
if (!\count($data)) {
return $value;
}
if (!\is_array($value)) {
$data['#'] = $value;
return $data;
}
if (1 === \count($value) && key($value)) {
$data[key($value)] = current($value);
return $data;
}
foreach ($value as $key => $val) {
$data[$key] = $val;
}
return $data;
}
/**
* Parse the input DOMNode attributes into an array.
*
* @return array
*/
private function parseXmlAttributes(\DOMNode $node, array $context = array())
{
if (!$node->hasAttributes()) {
return array();
}
$data = array();
$typeCastAttributes = $this->resolveXmlTypeCastAttributes($context);
foreach ($node->attributes as $attr) {
if (!is_numeric($attr->nodeValue) || !$typeCastAttributes) {
$data['@'.$attr->nodeName] = $attr->nodeValue;
continue;
}
if (false !== $val = filter_var($attr->nodeValue, FILTER_VALIDATE_INT)) {
$data['@'.$attr->nodeName] = $val;
continue;
}
$data['@'.$attr->nodeName] = (float) $attr->nodeValue;
}
return $data;
}
/**
* Parse the input DOMNode value (content and children) into an array or a string.
*
* @return array|string
*/
private function parseXmlValue(\DOMNode $node, array $context = array())
{
if (!$node->hasChildNodes()) {
return $node->nodeValue;
}
if (1 === $node->childNodes->length && \in_array($node->firstChild->nodeType, array(XML_TEXT_NODE, XML_CDATA_SECTION_NODE))) {
return $node->firstChild->nodeValue;
}
$value = array();
foreach ($node->childNodes as $subnode) {
if (XML_PI_NODE === $subnode->nodeType) {
continue;
}
$val = $this->parseXml($subnode, $context);
if ('item' === $subnode->nodeName && isset($val['@key'])) {
if (isset($val['#'])) {
$value[$val['@key']] = $val['#'];
} else {
$value[$val['@key']] = $val;
}
} else {
$value[$subnode->nodeName][] = $val;
}
}
foreach ($value as $key => $val) {
if (\is_array($val) && 1 === \count($val)) {
$value[$key] = current($val);
}
}
return $value;
}
/**
* Parse the data and convert it to DOMElements.
*
* @param \DOMNode $parentNode
* @param array|object $data
* @param string|null $xmlRootNodeName
*
* @return bool
*
* @throws NotEncodableValueException
*/
private function buildXml(\DOMNode $parentNode, $data, $xmlRootNodeName = null)
{
$append = true;
if (\is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) {
foreach ($data as $key => $data) {
//Ah this is the magic @ attribute types.
if (0 === strpos($key, '@') && $this->isElementNameValid($attributeName = substr($key, 1))) {
if (!is_scalar($data)) {
$data = $this->serializer->normalize($data, $this->format, $this->context);
}
$parentNode->setAttribute($attributeName, $data);
} elseif ('#' === $key) {
$append = $this->selectNodeType($parentNode, $data);
} elseif (\is_array($data) && false === is_numeric($key)) {
// Is this array fully numeric keys?
if (ctype_digit(implode('', array_keys($data)))) {
/*
* Create nodes to append to $parentNode based on the $key of this array
* Produces <xml><item>0</item><item>1</item></xml>
* From array("item" => array(0,1));.
*/
foreach ($data as $subData) {
$append = $this->appendNode($parentNode, $subData, $key);
}
} else {
$append = $this->appendNode($parentNode, $data, $key);
}
} elseif (is_numeric($key) || !$this->isElementNameValid($key)) {
$append = $this->appendNode($parentNode, $data, 'item', $key);
} elseif (null !== $data || !isset($this->context['remove_empty_tags']) || false === $this->context['remove_empty_tags']) {
$append = $this->appendNode($parentNode, $data, $key);
}
}
return $append;
}
if (\is_object($data)) {
$data = $this->serializer->normalize($data, $this->format, $this->context);
if (null !== $data && !is_scalar($data)) {
return $this->buildXml($parentNode, $data, $xmlRootNodeName);
}
// top level data object was normalized into a scalar
if (!$parentNode->parentNode->parentNode) {
$root = $parentNode->parentNode;
$root->removeChild($parentNode);
return $this->appendNode($root, $data, $xmlRootNodeName);
}
return $this->appendNode($parentNode, $data, 'data');
}
throw new NotEncodableValueException(sprintf('An unexpected value could not be serialized: %s', var_export($data, true)));
}
/**
* Selects the type of node to create and appends it to the parent.
*
* @param \DOMNode $parentNode
* @param array|object $data
* @param string $nodeName
* @param string $key
*
* @return bool
*/
private function appendNode(\DOMNode $parentNode, $data, $nodeName, $key = null)
{
$node = $this->dom->createElement($nodeName);
if (null !== $key) {
$node->setAttribute('key', $key);
}
$appendNode = $this->selectNodeType($node, $data);
// we may have decided not to append this node, either in error or if its $nodeName is not valid
if ($appendNode) {
$parentNode->appendChild($node);
}
return $appendNode;
}
/**
* Checks if a value contains any characters which would require CDATA wrapping.
*
* @param string $val
*
* @return bool
*/
private function needsCdataWrapping($val)
{
return 0 < preg_match('/[<>&]/', $val);
}
/**
* Tests the value being passed and decide what sort of element to create.
*
* @param \DOMNode $node
* @param mixed $val
*
* @return bool
*
* @throws NotEncodableValueException
*/
private function selectNodeType(\DOMNode $node, $val)
{
if (\is_array($val)) {
return $this->buildXml($node, $val);
} elseif ($val instanceof \SimpleXMLElement) {
$child = $this->dom->importNode(dom_import_simplexml($val), true);
$node->appendChild($child);
} elseif ($val instanceof \Traversable) {
$this->buildXml($node, $val);
} elseif (\is_object($val)) {
return $this->selectNodeType($node, $this->serializer->normalize($val, $this->format, $this->context));
} elseif (is_numeric($val)) {
return $this->appendText($node, (string) $val);
} elseif (\is_string($val) && $this->needsCdataWrapping($val)) {
return $this->appendCData($node, $val);
} elseif (\is_string($val)) {
return $this->appendText($node, $val);
} elseif (\is_bool($val)) {
return $this->appendText($node, (int) $val);
} elseif ($val instanceof \DOMNode) {
$child = $this->dom->importNode($val, true);
$node->appendChild($child);
}
return true;
}
/**
* Get real XML root node name, taking serializer options into account.
*
* @return string
*/
private function resolveXmlRootName(array $context = array())
{
return isset($context['xml_root_node_name'])
? $context['xml_root_node_name']
: $this->rootNodeName;
}
/**
* Get XML option for type casting attributes Defaults to true.
*
* @param array $context
*
* @return bool
*/
private function resolveXmlTypeCastAttributes(array $context = array())
{
return isset($context['xml_type_cast_attributes'])
? (bool) $context['xml_type_cast_attributes']
: true;
}
/**
* Create a DOM document, taking serializer options into account.
*
* @param array $context Options that the encoder has access to
*
* @return \DOMDocument
*/
private function createDomDocument(array $context)
{
$document = new \DOMDocument();
// Set an attribute on the DOM document specifying, as part of the XML declaration,
$xmlOptions = array(
// nicely formats output with indentation and extra space
'xml_format_output' => 'formatOutput',
// the version number of the document
'xml_version' => 'xmlVersion',
// the encoding of the document
'xml_encoding' => 'encoding',
// whether the document is standalone
'xml_standalone' => 'xmlStandalone',
);
foreach ($xmlOptions as $xmlOption => $documentProperty) {
if (isset($context[$xmlOption])) {
$document->$documentProperty = $context[$xmlOption];
}
}
return $document;
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\Yaml\Parser;
/**
* Encodes YAML data.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class YamlEncoder implements EncoderInterface, DecoderInterface
{
const FORMAT = 'yaml';
private $dumper;
private $parser;
private $defaultContext = array('yaml_inline' => 0, 'yaml_indent' => 0, 'yaml_flags' => 0);
public function __construct(Dumper $dumper = null, Parser $parser = null, array $defaultContext = array())
{
if (!class_exists(Dumper::class)) {
throw new RuntimeException('The YamlEncoder class requires the "Yaml" component. Install "symfony/yaml" to use it.');
}
$this->dumper = $dumper ?: new Dumper();
$this->parser = $parser ?: new Parser();
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
}
/**
* {@inheritdoc}
*/
public function encode($data, $format, array $context = array())
{
$context = array_merge($this->defaultContext, $context);
return $this->dumper->dump($data, $context['yaml_inline'], $context['yaml_indent'], $context['yaml_flags']);
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format)
{
return self::FORMAT === $format;
}
/**
* {@inheritdoc}
*/
public function decode($data, $format, array $context = array())
{
$context = array_merge($this->defaultContext, $context);
return $this->parser->parse($data, $context['yaml_flags']);
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format)
{
return self::FORMAT === $format;
}
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* CircularReferenceException.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CircularReferenceException extends RuntimeException
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* Base exception interface.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ExceptionInterface
{
}

View file

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* ExtraAttributesException.
*
* @author Julien DIDIER <julien@didier.io>
*/
class ExtraAttributesException extends RuntimeException
{
private $extraAttributes;
public function __construct(array $extraAttributes, \Exception $previous = null)
{
$msg = sprintf('Extra attributes are not allowed ("%s" are unknown).', implode('", "', $extraAttributes));
$this->extraAttributes = $extraAttributes;
parent::__construct($msg, 0, $previous);
}
/**
* Get the extra attributes that are not allowed.
*
* @return array
*/
public function getExtraAttributes()
{
return $this->extraAttributes;
}
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* InvalidArgumentException.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* LogicException.
*
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* MappingException.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class MappingException extends RuntimeException
{
}

View file

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class NotEncodableValueException extends UnexpectedValueException
{
}

View file

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class NotNormalizableValueException extends UnexpectedValueException
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* RuntimeException.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* UnexpectedValueException.
*
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
*/
class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Exception;
/**
* UnsupportedException.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class UnsupportedException extends InvalidArgumentException
{
}

19
vendor/symfony/serializer/LICENSE vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2004-2018 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,120 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping;
/**
* {@inheritdoc}
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class AttributeMetadata implements AttributeMetadataInterface
{
/**
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getName()} instead.
*/
public $name;
/**
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getGroups()} instead.
*/
public $groups = array();
/**
* @var int|null
*
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getMaxDepth()} instead.
*/
public $maxDepth;
/**
* Constructs a metadata for the given attribute.
*
* @param string $name
*/
public function __construct($name)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function addGroup($group)
{
if (!\in_array($group, $this->groups)) {
$this->groups[] = $group;
}
}
/**
* {@inheritdoc}
*/
public function getGroups()
{
return $this->groups;
}
/**
* {@inheritdoc}
*/
public function setMaxDepth($maxDepth)
{
$this->maxDepth = $maxDepth;
}
/**
* {@inheritdoc}
*/
public function getMaxDepth()
{
return $this->maxDepth;
}
/**
* {@inheritdoc}
*/
public function merge(AttributeMetadataInterface $attributeMetadata)
{
foreach ($attributeMetadata->getGroups() as $group) {
$this->addGroup($group);
}
// Overwrite only if not defined
if (null === $this->maxDepth) {
$this->maxDepth = $attributeMetadata->getMaxDepth();
}
}
/**
* Returns the names of the properties that should be serialized.
*
* @return string[]
*/
public function __sleep()
{
return array('name', 'groups', 'maxDepth');
}
}

View file

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping;
/**
* Stores metadata needed for serializing and deserializing attributes.
*
* Primarily, the metadata stores serialization groups.
*
* @internal
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface AttributeMetadataInterface
{
/**
* Gets the attribute name.
*
* @return string
*/
public function getName();
/**
* Adds this attribute to the given group.
*
* @param string $group
*/
public function addGroup($group);
/**
* Gets groups of this attribute.
*
* @return string[]
*/
public function getGroups();
/**
* Sets the serialization max depth for this attribute.
*
* @param int|null $maxDepth
*/
public function setMaxDepth($maxDepth);
/**
* Gets the serialization max depth for this attribute.
*
* @return int|null
*/
public function getMaxDepth();
/**
* Merges an {@see AttributeMetadataInterface} with in the current one.
*/
public function merge(AttributeMetadataInterface $attributeMetadata);
}

View file

@ -0,0 +1,114 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping;
/**
* {@inheritdoc}
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ClassMetadata implements ClassMetadataInterface
{
/**
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getName()} instead.
*/
public $name;
/**
* @var AttributeMetadataInterface[]
*
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getAttributesMetadata()} instead.
*/
public $attributesMetadata = array();
/**
* @var \ReflectionClass
*/
private $reflClass;
/**
* Constructs a metadata for the given class.
*
* @param string $class
*/
public function __construct($class)
{
$this->name = $class;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata)
{
$this->attributesMetadata[$attributeMetadata->getName()] = $attributeMetadata;
}
/**
* {@inheritdoc}
*/
public function getAttributesMetadata()
{
return $this->attributesMetadata;
}
/**
* {@inheritdoc}
*/
public function merge(ClassMetadataInterface $classMetadata)
{
foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) {
if (isset($this->attributesMetadata[$attributeMetadata->getName()])) {
$this->attributesMetadata[$attributeMetadata->getName()]->merge($attributeMetadata);
} else {
$this->addAttributeMetadata($attributeMetadata);
}
}
}
/**
* {@inheritdoc}
*/
public function getReflectionClass()
{
if (!$this->reflClass) {
$this->reflClass = new \ReflectionClass($this->getName());
}
return $this->reflClass;
}
/**
* Returns the names of the properties that should be serialized.
*
* @return string[]
*/
public function __sleep()
{
return array(
'name',
'attributesMetadata',
);
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping;
/**
* Stores metadata needed for serializing and deserializing objects of specific class.
*
* Primarily, the metadata stores the set of attributes to serialize or deserialize.
*
* There may only exist one metadata for each attribute according to its name.
*
* @internal
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface ClassMetadataInterface
{
/**
* Returns the name of the backing PHP class.
*
* @return string The name of the backing class
*/
public function getName();
/**
* Adds an {@link AttributeMetadataInterface}.
*/
public function addAttributeMetadata(AttributeMetadataInterface $attributeMetadata);
/**
* Gets the list of {@link AttributeMetadataInterface}.
*
* @return AttributeMetadataInterface[]
*/
public function getAttributesMetadata();
/**
* Merges a {@link ClassMetadataInterface} in the current one.
*/
public function merge(self $classMetadata);
/**
* Returns a {@link \ReflectionClass} instance for this class.
*
* @return \ReflectionClass
*/
public function getReflectionClass();
}

View file

@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Factory;
use Psr\Cache\CacheItemPoolInterface;
/**
* Caches metadata using a PSR-6 implementation.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CacheClassMetadataFactory implements ClassMetadataFactoryInterface
{
use ClassResolverTrait;
/**
* @var ClassMetadataFactoryInterface
*/
private $decorated;
/**
* @var CacheItemPoolInterface
*/
private $cacheItemPool;
public function __construct(ClassMetadataFactoryInterface $decorated, CacheItemPoolInterface $cacheItemPool)
{
$this->decorated = $decorated;
$this->cacheItemPool = $cacheItemPool;
}
/**
* {@inheritdoc}
*/
public function getMetadataFor($value)
{
$class = $this->getClass($value);
// Key cannot contain backslashes according to PSR-6
$key = strtr($class, '\\', '_');
$item = $this->cacheItemPool->getItem($key);
if ($item->isHit()) {
return $item->get();
}
$metadata = $this->decorated->getMetadataFor($value);
$this->cacheItemPool->save($item->set($metadata));
return $metadata;
}
/**
* {@inheritdoc}
*/
public function hasMetadataFor($value)
{
return $this->decorated->hasMetadataFor($value);
}
}

View file

@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Factory;
use Doctrine\Common\Cache\Cache;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
/**
* Returns a {@link ClassMetadata}.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ClassMetadataFactory implements ClassMetadataFactoryInterface
{
use ClassResolverTrait;
private $loader;
private $cache;
private $loadedClasses;
public function __construct(LoaderInterface $loader, Cache $cache = null)
{
$this->loader = $loader;
$this->cache = $cache;
if (null !== $cache) {
@trigger_error(sprintf('Passing a Doctrine Cache instance as 2nd parameter of the "%s" constructor is deprecated since Symfony 3.1. This parameter will be removed in Symfony 4.0. Use the "%s" class instead.', __CLASS__, CacheClassMetadataFactory::class), E_USER_DEPRECATED);
}
}
/**
* {@inheritdoc}
*/
public function getMetadataFor($value)
{
$class = $this->getClass($value);
if (isset($this->loadedClasses[$class])) {
return $this->loadedClasses[$class];
}
if ($this->cache && ($this->loadedClasses[$class] = $this->cache->fetch($class))) {
return $this->loadedClasses[$class];
}
$classMetadata = new ClassMetadata($class);
$this->loader->loadClassMetadata($classMetadata);
$reflectionClass = $classMetadata->getReflectionClass();
// Include metadata from the parent class
if ($parent = $reflectionClass->getParentClass()) {
$classMetadata->merge($this->getMetadataFor($parent->name));
}
// Include metadata from all implemented interfaces
foreach ($reflectionClass->getInterfaces() as $interface) {
$classMetadata->merge($this->getMetadataFor($interface->name));
}
if ($this->cache) {
$this->cache->save($class, $classMetadata);
}
return $this->loadedClasses[$class] = $classMetadata;
}
/**
* {@inheritdoc}
*/
public function hasMetadataFor($value)
{
try {
$this->getClass($value);
return true;
} catch (InvalidArgumentException $invalidArgumentException) {
// Return false in case of exception
}
return false;
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Factory;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Returns a {@see ClassMetadataInterface}.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface ClassMetadataFactoryInterface
{
/**
* If the method was called with the same class name (or an object of that
* class) before, the same metadata instance is returned.
*
* If the factory was configured with a cache, this method will first look
* for an existing metadata instance in the cache. If an existing instance
* is found, it will be returned without further ado.
*
* Otherwise, a new metadata instance is created. If the factory was
* configured with a loader, the metadata is passed to the
* {@link \Symfony\Component\Serializer\Mapping\Loader\LoaderInterface::loadClassMetadata()} method for further
* configuration. At last, the new object is returned.
*
* @param string|object $value
*
* @return ClassMetadataInterface
*
* @throws InvalidArgumentException
*/
public function getMetadataFor($value);
/**
* Checks if class has metadata.
*
* @param mixed $value
*
* @return bool
*/
public function hasMetadataFor($value);
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Factory;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
/**
* Resolves a class name.
*
* @internal
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
trait ClassResolverTrait
{
/**
* Gets a class name for a given class or instance.
*
* @param mixed $value
*
* @return string
*
* @throws InvalidArgumentException If the class does not exists
*/
private function getClass($value)
{
if (\is_string($value)) {
if (!class_exists($value) && !interface_exists($value)) {
throw new InvalidArgumentException(sprintf('The class or interface "%s" does not exist.', $value));
}
return ltrim($value, '\\');
}
if (!\is_object($value)) {
throw new InvalidArgumentException(sprintf('Cannot create metadata for non-objects. Got: "%s"', \gettype($value)));
}
return \get_class($value);
}
}

View file

@ -0,0 +1,107 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Annotation loader.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class AnnotationLoader implements LoaderInterface
{
private $reader;
public function __construct(Reader $reader)
{
$this->reader = $reader;
}
/**
* {@inheritdoc}
*/
public function loadClassMetadata(ClassMetadataInterface $classMetadata)
{
$reflectionClass = $classMetadata->getReflectionClass();
$className = $reflectionClass->name;
$loaded = false;
$attributesMetadata = $classMetadata->getAttributesMetadata();
foreach ($reflectionClass->getProperties() as $property) {
if (!isset($attributesMetadata[$property->name])) {
$attributesMetadata[$property->name] = new AttributeMetadata($property->name);
$classMetadata->addAttributeMetadata($attributesMetadata[$property->name]);
}
if ($property->getDeclaringClass()->name === $className) {
foreach ($this->reader->getPropertyAnnotations($property) as $annotation) {
if ($annotation instanceof Groups) {
foreach ($annotation->getGroups() as $group) {
$attributesMetadata[$property->name]->addGroup($group);
}
} elseif ($annotation instanceof MaxDepth) {
$attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
}
$loaded = true;
}
}
}
foreach ($reflectionClass->getMethods() as $method) {
if ($method->getDeclaringClass()->name !== $className) {
continue;
}
$accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches);
if ($accessorOrMutator) {
$attributeName = lcfirst($matches[2]);
if (isset($attributesMetadata[$attributeName])) {
$attributeMetadata = $attributesMetadata[$attributeName];
} else {
$attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName);
$classMetadata->addAttributeMetadata($attributeMetadata);
}
}
foreach ($this->reader->getMethodAnnotations($method) as $annotation) {
if ($annotation instanceof Groups) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('Groups on "%s::%s" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
}
foreach ($annotation->getGroups() as $group) {
$attributeMetadata->addGroup($group);
}
} elseif ($annotation instanceof MaxDepth) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('MaxDepth on "%s::%s" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
}
$attributeMetadata->setMaxDepth($annotation->getMaxDepth());
}
$loaded = true;
}
}
return $loaded;
}
}

View file

@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Symfony\Component\Serializer\Exception\MappingException;
/**
* Base class for all file based loaders.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
abstract class FileLoader implements LoaderInterface
{
protected $file;
/**
* @param string $file The mapping file to load
*
* @throws MappingException if the mapping file does not exist or is not readable
*/
public function __construct($file)
{
if (!is_file($file)) {
throw new MappingException(sprintf('The mapping file %s does not exist', $file));
}
if (!is_readable($file)) {
throw new MappingException(sprintf('The mapping file %s is not readable', $file));
}
$this->file = $file;
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Calls multiple {@link LoaderInterface} instances in a chain.
*
* This class accepts multiple instances of LoaderInterface to be passed to the
* constructor. When {@link loadClassMetadata()} is called, the same method is called
* in <em>all</em> of these loaders, regardless of whether any of them was
* successful or not.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class LoaderChain implements LoaderInterface
{
private $loaders;
/**
* Accepts a list of LoaderInterface instances.
*
* @param LoaderInterface[] $loaders An array of LoaderInterface instances
*
* @throws MappingException If any of the loaders does not implement LoaderInterface
*/
public function __construct(array $loaders)
{
foreach ($loaders as $loader) {
if (!$loader instanceof LoaderInterface) {
throw new MappingException(sprintf('Class %s is expected to implement LoaderInterface', \get_class($loader)));
}
}
$this->loaders = $loaders;
}
/**
* {@inheritdoc}
*/
public function loadClassMetadata(ClassMetadataInterface $metadata)
{
$success = false;
foreach ($this->loaders as $loader) {
$success = $loader->loadClassMetadata($metadata) || $success;
}
return $success;
}
/**
* @return LoaderInterface[]
*/
public function getLoaders()
{
return $this->loaders;
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Loads {@link ClassMetadataInterface}.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface LoaderInterface
{
/**
* @return bool
*/
public function loadClassMetadata(ClassMetadataInterface $classMetadata);
}

View file

@ -0,0 +1,121 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Loads XML mapping files.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class XmlFileLoader extends FileLoader
{
/**
* An array of {@class \SimpleXMLElement} instances.
*
* @var \SimpleXMLElement[]|null
*/
private $classes;
/**
* {@inheritdoc}
*/
public function loadClassMetadata(ClassMetadataInterface $classMetadata)
{
if (null === $this->classes) {
$this->classes = $this->getClassesFromXml();
}
if (!$this->classes) {
return false;
}
$attributesMetadata = $classMetadata->getAttributesMetadata();
if (isset($this->classes[$classMetadata->getName()])) {
$xml = $this->classes[$classMetadata->getName()];
foreach ($xml->attribute as $attribute) {
$attributeName = (string) $attribute['name'];
if (isset($attributesMetadata[$attributeName])) {
$attributeMetadata = $attributesMetadata[$attributeName];
} else {
$attributeMetadata = new AttributeMetadata($attributeName);
$classMetadata->addAttributeMetadata($attributeMetadata);
}
foreach ($attribute->group as $group) {
$attributeMetadata->addGroup((string) $group);
}
if (isset($attribute['max-depth'])) {
$attributeMetadata->setMaxDepth((int) $attribute['max-depth']);
}
}
return true;
}
return false;
}
/**
* Return the names of the classes mapped in this file.
*
* @return string[] The classes names
*/
public function getMappedClasses()
{
if (null === $this->classes) {
$this->classes = $this->getClassesFromXml();
}
return array_keys($this->classes);
}
/**
* Parses a XML File.
*
* @param string $file Path of file
*
* @return \SimpleXMLElement
*
* @throws MappingException
*/
private function parseFile($file)
{
try {
$dom = XmlUtils::loadFile($file, __DIR__.'/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd');
} catch (\Exception $e) {
throw new MappingException($e->getMessage(), $e->getCode(), $e);
}
return simplexml_import_dom($dom);
}
private function getClassesFromXml()
{
$xml = $this->parseFile($this->file);
$classes = array();
foreach ($xml->class as $class) {
$classes[(string) $class['name']] = $class;
}
return $classes;
}
}

View file

@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
use Symfony\Component\Yaml\Parser;
/**
* YAML File Loader.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class YamlFileLoader extends FileLoader
{
private $yamlParser;
/**
* An array of YAML class descriptions.
*
* @var array
*/
private $classes;
/**
* {@inheritdoc}
*/
public function loadClassMetadata(ClassMetadataInterface $classMetadata)
{
if (null === $this->classes) {
$this->classes = $this->getClassesFromYaml();
}
if (!$this->classes) {
return false;
}
if (!isset($this->classes[$classMetadata->getName()])) {
return false;
}
$yaml = $this->classes[$classMetadata->getName()];
if (isset($yaml['attributes']) && \is_array($yaml['attributes'])) {
$attributesMetadata = $classMetadata->getAttributesMetadata();
foreach ($yaml['attributes'] as $attribute => $data) {
if (isset($attributesMetadata[$attribute])) {
$attributeMetadata = $attributesMetadata[$attribute];
} else {
$attributeMetadata = new AttributeMetadata($attribute);
$classMetadata->addAttributeMetadata($attributeMetadata);
}
if (isset($data['groups'])) {
if (!\is_array($data['groups'])) {
throw new MappingException(sprintf('The "groups" key must be an array of strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
foreach ($data['groups'] as $group) {
if (!\is_string($group)) {
throw new MappingException(sprintf('Group names must be strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
$attributeMetadata->addGroup($group);
}
}
if (isset($data['max_depth'])) {
if (!\is_int($data['max_depth'])) {
throw new MappingException(sprintf('The "max_depth" value must be an integer in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
$attributeMetadata->setMaxDepth($data['max_depth']);
}
}
}
return true;
}
/**
* Return the names of the classes mapped in this file.
*
* @return string[] The classes names
*/
public function getMappedClasses()
{
if (null === $this->classes) {
$this->classes = $this->getClassesFromYaml();
}
return array_keys($this->classes);
}
private function getClassesFromYaml()
{
if (!stream_is_local($this->file)) {
throw new MappingException(sprintf('This is not a local file "%s".', $this->file));
}
if (null === $this->yamlParser) {
$this->yamlParser = new Parser();
}
$classes = $this->yamlParser->parseFile($this->file);
if (empty($classes)) {
return array();
}
if (!\is_array($classes)) {
throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file));
}
return $classes;
}
}

View file

@ -0,0 +1,63 @@
<?xml version="1.0" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/serializer-mapping"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/serializer-mapping"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Symfony Serializer Mapping Schema, version 1.0
Authors: Kévin Dunglas
A serializer mapping connects attributes with serialization groups.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="serializer" type="serializer" />
<xsd:complexType name="serializer">
<xsd:annotation>
<xsd:documentation><![CDATA[
The root element of the serializer mapping definition.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="class" type="class" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="class">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains serialization groups for a single class.
Nested elements may be class property and/or getter definitions.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="attribute" type="attribute" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="attribute">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains serialization groups and max depth for attributes. The name of the attribute should be given in the "name" option.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence minOccurs="0">
<xsd:element name="group" type="xsd:string" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="max-depth">
<xsd:simpleType>
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="0" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\NameConverter;
/**
* CamelCase to Underscore name converter.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CamelCaseToSnakeCaseNameConverter implements NameConverterInterface
{
private $attributes;
private $lowerCamelCase;
/**
* @param array|null $attributes The list of attributes to rename or null for all attributes
* @param bool $lowerCamelCase Use lowerCamelCase style
*/
public function __construct(array $attributes = null, $lowerCamelCase = true)
{
$this->attributes = $attributes;
$this->lowerCamelCase = $lowerCamelCase;
}
/**
* {@inheritdoc}
*/
public function normalize($propertyName)
{
if (null === $this->attributes || \in_array($propertyName, $this->attributes)) {
return strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName)));
}
return $propertyName;
}
/**
* {@inheritdoc}
*/
public function denormalize($propertyName)
{
$camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
}, $propertyName);
if ($this->lowerCamelCase) {
$camelCasedName = lcfirst($camelCasedName);
}
if (null === $this->attributes || \in_array($camelCasedName, $this->attributes)) {
return $camelCasedName;
}
return $propertyName;
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\NameConverter;
/**
* Defines the interface for property name converters.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface NameConverterInterface
{
/**
* Converts a property name to its normalized value.
*
* @param string $propertyName
*
* @return string
*/
public function normalize($propertyName);
/**
* Converts a property name to its denormalized value.
*
* @param string $propertyName
*
* @return string
*/
public function denormalize($propertyName);
}

View file

@ -0,0 +1,411 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\Exception\CircularReferenceException;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
/**
* Normalizer implementation.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
abstract class AbstractNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
{
use ObjectToPopulateTrait;
const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit';
const OBJECT_TO_POPULATE = 'object_to_populate';
const GROUPS = 'groups';
const ATTRIBUTES = 'attributes';
const ALLOW_EXTRA_ATTRIBUTES = 'allow_extra_attributes';
/**
* @var int
*/
protected $circularReferenceLimit = 1;
/**
* @var callable
*/
protected $circularReferenceHandler;
/**
* @var ClassMetadataFactoryInterface|null
*/
protected $classMetadataFactory;
/**
* @var NameConverterInterface|null
*/
protected $nameConverter;
/**
* @var array
*/
protected $callbacks = array();
/**
* @var array
*/
protected $ignoredAttributes = array();
/**
* @var array
*/
protected $camelizedAttributes = array();
/**
* Sets the {@link ClassMetadataFactoryInterface} to use.
*/
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null)
{
$this->classMetadataFactory = $classMetadataFactory;
$this->nameConverter = $nameConverter;
}
/**
* Set circular reference limit.
*
* @param int $circularReferenceLimit Limit of iterations for the same object
*
* @return self
*/
public function setCircularReferenceLimit($circularReferenceLimit)
{
$this->circularReferenceLimit = $circularReferenceLimit;
return $this;
}
/**
* Set circular reference handler.
*
* @param callable $circularReferenceHandler
*
* @return self
*/
public function setCircularReferenceHandler(callable $circularReferenceHandler)
{
$this->circularReferenceHandler = $circularReferenceHandler;
return $this;
}
/**
* Set normalization callbacks.
*
* @param callable[] $callbacks Help normalize the result
*
* @return self
*
* @throws InvalidArgumentException if a non-callable callback is set
*/
public function setCallbacks(array $callbacks)
{
foreach ($callbacks as $attribute => $callback) {
if (!\is_callable($callback)) {
throw new InvalidArgumentException(sprintf('The given callback for attribute "%s" is not callable.', $attribute));
}
}
$this->callbacks = $callbacks;
return $this;
}
/**
* Set ignored attributes for normalization and denormalization.
*
* @return self
*/
public function setIgnoredAttributes(array $ignoredAttributes)
{
$this->ignoredAttributes = $ignoredAttributes;
return $this;
}
/**
* Detects if the configured circular reference limit is reached.
*
* @param object $object
* @param array $context
*
* @return bool
*
* @throws CircularReferenceException
*/
protected function isCircularReference($object, &$context)
{
$objectHash = spl_object_hash($object);
if (isset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash])) {
if ($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] >= $this->circularReferenceLimit) {
unset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]);
return true;
}
++$context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash];
} else {
$context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] = 1;
}
return false;
}
/**
* Handles a circular reference.
*
* If a circular reference handler is set, it will be called. Otherwise, a
* {@class CircularReferenceException} will be thrown.
*
* @param object $object
*
* @return mixed
*
* @throws CircularReferenceException
*/
protected function handleCircularReference($object)
{
if ($this->circularReferenceHandler) {
return \call_user_func($this->circularReferenceHandler, $object);
}
throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $this->circularReferenceLimit));
}
/**
* Gets attributes to normalize using groups.
*
* @param string|object $classOrObject
* @param array $context
* @param bool $attributesAsString If false, return an array of {@link AttributeMetadataInterface}
*
* @throws LogicException if the 'allow_extra_attributes' context variable is false and no class metadata factory is provided
*
* @return string[]|AttributeMetadataInterface[]|bool
*/
protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false)
{
if (!$this->classMetadataFactory) {
if (isset($context[static::ALLOW_EXTRA_ATTRIBUTES]) && !$context[static::ALLOW_EXTRA_ATTRIBUTES]) {
throw new LogicException(sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.', static::ALLOW_EXTRA_ATTRIBUTES));
}
return false;
}
$groups = false;
if (isset($context[static::GROUPS]) && \is_array($context[static::GROUPS])) {
$groups = $context[static::GROUPS];
} elseif (!isset($context[static::ALLOW_EXTRA_ATTRIBUTES]) || $context[static::ALLOW_EXTRA_ATTRIBUTES]) {
return false;
}
$allowedAttributes = array();
foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) {
$name = $attributeMetadata->getName();
if (
(false === $groups || array_intersect($attributeMetadata->getGroups(), $groups)) &&
$this->isAllowedAttribute($classOrObject, $name, null, $context)
) {
$allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata;
}
}
return $allowedAttributes;
}
/**
* Is this attribute allowed?
*
* @param object|string $classOrObject
* @param string $attribute
* @param string|null $format
* @param array $context
*
* @return bool
*/
protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array())
{
if (\in_array($attribute, $this->ignoredAttributes)) {
return false;
}
if (isset($context[self::ATTRIBUTES][$attribute])) {
// Nested attributes
return true;
}
if (isset($context[self::ATTRIBUTES]) && \is_array($context[self::ATTRIBUTES])) {
return \in_array($attribute, $context[self::ATTRIBUTES], true);
}
return true;
}
/**
* Normalizes the given data to an array. It's particularly useful during
* the denormalization process.
*
* @param object|array $data
*
* @return array
*/
protected function prepareForDenormalization($data)
{
return (array) $data;
}
/**
* Returns the method to use to construct an object. This method must be either
* the object constructor or static.
*
* @param array $data
* @param string $class
* @param array $context
* @param \ReflectionClass $reflectionClass
* @param array|bool $allowedAttributes
*
* @return \ReflectionMethod|null
*/
protected function getConstructor(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)
{
return $reflectionClass->getConstructor();
}
/**
* Instantiates an object using constructor parameters when needed.
*
* This method also allows to denormalize data into an existing object if
* it is present in the context with the object_to_populate. This object
* is removed from the context before being returned to avoid side effects
* when recursively normalizing an object graph.
*
* @param array $data
* @param string $class
* @param array $context
* @param \ReflectionClass $reflectionClass
* @param array|bool $allowedAttributes
* @param string|null $format
*
* @return object
*
* @throws RuntimeException
*/
protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes/*, string $format = null*/)
{
if (\func_num_args() >= 6) {
$format = \func_get_arg(5);
} else {
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, __FUNCTION__);
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('Method %s::%s() will have a 6th `string $format = null` argument in version 4.0. Not defining it is deprecated since Symfony 3.2.', \get_class($this), __FUNCTION__), E_USER_DEPRECATED);
}
}
$format = null;
}
if (null !== $object = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) {
unset($context[static::OBJECT_TO_POPULATE]);
return $object;
}
$constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
if ($constructor) {
$constructorParameters = $constructor->getParameters();
$params = array();
foreach ($constructorParameters as $constructorParameter) {
$paramName = $constructorParameter->name;
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;
$allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes);
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
if (method_exists($constructorParameter, 'isVariadic') && $constructorParameter->isVariadic()) {
if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
if (!\is_array($data[$paramName])) {
throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.', $class, $constructorParameter->name));
}
$params = array_merge($params, $data[$paramName]);
}
} elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
$parameterData = $data[$key];
if (null === $parameterData && $constructorParameter->allowsNull()) {
$params[] = null;
// Don't run set for a parameter passed to the constructor
unset($data[$key]);
continue;
}
try {
if (null !== $constructorParameter->getClass()) {
if (!$this->serializer instanceof DenormalizerInterface) {
throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $constructorParameter->getClass(), static::class));
}
$parameterClass = $constructorParameter->getClass()->getName();
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $paramName));
}
} catch (\ReflectionException $e) {
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $key), 0, $e);
}
// Don't run set for a parameter passed to the constructor
$params[] = $parameterData;
unset($data[$key]);
} elseif ($constructorParameter->isDefaultValueAvailable()) {
$params[] = $constructorParameter->getDefaultValue();
} else {
throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name));
}
}
if ($constructor->isConstructor()) {
return $reflectionClass->newInstanceArgs($params);
} else {
return $constructor->invokeArgs(null, $params);
}
}
return new $class();
}
/**
* @param array $parentContext
* @param string $attribute
*
* @return array
*
* @internal
*/
protected function createChildContext(array $parentContext, $attribute)
{
if (isset($parentContext[self::ATTRIBUTES][$attribute])) {
$parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute];
} else {
unset($parentContext[self::ATTRIBUTES]);
}
return $parentContext;
}
}

View file

@ -0,0 +1,380 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Base class for a normalizer dealing with objects.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
abstract class AbstractObjectNormalizer extends AbstractNormalizer
{
const ENABLE_MAX_DEPTH = 'enable_max_depth';
const DEPTH_KEY_PATTERN = 'depth_%s::%s';
const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement';
private $propertyTypeExtractor;
private $attributesCache = array();
private $cache = array();
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null)
{
parent::__construct($classMetadataFactory, $nameConverter);
$this->propertyTypeExtractor = $propertyTypeExtractor;
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return \is_object($data) && !$data instanceof \Traversable;
}
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
if (!isset($context['cache_key'])) {
$context['cache_key'] = $this->getCacheKey($format, $context);
}
if ($this->isCircularReference($object, $context)) {
return $this->handleCircularReference($object);
}
$data = array();
$stack = array();
$attributes = $this->getAttributes($object, $format, $context);
$class = \get_class($object);
$attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata() : null;
foreach ($attributes as $attribute) {
if (null !== $attributesMetadata && $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) {
continue;
}
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $context);
if (isset($this->callbacks[$attribute])) {
$attributeValue = \call_user_func($this->callbacks[$attribute], $attributeValue);
}
if (null !== $attributeValue && !is_scalar($attributeValue)) {
$stack[$attribute] = $attributeValue;
}
$data = $this->updateData($data, $attribute, $attributeValue);
}
foreach ($stack as $attribute => $attributeValue) {
if (!$this->serializer instanceof NormalizerInterface) {
throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer', $attribute));
}
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute)));
}
return $data;
}
/**
* Gets and caches attributes for the given object, format and context.
*
* @param object $object
* @param string|null $format
* @param array $context
*
* @return string[]
*/
protected function getAttributes($object, $format = null, array $context)
{
$class = \get_class($object);
$key = $class.'-'.$context['cache_key'];
if (isset($this->attributesCache[$key])) {
return $this->attributesCache[$key];
}
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
if (false !== $allowedAttributes) {
if ($context['cache_key']) {
$this->attributesCache[$key] = $allowedAttributes;
}
return $allowedAttributes;
}
if (isset($context['attributes'])) {
return $this->extractAttributes($object, $format, $context);
}
if (isset($this->attributesCache[$class])) {
return $this->attributesCache[$class];
}
return $this->attributesCache[$class] = $this->extractAttributes($object, $format, $context);
}
/**
* Extracts attributes to normalize from the class of the given object, format and context.
*
* @param object $object
* @param string|null $format
* @param array $context
*
* @return string[]
*/
abstract protected function extractAttributes($object, $format = null, array $context = array());
/**
* Gets the attribute value.
*
* @param object $object
* @param string $attribute
* @param string|null $format
* @param array $context
*
* @return mixed
*/
abstract protected function getAttributeValue($object, $attribute, $format = null, array $context = array());
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return isset($this->cache[$type]) ? $this->cache[$type] : $this->cache[$type] = class_exists($type);
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
if (!isset($context['cache_key'])) {
$context['cache_key'] = $this->getCacheKey($format, $context);
}
$allowedAttributes = $this->getAllowedAttributes($class, $context, true);
$normalizedData = $this->prepareForDenormalization($data);
$extraAttributes = array();
$reflectionClass = new \ReflectionClass($class);
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format);
foreach ($normalizedData as $attribute => $value) {
if ($this->nameConverter) {
$attribute = $this->nameConverter->denormalize($attribute);
}
if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) {
if (isset($context[self::ALLOW_EXTRA_ATTRIBUTES]) && !$context[self::ALLOW_EXTRA_ATTRIBUTES]) {
$extraAttributes[] = $attribute;
}
continue;
}
$value = $this->validateAndDenormalize($class, $attribute, $value, $format, $context);
try {
$this->setAttributeValue($object, $attribute, $value, $format, $context);
} catch (InvalidArgumentException $e) {
throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e);
}
}
if (!empty($extraAttributes)) {
throw new ExtraAttributesException($extraAttributes);
}
return $object;
}
/**
* Sets attribute value.
*
* @param object $object
* @param string $attribute
* @param mixed $value
* @param string|null $format
* @param array $context
*/
abstract protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = array());
/**
* Validates the submitted data and denormalizes it.
*
* @param string $currentClass
* @param string $attribute
* @param mixed $data
* @param string|null $format
* @param array $context
*
* @return mixed
*
* @throws NotNormalizableValueException
* @throws LogicException
*/
private function validateAndDenormalize($currentClass, $attribute, $data, $format, array $context)
{
if (null === $this->propertyTypeExtractor || null === $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)) {
return $data;
}
$expectedTypes = array();
foreach ($types as $type) {
if (null === $data && $type->isNullable()) {
return;
}
if ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueType()) && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) {
$builtinType = Type::BUILTIN_TYPE_OBJECT;
$class = $collectionValueType->getClassName().'[]';
// Fix a collection that contains the only one element
// This is special to xml format only
if ('xml' === $format && !\is_int(key($data))) {
$data = array($data);
}
if (null !== $collectionKeyType = $type->getCollectionKeyType()) {
$context['key_type'] = $collectionKeyType;
}
} else {
$builtinType = $type->getBuiltinType();
$class = $type->getClassName();
}
$expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true;
if (Type::BUILTIN_TYPE_OBJECT === $builtinType) {
if (!$this->serializer instanceof DenormalizerInterface) {
throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer', $attribute, $class));
}
$childContext = $this->createChildContext($context, $attribute);
if ($this->serializer->supportsDenormalization($data, $class, $format, $childContext)) {
return $this->serializer->denormalize($data, $class, $format, $childContext);
}
}
// JSON only has a Number type corresponding to both int and float PHP types.
// PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert
// floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible).
// PHP's json_decode automatically converts Numbers without a decimal part to integers.
// To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when
// a float is expected.
if (Type::BUILTIN_TYPE_FLOAT === $builtinType && \is_int($data) && false !== strpos($format, JsonEncoder::FORMAT)) {
return (float) $data;
}
if (\call_user_func('is_'.$builtinType, $data)) {
return $data;
}
}
if (!empty($context[self::DISABLE_TYPE_ENFORCEMENT])) {
return $data;
}
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), \gettype($data)));
}
/**
* Sets an attribute and apply the name converter if necessary.
*
* @param string $attribute
* @param mixed $attributeValue
*
* @return array
*/
private function updateData(array $data, $attribute, $attributeValue)
{
if ($this->nameConverter) {
$attribute = $this->nameConverter->normalize($attribute);
}
$data[$attribute] = $attributeValue;
return $data;
}
/**
* Is the max depth reached for the given attribute?
*
* @param AttributeMetadataInterface[] $attributesMetadata
* @param string $class
* @param string $attribute
* @param array $context
*
* @return bool
*/
private function isMaxDepthReached(array $attributesMetadata, $class, $attribute, array &$context)
{
if (
!isset($context[static::ENABLE_MAX_DEPTH]) ||
!$context[static::ENABLE_MAX_DEPTH] ||
!isset($attributesMetadata[$attribute]) ||
null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth()
) {
return false;
}
$key = sprintf(static::DEPTH_KEY_PATTERN, $class, $attribute);
if (!isset($context[$key])) {
$context[$key] = 1;
return false;
}
if ($context[$key] === $maxDepth) {
return true;
}
++$context[$key];
return false;
}
/**
* Gets the cache key to use.
*
* @param string|null $format
* @param array $context
*
* @return bool|string
*/
private function getCacheKey($format, array $context)
{
try {
return md5($format.serialize($context));
} catch (\Exception $exception) {
// The context cannot be serialized, skip the cache
return false;
}
}
}

View file

@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\Exception\BadMethodCallException;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Denormalizes arrays of objects.
*
* @author Alexander M. Turek <me@derrabus.de>
*
* @final since version 3.3.
*/
class ArrayDenormalizer implements DenormalizerInterface, SerializerAwareInterface
{
/**
* @var SerializerInterface|DenormalizerInterface
*/
private $serializer;
/**
* {@inheritdoc}
*
* @throws NotNormalizableValueException
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
if (null === $this->serializer) {
throw new BadMethodCallException('Please set a serializer before calling denormalize()!');
}
if (!\is_array($data)) {
throw new InvalidArgumentException('Data expected to be an array, '.\gettype($data).' given.');
}
if ('[]' !== substr($class, -2)) {
throw new InvalidArgumentException('Unsupported class: '.$class);
}
$serializer = $this->serializer;
$class = substr($class, 0, -2);
$builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null;
foreach ($data as $key => $value) {
if (null !== $builtinType && !\call_user_func('is_'.$builtinType, $key)) {
throw new NotNormalizableValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, \gettype($key)));
}
$data[$key] = $serializer->denormalize($value, $class, $format, $context);
}
return $data;
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null/*, array $context = array()*/)
{
$context = \func_num_args() > 3 ? func_get_arg(3) : array();
return '[]' === substr($type, -2)
&& $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format, $context);
}
/**
* {@inheritdoc}
*/
public function setSerializer(SerializerInterface $serializer)
{
if (!$serializer instanceof DenormalizerInterface) {
throw new InvalidArgumentException('Expected a serializer that also implements DenormalizerInterface.');
}
$this->serializer = $serializer;
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Adds the support of an extra $context parameter for the supportsDenormalization method.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface ContextAwareDenormalizerInterface extends DenormalizerInterface
{
/**
* {@inheritdoc}
*
* @param array $context options that denormalizers have access to
*/
public function supportsDenormalization($data, $type, $format = null, array $context = array());
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Adds the support of an extra $context parameter for the supportsNormalization method.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface ContextAwareNormalizerInterface extends NormalizerInterface
{
/**
* {@inheritdoc}
*
* @param array $context options that normalizers have access to
*/
public function supportsNormalization($data, $format = null, array $context = array());
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
{
use ObjectToPopulateTrait;
use SerializerAwareTrait;
private $cache = array();
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
return $object->normalize($this->serializer, $format, $context);
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
$object = $this->extractObjectToPopulate($class, $context) ?: new $class();
$object->denormalize($this->serializer, $data, $format, $context);
return $object;
}
/**
* Checks if the given class implements the NormalizableInterface.
*
* @param mixed $data Data to normalize
* @param string $format The format being (de-)serialized from or into
*
* @return bool
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof NormalizableInterface;
}
/**
* Checks if the given class implements the DenormalizableInterface.
*
* @param mixed $data Data to denormalize from
* @param string $type The class to which the data should be denormalized
* @param string $format The format being deserialized from
*
* @return bool
*/
public function supportsDenormalization($data, $type, $format = null)
{
if (isset($this->cache[$type])) {
return $this->cache[$type];
}
if (!class_exists($type)) {
return $this->cache[$type] = false;
}
return $this->cache[$type] = is_subclass_of($type, 'Symfony\Component\Serializer\Normalizer\DenormalizableInterface');
}
}

View file

@ -0,0 +1,157 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
/**
* Normalizes an {@see \SplFileInfo} object to a data URI.
* Denormalizes a data URI to a {@see \SplFileObject} object.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface
{
private static $supportedTypes = array(
\SplFileInfo::class => true,
\SplFileObject::class => true,
File::class => true,
);
/**
* @var MimeTypeGuesserInterface
*/
private $mimeTypeGuesser;
public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser = null)
{
if (null === $mimeTypeGuesser && class_exists('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser')) {
$mimeTypeGuesser = MimeTypeGuesser::getInstance();
}
$this->mimeTypeGuesser = $mimeTypeGuesser;
}
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
if (!$object instanceof \SplFileInfo) {
throw new InvalidArgumentException('The object must be an instance of "\SplFileInfo".');
}
$mimeType = $this->getMimeType($object);
$splFileObject = $this->extractSplFileObject($object);
$data = '';
$splFileObject->rewind();
while (!$splFileObject->eof()) {
$data .= $splFileObject->fgets();
}
if ('text' === explode('/', $mimeType, 2)[0]) {
return sprintf('data:%s,%s', $mimeType, rawurlencode($data));
}
return sprintf('data:%s;base64,%s', $mimeType, base64_encode($data));
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \SplFileInfo;
}
/**
* {@inheritdoc}
*
* Regex adapted from Brian Grinstead code.
*
* @see https://gist.github.com/bgrins/6194623
*
* @throws InvalidArgumentException
* @throws NotNormalizableValueException
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
if (!preg_match('/^data:([a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}\/[a-z0-9][a-z0-9\!\#\$\&\-\^\_\+\.]{0,126}(;[a-z0-9\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\\\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i', $data)) {
throw new NotNormalizableValueException('The provided "data:" URI is not valid.');
}
try {
switch ($class) {
case 'Symfony\Component\HttpFoundation\File\File':
return new File($data, false);
case 'SplFileObject':
case 'SplFileInfo':
return new \SplFileObject($data);
}
} catch (\RuntimeException $exception) {
throw new NotNormalizableValueException($exception->getMessage(), $exception->getCode(), $exception);
}
throw new InvalidArgumentException(sprintf('The class parameter "%s" is not supported. It must be one of "SplFileInfo", "SplFileObject" or "Symfony\Component\HttpFoundation\File\File".', $class));
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return isset(self::$supportedTypes[$type]);
}
/**
* Gets the mime type of the object. Defaults to application/octet-stream.
*
* @param \SplFileInfo $object
*
* @return string
*/
private function getMimeType(\SplFileInfo $object)
{
if ($object instanceof File) {
return $object->getMimeType();
}
if ($this->mimeTypeGuesser && $mimeType = $this->mimeTypeGuesser->guess($object->getPathname())) {
return $mimeType;
}
return 'application/octet-stream';
}
/**
* Returns the \SplFileObject instance associated with the given \SplFileInfo instance.
*
* @param \SplFileInfo $object
*
* @return \SplFileObject
*/
private function extractSplFileObject(\SplFileInfo $object)
{
if ($object instanceof \SplFileObject) {
return $object;
}
return $object->openFile();
}
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Normalizes an instance of {@see \DateInterval} to an interval string.
* Denormalizes an interval string to an instance of {@see \DateInterval}.
*
* @author Jérôme Parmentier <jerome@prmntr.me>
*/
class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface
{
const FORMAT_KEY = 'dateinterval_format';
private $format;
/**
* @param string $format
*/
public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS')
{
$this->format = $format;
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException
*/
public function normalize($object, $format = null, array $context = array())
{
if (!$object instanceof \DateInterval) {
throw new InvalidArgumentException('The object must be an instance of "\DateInterval".');
}
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
return $object->format($dateIntervalFormat);
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \DateInterval;
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException
* @throws UnexpectedValueException
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
if (!\is_string($data)) {
throw new InvalidArgumentException(sprintf('Data expected to be a string, %s given.', \gettype($data)));
}
if (!$this->isISO8601($data)) {
throw new UnexpectedValueException('Expected a valid ISO 8601 interval string.');
}
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
$valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/';
if (!preg_match($valuePattern, $data)) {
throw new UnexpectedValueException(sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat));
}
try {
return new \DateInterval($data);
} catch (\Exception $e) {
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return \DateInterval::class === $type;
}
private function isISO8601($string)
{
return preg_match('/^P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string);
}
}

View file

@ -0,0 +1,154 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
/**
* Normalizes an object implementing the {@see \DateTimeInterface} to a date string.
* Denormalizes a date string to an instance of {@see \DateTime} or {@see \DateTimeImmutable}.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface
{
const FORMAT_KEY = 'datetime_format';
const TIMEZONE_KEY = 'datetime_timezone';
private $format;
private $timezone;
private static $supportedTypes = array(
\DateTimeInterface::class => true,
\DateTimeImmutable::class => true,
\DateTime::class => true,
);
/**
* @param string $format
* @param \DateTimeZone|null $timezone
*/
public function __construct($format = \DateTime::RFC3339, \DateTimeZone $timezone = null)
{
$this->format = $format;
$this->timezone = $timezone;
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException
*/
public function normalize($object, $format = null, array $context = array())
{
if (!$object instanceof \DateTimeInterface) {
throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".');
}
$format = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
$timezone = $this->getTimezone($context);
if (null !== $timezone) {
$object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone);
}
return $object->format($format);
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \DateTimeInterface;
}
/**
* {@inheritdoc}
*
* @throws NotNormalizableValueException
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
$dateTimeFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : null;
$timezone = $this->getTimezone($context);
if ('' === $data || null === $data) {
throw new NotNormalizableValueException('The data is either an empty string or null, you should pass a string that can be parsed with the passed format or a valid DateTime string.');
}
if (null !== $dateTimeFormat) {
if (null === $timezone && \PHP_VERSION_ID < 70000) {
// https://bugs.php.net/bug.php?id=68669
$object = \DateTime::class === $class ? \DateTime::createFromFormat($dateTimeFormat, $data) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data);
} else {
$object = \DateTime::class === $class ? \DateTime::createFromFormat($dateTimeFormat, $data, $timezone) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data, $timezone);
}
if (false !== $object) {
return $object;
}
$dateTimeErrors = \DateTime::class === $class ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors();
throw new NotNormalizableValueException(sprintf(
'Parsing datetime string "%s" using format "%s" resulted in %d errors:'."\n".'%s',
$data,
$dateTimeFormat,
$dateTimeErrors['error_count'],
implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors']))
));
}
try {
return \DateTime::class === $class ? new \DateTime($data, $timezone) : new \DateTimeImmutable($data, $timezone);
} catch (\Exception $e) {
throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return isset(self::$supportedTypes[$type]);
}
/**
* Formats datetime errors.
*
* @return string[]
*/
private function formatDateTimeErrors(array $errors)
{
$formattedErrors = array();
foreach ($errors as $pos => $message) {
$formattedErrors[] = sprintf('at position %d: %s', $pos, $message);
}
return $formattedErrors;
}
private function getTimezone(array $context)
{
$dateTimeZone = array_key_exists(self::TIMEZONE_KEY, $context) ? $context[self::TIMEZONE_KEY] : $this->timezone;
if (null === $dateTimeZone) {
return null;
}
return $dateTimeZone instanceof \DateTimeZone ? $dateTimeZone : new \DateTimeZone($dateTimeZone);
}
}

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Defines the most basic interface a class must implement to be denormalizable.
*
* If a denormalizer is registered for the class and it doesn't implement
* the Denormalizable interfaces, the normalizer will be used instead
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface DenormalizableInterface
{
/**
* Denormalizes the object back from an array of scalars|arrays.
*
* It is important to understand that the denormalize() call should denormalize
* recursively all child objects of the implementor.
*
* @param DenormalizerInterface $denormalizer The denormalizer is given so that you
* can use it to denormalize objects contained within this object
* @param array|string|int|float|bool $data The data from which to re-create the object
* @param string|null $format The format is optionally given to be able to denormalize
* differently based on different input formats
* @param array $context Options for denormalizing
*
* @return object
*/
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array());
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Class accepting a denormalizer.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
interface DenormalizerAwareInterface
{
/**
* Sets the owning Denormalizer object.
*
* @param DenormalizerInterface $denormalizer
*/
public function setDenormalizer(DenormalizerInterface $denormalizer);
}

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* DenormalizerAware trait.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
trait DenormalizerAwareTrait
{
/**
* @var DenormalizerInterface
*/
protected $denormalizer;
/**
* Sets the Denormalizer.
*
* @param DenormalizerInterface $denormalizer A DenormalizerInterface instance
*/
public function setDenormalizer(DenormalizerInterface $denormalizer)
{
$this->denormalizer = $denormalizer;
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\Exception\BadMethodCallException;
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
/**
* Defines the interface of denormalizers.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface DenormalizerInterface
{
/**
* Denormalizes data back into an object of the given class.
*
* @param mixed $data Data to restore
* @param string $class The expected class to instantiate
* @param string $format Format the given data was extracted from
* @param array $context Options available to the denormalizer
*
* @return object
*
* @throws BadMethodCallException Occurs when the normalizer is not called in an expected context
* @throws InvalidArgumentException Occurs when the arguments are not coherent or not supported
* @throws UnexpectedValueException Occurs when the item cannot be hydrated with the given data
* @throws ExtraAttributesException Occurs when the item doesn't have attribute to receive given data
* @throws LogicException Occurs when the normalizer is not supposed to denormalize
* @throws RuntimeException Occurs if the class cannot be instantiated
*/
public function denormalize($data, $class, $format = null, array $context = array());
/**
* Checks whether the given class is supported for denormalization by this normalizer.
*
* @param mixed $data Data to denormalize from
* @param string $type The class to which the data should be denormalized
* @param string $format The format being deserialized from
*
* @return bool
*/
public function supportsDenormalization($data, $type, $format = null);
}

View file

@ -0,0 +1,159 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Converts between objects with getter and setter methods and arrays.
*
* The normalization process looks at all public methods and calls the ones
* which have a name starting with get and take no parameters. The result is a
* map from property names (method name stripped of the get prefix and converted
* to lower case) to property values. Property values are normalized through the
* serializer.
*
* The denormalization first looks at the constructor of the given class to see
* if any of the parameters have the same name as one of the properties. The
* constructor is then called with all parameters or an exception is thrown if
* any required parameters were not present as properties. Then the denormalizer
* walks through the given map of property names to property values to see if a
* setter method exists for any of the properties. If a setter exists it is
* called with the property value. No automatic denormalization of the value
* takes place.
*
* @author Nils Adermann <naderman@naderman.de>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class GetSetMethodNormalizer extends AbstractObjectNormalizer
{
private static $setterAccessibleCache = array();
private $cache = array();
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return parent::supportsNormalization($data, $format) && (isset($this->cache[$type = \get_class($data)]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type));
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return parent::supportsDenormalization($data, $type, $format) && (isset($this->cache[$type]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type));
}
/**
* Checks if the given class has any get{Property} method.
*
* @param string $class
*
* @return bool
*/
private function supports($class)
{
$class = new \ReflectionClass($class);
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
if ($this->isGetMethod($method)) {
return true;
}
}
return false;
}
/**
* Checks if a method's name is get.* or is.*, and can be called without parameters.
*
* @return bool whether the method is a getter or boolean getter
*/
private function isGetMethod(\ReflectionMethod $method)
{
$methodLength = \strlen($method->name);
return
!$method->isStatic() &&
(
((0 === strpos($method->name, 'get') && 3 < $methodLength) ||
(0 === strpos($method->name, 'is') && 2 < $methodLength) ||
(0 === strpos($method->name, 'has') && 3 < $methodLength)) &&
0 === $method->getNumberOfRequiredParameters()
)
;
}
/**
* {@inheritdoc}
*/
protected function extractAttributes($object, $format = null, array $context = array())
{
$reflectionObject = new \ReflectionObject($object);
$reflectionMethods = $reflectionObject->getMethods(\ReflectionMethod::IS_PUBLIC);
$attributes = array();
foreach ($reflectionMethods as $method) {
if (!$this->isGetMethod($method)) {
continue;
}
$attributeName = lcfirst(substr($method->name, 0 === strpos($method->name, 'is') ? 2 : 3));
if ($this->isAllowedAttribute($object, $attributeName)) {
$attributes[] = $attributeName;
}
}
return $attributes;
}
/**
* {@inheritdoc}
*/
protected function getAttributeValue($object, $attribute, $format = null, array $context = array())
{
$ucfirsted = ucfirst($attribute);
$getter = 'get'.$ucfirsted;
if (\is_callable(array($object, $getter))) {
return $object->$getter();
}
$isser = 'is'.$ucfirsted;
if (\is_callable(array($object, $isser))) {
return $object->$isser();
}
$haser = 'has'.$ucfirsted;
if (\is_callable(array($object, $haser))) {
return $object->$haser();
}
}
/**
* {@inheritdoc}
*/
protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = array())
{
$setter = 'set'.ucfirst($attribute);
$key = \get_class($object).':'.$setter;
if (!isset(self::$setterAccessibleCache[$key])) {
self::$setterAccessibleCache[$key] = \is_callable(array($object, $setter)) && !(new \ReflectionMethod($object, $setter))->isStatic();
}
if (self::$setterAccessibleCache[$key]) {
$object->$setter($value);
}
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\LogicException;
/**
* A normalizer that uses an objects own JsonSerializable implementation.
*
* @author Fred Cox <mcfedr@gmail.com>
*/
class JsonSerializableNormalizer extends AbstractNormalizer
{
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
if ($this->isCircularReference($object, $context)) {
return $this->handleCircularReference($object);
}
if (!$object instanceof \JsonSerializable) {
throw new InvalidArgumentException(sprintf('The object must implement "%s".', \JsonSerializable::class));
}
if (!$this->serializer instanceof NormalizerInterface) {
throw new LogicException('Cannot normalize object because injected serializer is not a normalizer');
}
return $this->serializer->normalize($object->jsonSerialize(), $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof \JsonSerializable;
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return false;
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class));
}
}

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Defines the most basic interface a class must implement to be normalizable.
*
* If a normalizer is registered for the class and it doesn't implement
* the Normalizable interfaces, the normalizer will be used instead.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface NormalizableInterface
{
/**
* Normalizes the object into an array of scalars|arrays.
*
* It is important to understand that the normalize() call should normalize
* recursively all child objects of the implementor.
*
* @param NormalizerInterface $normalizer The normalizer is given so that you
* can use it to normalize objects contained within this object
* @param string|null $format The format is optionally given to be able to normalize differently
* based on different output formats
* @param array $context Options for normalizing this object
*
* @return array|string|int|float|bool
*/
public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array());
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Class accepting a normalizer.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
interface NormalizerAwareInterface
{
/**
* Sets the owning Normalizer object.
*
* @param NormalizerInterface $normalizer
*/
public function setNormalizer(NormalizerInterface $normalizer);
}

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* NormalizerAware trait.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
trait NormalizerAwareTrait
{
/**
* @var NormalizerInterface
*/
protected $normalizer;
/**
* Sets the normalizer.
*
* @param NormalizerInterface $normalizer A NormalizerInterface instance
*/
public function setNormalizer(NormalizerInterface $normalizer)
{
$this->normalizer = $normalizer;
}
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\Exception\CircularReferenceException;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Exception\LogicException;
/**
* Defines the interface of normalizers.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface NormalizerInterface
{
/**
* Normalizes an object into a set of arrays/scalars.
*
* @param mixed $object Object to normalize
* @param string $format Format the normalization result will be encoded as
* @param array $context Context options for the normalizer
*
* @return array|string|int|float|bool
*
* @throws InvalidArgumentException Occurs when the object given is not an attempted type for the normalizer
* @throws CircularReferenceException Occurs when the normalizer detects a circular reference when no circular
* reference handler can fix it
* @throws LogicException Occurs when the normalizer is not called in an expected context
*/
public function normalize($object, $format = null, array $context = array());
/**
* Checks whether the given class is supported for normalization by this normalizer.
*
* @param mixed $data Data to normalize
* @param string $format The format being (de-)serialized from or into
*
* @return bool
*/
public function supportsNormalization($data, $format = null);
}

View file

@ -0,0 +1,117 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Converts between objects and arrays using the PropertyAccess component.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ObjectNormalizer extends AbstractObjectNormalizer
{
protected $propertyAccessor;
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null)
{
if (!\class_exists(PropertyAccess::class)) {
throw new RuntimeException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.');
}
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
/**
* {@inheritdoc}
*/
protected function extractAttributes($object, $format = null, array $context = array())
{
// If not using groups, detect manually
$attributes = array();
// methods
$reflClass = new \ReflectionClass($object);
foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
if (
0 !== $reflMethod->getNumberOfRequiredParameters() ||
$reflMethod->isStatic() ||
$reflMethod->isConstructor() ||
$reflMethod->isDestructor()
) {
continue;
}
$name = $reflMethod->name;
$attributeName = null;
if (0 === strpos($name, 'get') || 0 === strpos($name, 'has')) {
// getters and hassers
$attributeName = substr($name, 3);
if (!$reflClass->hasProperty($attributeName)) {
$attributeName = lcfirst($attributeName);
}
} elseif (0 === strpos($name, 'is')) {
// issers
$attributeName = substr($name, 2);
if (!$reflClass->hasProperty($attributeName)) {
$attributeName = lcfirst($attributeName);
}
}
if (null !== $attributeName && $this->isAllowedAttribute($object, $attributeName, $format, $context)) {
$attributes[$attributeName] = true;
}
}
// properties
foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
if ($reflProperty->isStatic() || !$this->isAllowedAttribute($object, $reflProperty->name, $format, $context)) {
continue;
}
$attributes[$reflProperty->name] = true;
}
return array_keys($attributes);
}
/**
* {@inheritdoc}
*/
protected function getAttributeValue($object, $attribute, $format = null, array $context = array())
{
return $this->propertyAccessor->getValue($object, $attribute);
}
/**
* {@inheritdoc}
*/
protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = array())
{
try {
$this->propertyAccessor->setValue($object, $attribute, $value);
} catch (NoSuchPropertyException $exception) {
// Properties not found are ignored
}
}
}

View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
trait ObjectToPopulateTrait
{
/**
* Extract the `object_to_populate` field from the context if it exists
* and is an instance of the provided $class.
*
* @param string $class The class the object should be
* @param $context The denormalization context
* @param string $key They in which to look for the object to populate.
* Keeps backwards compatibility with `AbstractNormalizer`.
*
* @return object|null an object if things check out, null otherwise
*/
protected function extractObjectToPopulate($class, array $context, $key = null)
{
$key = $key ?: 'object_to_populate';
if (isset($context[$key]) && \is_object($context[$key]) && $context[$key] instanceof $class) {
return $context[$key];
}
return null;
}
}

View file

@ -0,0 +1,179 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
/**
* Converts between objects and arrays by mapping properties.
*
* The normalization process looks for all the object's properties (public and private).
* The result is a map from property names to property values. Property values
* are normalized through the serializer.
*
* The denormalization first looks at the constructor of the given class to see
* if any of the parameters have the same name as one of the properties. The
* constructor is then called with all parameters or an exception is thrown if
* any required parameters were not present as properties. Then the denormalizer
* walks through the given map of property names to property values to see if a
* property with the corresponding name exists. If found, the property gets the value.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyNormalizer extends AbstractObjectNormalizer
{
private $cache = array();
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return parent::supportsNormalization($data, $format) && (isset($this->cache[$type = \get_class($data)]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type));
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return parent::supportsDenormalization($data, $type, $format) && (isset($this->cache[$type]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type));
}
/**
* Checks if the given class has any non-static property.
*
* @param string $class
*
* @return bool
*/
private function supports($class)
{
$class = new \ReflectionClass($class);
// We look for at least one non-static property
do {
foreach ($class->getProperties() as $property) {
if (!$property->isStatic()) {
return true;
}
}
} while ($class = $class->getParentClass());
return false;
}
/**
* {@inheritdoc}
*/
protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array())
{
if (!parent::isAllowedAttribute($classOrObject, $attribute, $format, $context)) {
return false;
}
try {
$reflectionProperty = $this->getReflectionProperty($classOrObject, $attribute);
if ($reflectionProperty->isStatic()) {
return false;
}
} catch (\ReflectionException $reflectionException) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function extractAttributes($object, $format = null, array $context = array())
{
$reflectionObject = new \ReflectionObject($object);
$attributes = array();
do {
foreach ($reflectionObject->getProperties() as $property) {
if (!$this->isAllowedAttribute($reflectionObject->getName(), $property->name)) {
continue;
}
$attributes[] = $property->name;
}
} while ($reflectionObject = $reflectionObject->getParentClass());
return $attributes;
}
/**
* {@inheritdoc}
*/
protected function getAttributeValue($object, $attribute, $format = null, array $context = array())
{
try {
$reflectionProperty = $this->getReflectionProperty($object, $attribute);
} catch (\ReflectionException $reflectionException) {
return;
}
// Override visibility
if (!$reflectionProperty->isPublic()) {
$reflectionProperty->setAccessible(true);
}
return $reflectionProperty->getValue($object);
}
/**
* {@inheritdoc}
*/
protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = array())
{
try {
$reflectionProperty = $this->getReflectionProperty($object, $attribute);
} catch (\ReflectionException $reflectionException) {
return;
}
if ($reflectionProperty->isStatic()) {
return;
}
// Override visibility
if (!$reflectionProperty->isPublic()) {
$reflectionProperty->setAccessible(true);
}
$reflectionProperty->setValue($object, $value);
}
/**
* @param string|object $classOrObject
* @param string $attribute
*
* @return \ReflectionProperty
*
* @throws \ReflectionException
*/
private function getReflectionProperty($classOrObject, $attribute)
{
$reflectionClass = new \ReflectionClass($classOrObject);
while (true) {
try {
return $reflectionClass->getProperty($attribute);
} catch (\ReflectionException $e) {
if (!$reflectionClass = $reflectionClass->getParentClass()) {
throw $e;
}
}
}
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Normalizer;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;
/**
* SerializerAware Normalizer implementation.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @deprecated since version 3.1, to be removed in 4.0. Use the SerializerAwareTrait instead.
*/
abstract class SerializerAwareNormalizer implements SerializerAwareInterface
{
use SerializerAwareTrait;
}

16
vendor/symfony/serializer/README.md vendored Normal file
View file

@ -0,0 +1,16 @@
Serializer Component
====================
With the Serializer component it's possible to handle serializing data
structures, including object graphs, into array structures or other formats like
XML and JSON. It can also handle deserializing XML and JSON back to object
graphs.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/serializer.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

324
vendor/symfony/serializer/Serializer.php vendored Normal file
View file

@ -0,0 +1,324 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer;
use Symfony\Component\Serializer\Encoder\ChainDecoder;
use Symfony\Component\Serializer\Encoder\ChainEncoder;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Serializer serializes and deserializes data.
*
* objects are turned into arrays by normalizers.
* arrays are turned into various output formats by encoders.
*
* $serializer->serialize($obj, 'xml')
* $serializer->decode($data, 'xml')
* $serializer->denormalize($data, 'Class', 'xml')
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class Serializer implements SerializerInterface, NormalizerInterface, DenormalizerInterface, EncoderInterface, DecoderInterface
{
/**
* @var Encoder\ChainEncoder
*/
protected $encoder;
/**
* @var Encoder\ChainDecoder
*/
protected $decoder;
/**
* @var array
*/
protected $normalizers = array();
/**
* @var array
*
* @deprecated since 3.1 will be removed in 4.0
*/
protected $normalizerCache = array();
/**
* @var array
*
* @deprecated since 3.1 will be removed in 4.0
*/
protected $denormalizerCache = array();
public function __construct(array $normalizers = array(), array $encoders = array())
{
foreach ($normalizers as $normalizer) {
if ($normalizer instanceof SerializerAwareInterface) {
$normalizer->setSerializer($this);
}
if ($normalizer instanceof DenormalizerAwareInterface) {
$normalizer->setDenormalizer($this);
}
if ($normalizer instanceof NormalizerAwareInterface) {
$normalizer->setNormalizer($this);
}
}
$this->normalizers = $normalizers;
$decoders = array();
$realEncoders = array();
foreach ($encoders as $encoder) {
if ($encoder instanceof SerializerAwareInterface) {
$encoder->setSerializer($this);
}
if ($encoder instanceof DecoderInterface) {
$decoders[] = $encoder;
}
if ($encoder instanceof EncoderInterface) {
$realEncoders[] = $encoder;
}
}
$this->encoder = new ChainEncoder($realEncoders);
$this->decoder = new ChainDecoder($decoders);
}
/**
* {@inheritdoc}
*/
final public function serialize($data, $format, array $context = array())
{
if (!$this->supportsEncoding($format, $context)) {
throw new NotEncodableValueException(sprintf('Serialization for the format %s is not supported', $format));
}
if ($this->encoder->needsNormalization($format, $context)) {
$data = $this->normalize($data, $format, $context);
}
return $this->encode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
final public function deserialize($data, $type, $format, array $context = array())
{
if (!$this->supportsDecoding($format, $context)) {
throw new NotEncodableValueException(sprintf('Deserialization for the format %s is not supported', $format));
}
$data = $this->decode($data, $format, $context);
return $this->denormalize($data, $type, $format, $context);
}
/**
* {@inheritdoc}
*/
public function normalize($data, $format = null, array $context = array())
{
// If a normalizer supports the given data, use it
if ($normalizer = $this->getNormalizer($data, $format, $context)) {
return $normalizer->normalize($data, $format, $context);
}
if (null === $data || is_scalar($data)) {
return $data;
}
if (\is_array($data) || $data instanceof \Traversable) {
$normalized = array();
foreach ($data as $key => $val) {
$normalized[$key] = $this->normalize($val, $format, $context);
}
return $normalized;
}
if (\is_object($data)) {
if (!$this->normalizers) {
throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
}
throw new NotNormalizableValueException(sprintf('Could not normalize object of type %s, no supporting normalizer found.', \get_class($data)));
}
throw new NotNormalizableValueException(sprintf('An unexpected value could not be normalized: %s', var_export($data, true)));
}
/**
* {@inheritdoc}
*
* @throws NotNormalizableValueException
*/
public function denormalize($data, $type, $format = null, array $context = array())
{
if (!$this->normalizers) {
throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');
}
if ($normalizer = $this->getDenormalizer($data, $type, $format, $context)) {
return $normalizer->denormalize($data, $type, $format, $context);
}
throw new NotNormalizableValueException(sprintf('Could not denormalize object of type %s, no supporting normalizer found.', $type));
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null/*, array $context = array()*/)
{
if (\func_num_args() > 2) {
$context = \func_get_arg(2);
} else {
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, __FUNCTION__);
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s()" method will have a third `$context = array()` argument in version 4.0. Not defining it is deprecated since Symfony 3.3.', __METHOD__), E_USER_DEPRECATED);
}
}
$context = array();
}
return null !== $this->getNormalizer($data, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null/*, array $context = array()*/)
{
if (\func_num_args() > 3) {
$context = \func_get_arg(3);
} else {
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, __FUNCTION__);
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s()" method will have a fourth `$context = array()` argument in version 4.0. Not defining it is deprecated since Symfony 3.3.', __METHOD__), E_USER_DEPRECATED);
}
}
$context = array();
}
return null !== $this->getDenormalizer($data, $type, $format, $context);
}
/**
* Returns a matching normalizer.
*
* @param mixed $data Data to get the serializer for
* @param string $format Format name, present to give the option to normalizers to act differently based on formats
* @param array $context Options available to the normalizer
*
* @return NormalizerInterface|null
*/
private function getNormalizer($data, $format, array $context)
{
foreach ($this->normalizers as $normalizer) {
if ($normalizer instanceof NormalizerInterface && $normalizer->supportsNormalization($data, $format, $context)) {
return $normalizer;
}
}
}
/**
* Returns a matching denormalizer.
*
* @param mixed $data Data to restore
* @param string $class The expected class to instantiate
* @param string $format Format name, present to give the option to normalizers to act differently based on formats
* @param array $context Options available to the denormalizer
*
* @return DenormalizerInterface|null
*/
private function getDenormalizer($data, $class, $format, array $context)
{
foreach ($this->normalizers as $normalizer) {
if ($normalizer instanceof DenormalizerInterface && $normalizer->supportsDenormalization($data, $class, $format, $context)) {
return $normalizer;
}
}
}
/**
* {@inheritdoc}
*/
final public function encode($data, $format, array $context = array())
{
return $this->encoder->encode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
final public function decode($data, $format, array $context = array())
{
return $this->decoder->decode($data, $format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsEncoding($format/*, array $context = array()*/)
{
if (\func_num_args() > 1) {
$context = \func_get_arg(1);
} else {
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, __FUNCTION__);
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s()" method will have a second `$context = array()` argument in version 4.0. Not defining it is deprecated since Symfony 3.3.', __METHOD__), E_USER_DEPRECATED);
}
}
$context = array();
}
return $this->encoder->supportsEncoding($format, $context);
}
/**
* {@inheritdoc}
*/
public function supportsDecoding($format/*, array $context = array()*/)
{
if (\func_num_args() > 1) {
$context = \func_get_arg(1);
} else {
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, __FUNCTION__);
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s()" method will have a second `$context = array()` argument in version 4.0. Not defining it is deprecated since Symfony 3.3.', __METHOD__), E_USER_DEPRECATED);
}
}
$context = array();
}
return $this->decoder->supportsDecoding($format, $context);
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer;
/**
* Defines the interface of encoders.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface SerializerAwareInterface
{
/**
* Sets the owning Serializer object.
*/
public function setSerializer(SerializerInterface $serializer);
}

View file

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer;
/**
* SerializerAware trait.
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
trait SerializerAwareTrait
{
/**
* @var SerializerInterface
*/
protected $serializer;
/**
* Sets the serializer.
*
* @param SerializerInterface $serializer A SerializerInterface instance
*/
public function setSerializer(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer;
/**
* Defines the interface of the Serializer.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface SerializerInterface
{
/**
* Serializes data in the appropriate format.
*
* @param mixed $data Any data
* @param string $format Format name
* @param array $context Options normalizers/encoders have access to
*
* @return string
*/
public function serialize($data, $format, array $context = array());
/**
* Deserializes data into the given type.
*
* @param mixed $data
* @param string $type
* @param string $format
* @param array $context
*
* @return object
*/
public function deserialize($data, $type, $format, array $context = array());
}

View file

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Annotation;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class GroupsTest extends TestCase
{
/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
*/
public function testEmptyGroupsParameter()
{
new Groups(array('value' => array()));
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
*/
public function testNotAnArrayGroupsParameter()
{
new Groups(array('value' => 12));
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
*/
public function testInvalidGroupsParameter()
{
new Groups(array('value' => array('a', 1, new \stdClass())));
}
public function testGroupsParameters()
{
$validData = array('a', 'b');
$groups = new Groups(array('value' => $validData));
$this->assertEquals($validData, $groups->getGroups());
}
public function testSingleGroup()
{
$groups = new Groups(array('value' => 'a'));
$this->assertEquals(array('a'), $groups->getGroups());
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Annotation;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Annotation\MaxDepth;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class MaxDepthTest extends TestCase
{
/**
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
* @expectedExceptionMessage Parameter of annotation "Symfony\Component\Serializer\Annotation\MaxDepth" should be set.
*/
public function testNotSetMaxDepthParameter()
{
new MaxDepth(array());
}
public function provideInvalidValues()
{
return array(
array(''),
array('foo'),
array('1'),
array(0),
);
}
/**
* @dataProvider provideInvalidValues
*
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
* @expectedExceptionMessage Parameter of annotation "Symfony\Component\Serializer\Annotation\MaxDepth" must be a positive integer.
*/
public function testNotAnIntMaxDepthParameter($value)
{
new MaxDepth(array('value' => $value));
}
public function testMaxDepthParameters()
{
$maxDepth = new MaxDepth(array('value' => 3));
$this->assertEquals(3, $maxDepth->getMaxDepth());
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
/**
* Tests for the SerializerPass class.
*
* @author Javier Lopez <f12loalf@gmail.com>
*/
class SerializerPassTest extends TestCase
{
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage You must tag at least one service as "serializer.normalizer" to use the "serializer" service
*/
public function testThrowExceptionWhenNoNormalizers()
{
$container = new ContainerBuilder();
$container->register('serializer');
$serializerPass = new SerializerPass();
$serializerPass->process($container);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage You must tag at least one service as "serializer.encoder" to use the "serializer" service
*/
public function testThrowExceptionWhenNoEncoders()
{
$container = new ContainerBuilder();
$container->register('serializer')
->addArgument(array())
->addArgument(array());
$container->register('normalizer')->addTag('serializer.normalizer');
$serializerPass = new SerializerPass();
$serializerPass->process($container);
}
public function testServicesAreOrderedAccordingToPriority()
{
$container = new ContainerBuilder();
$definition = $container->register('serializer')->setArguments(array(null, null));
$container->register('n2')->addTag('serializer.normalizer', array('priority' => 100))->addTag('serializer.encoder', array('priority' => 100));
$container->register('n1')->addTag('serializer.normalizer', array('priority' => 200))->addTag('serializer.encoder', array('priority' => 200));
$container->register('n3')->addTag('serializer.normalizer')->addTag('serializer.encoder');
$serializerPass = new SerializerPass();
$serializerPass->process($container);
$expected = array(
new Reference('n1'),
new Reference('n2'),
new Reference('n3'),
);
$this->assertEquals($expected, $definition->getArgument(0));
$this->assertEquals($expected, $definition->getArgument(1));
}
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\ChainDecoder;
class ChainDecoderTest extends TestCase
{
const FORMAT_1 = 'format1';
const FORMAT_2 = 'format2';
const FORMAT_3 = 'format3';
private $chainDecoder;
private $decoder1;
private $decoder2;
protected function setUp()
{
$this->decoder1 = $this
->getMockBuilder('Symfony\Component\Serializer\Encoder\DecoderInterface')
->getMock();
$this->decoder1
->method('supportsDecoding')
->will($this->returnValueMap(array(
array(self::FORMAT_1, array(), true),
array(self::FORMAT_2, array(), false),
array(self::FORMAT_3, array(), false),
array(self::FORMAT_3, array('foo' => 'bar'), true),
)));
$this->decoder2 = $this
->getMockBuilder('Symfony\Component\Serializer\Encoder\DecoderInterface')
->getMock();
$this->decoder2
->method('supportsDecoding')
->will($this->returnValueMap(array(
array(self::FORMAT_1, array(), false),
array(self::FORMAT_2, array(), true),
array(self::FORMAT_3, array(), false),
)));
$this->chainDecoder = new ChainDecoder(array($this->decoder1, $this->decoder2));
}
public function testSupportsDecoding()
{
$this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_1));
$this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_2));
$this->assertFalse($this->chainDecoder->supportsDecoding(self::FORMAT_3));
$this->assertTrue($this->chainDecoder->supportsDecoding(self::FORMAT_3, array('foo' => 'bar')));
}
public function testDecode()
{
$this->decoder1->expects($this->never())->method('decode');
$this->decoder2->expects($this->once())->method('decode');
$this->chainDecoder->decode('string_to_decode', self::FORMAT_2);
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
*/
public function testDecodeUnsupportedFormat()
{
$this->chainDecoder->decode('string_to_decode', self::FORMAT_3);
}
}

View file

@ -0,0 +1,137 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\ChainEncoder;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Encoder\NormalizationAwareInterface;
class ChainEncoderTest extends TestCase
{
const FORMAT_1 = 'format1';
const FORMAT_2 = 'format2';
const FORMAT_3 = 'format3';
private $chainEncoder;
private $encoder1;
private $encoder2;
protected function setUp()
{
$this->encoder1 = $this
->getMockBuilder('Symfony\Component\Serializer\Encoder\EncoderInterface')
->getMock();
$this->encoder1
->method('supportsEncoding')
->will($this->returnValueMap(array(
array(self::FORMAT_1, array(), true),
array(self::FORMAT_2, array(), false),
array(self::FORMAT_3, array(), false),
array(self::FORMAT_3, array('foo' => 'bar'), true),
)));
$this->encoder2 = $this
->getMockBuilder('Symfony\Component\Serializer\Encoder\EncoderInterface')
->getMock();
$this->encoder2
->method('supportsEncoding')
->will($this->returnValueMap(array(
array(self::FORMAT_1, array(), false),
array(self::FORMAT_2, array(), true),
array(self::FORMAT_3, array(), false),
)));
$this->chainEncoder = new ChainEncoder(array($this->encoder1, $this->encoder2));
}
public function testSupportsEncoding()
{
$this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_1));
$this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_2));
$this->assertFalse($this->chainEncoder->supportsEncoding(self::FORMAT_3));
$this->assertTrue($this->chainEncoder->supportsEncoding(self::FORMAT_3, array('foo' => 'bar')));
}
public function testEncode()
{
$this->encoder1->expects($this->never())->method('encode');
$this->encoder2->expects($this->once())->method('encode');
$this->chainEncoder->encode(array('foo' => 123), self::FORMAT_2);
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
*/
public function testEncodeUnsupportedFormat()
{
$this->chainEncoder->encode(array('foo' => 123), self::FORMAT_3);
}
public function testNeedsNormalizationBasic()
{
$this->assertTrue($this->chainEncoder->needsNormalization(self::FORMAT_1));
$this->assertTrue($this->chainEncoder->needsNormalization(self::FORMAT_2));
}
/**
* @dataProvider booleanProvider
*/
public function testNeedsNormalizationChainNormalizationAware($bool)
{
$chainEncoder = $this
->getMockBuilder('Symfony\Component\Serializer\Tests\Encoder\ChainNormalizationAwareEncoder')
->getMock();
$chainEncoder->method('supportsEncoding')->willReturn(true);
$chainEncoder->method('needsNormalization')->willReturn($bool);
$sut = new ChainEncoder(array($chainEncoder));
$this->assertEquals($bool, $sut->needsNormalization(self::FORMAT_1));
}
public function testNeedsNormalizationNormalizationAware()
{
$encoder = new NormalizationAwareEncoder();
$sut = new ChainEncoder(array($encoder));
$this->assertFalse($sut->needsNormalization(self::FORMAT_1));
}
public function booleanProvider()
{
return array(
array(true),
array(false),
);
}
}
class ChainNormalizationAwareEncoder extends ChainEncoder implements NormalizationAwareInterface
{
}
class NormalizationAwareEncoder implements EncoderInterface, NormalizationAwareInterface
{
public function supportsEncoding($format)
{
return true;
}
public function encode($data, $format, array $context = array())
{
}
}

View file

@ -0,0 +1,294 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CsvEncoderTest extends TestCase
{
/**
* @var CsvEncoder
*/
private $encoder;
protected function setUp()
{
$this->encoder = new CsvEncoder();
}
public function testSupportEncoding()
{
$this->assertTrue($this->encoder->supportsEncoding('csv'));
$this->assertFalse($this->encoder->supportsEncoding('foo'));
}
public function testEncode()
{
$value = array('foo' => 'hello', 'bar' => 'hey ho');
$this->assertEquals(<<<'CSV'
foo,bar
hello,"hey ho"
CSV
, $this->encoder->encode($value, 'csv'));
}
public function testEncodeCollection()
{
$value = array(
array('foo' => 'hello', 'bar' => 'hey ho'),
array('foo' => 'hi', 'bar' => 'let\'s go'),
);
$this->assertEquals(<<<'CSV'
foo,bar
hello,"hey ho"
hi,"let's go"
CSV
, $this->encoder->encode($value, 'csv'));
}
public function testEncodePlainIndexedArray()
{
$this->assertEquals(<<<'CSV'
0,1,2
a,b,c
CSV
, $this->encoder->encode(array('a', 'b', 'c'), 'csv'));
}
public function testEncodeNonArray()
{
$this->assertEquals(<<<'CSV'
0
foo
CSV
, $this->encoder->encode('foo', 'csv'));
}
public function testEncodeNestedArrays()
{
$value = array('foo' => 'hello', 'bar' => array(
array('id' => 'yo', 1 => 'wesh'),
array('baz' => 'Halo', 'foo' => 'olá'),
));
$this->assertEquals(<<<'CSV'
foo,bar.0.id,bar.0.1,bar.1.baz,bar.1.foo
hello,yo,wesh,Halo,olá
CSV
, $this->encoder->encode($value, 'csv'));
}
public function testEncodeCustomSettings()
{
$this->encoder = new CsvEncoder(';', "'", '|', '-');
$value = array('a' => 'he\'llo', 'c' => array('d' => 'foo'));
$this->assertEquals(<<<'CSV'
a;c-d
'he''llo';foo
CSV
, $this->encoder->encode($value, 'csv'));
}
public function testEncodeCustomSettingsPassedInContext()
{
$value = array('a' => 'he\'llo', 'c' => array('d' => 'foo'));
$this->assertSame(<<<'CSV'
a;c-d
'he''llo';foo
CSV
, $this->encoder->encode($value, 'csv', array(
CsvEncoder::DELIMITER_KEY => ';',
CsvEncoder::ENCLOSURE_KEY => "'",
CsvEncoder::ESCAPE_CHAR_KEY => '|',
CsvEncoder::KEY_SEPARATOR_KEY => '-',
)));
}
public function testEncodeEmptyArray()
{
$this->assertEquals("\n\n", $this->encoder->encode(array(), 'csv'));
$this->assertEquals("\n\n", $this->encoder->encode(array(array()), 'csv'));
}
public function testEncodeVariableStructure()
{
$value = array(
array('a' => array('foo', 'bar')),
array('a' => array(), 'b' => 'baz'),
array('a' => array('bar', 'foo'), 'c' => 'pong'),
);
$csv = <<<CSV
a.0,a.1,c,b
foo,bar,,
,,,baz
bar,foo,pong,
CSV;
$this->assertEquals($csv, $this->encoder->encode($value, 'csv'));
}
public function testEncodeCustomHeaders()
{
$context = array(
CsvEncoder::HEADERS_KEY => array(
'b',
'c',
),
);
$value = array(
array('a' => 'foo', 'b' => 'bar'),
);
$csv = <<<CSV
b,c,a
bar,,foo
CSV;
$this->assertEquals($csv, $this->encoder->encode($value, 'csv', $context));
}
public function testSupportsDecoding()
{
$this->assertTrue($this->encoder->supportsDecoding('csv'));
$this->assertFalse($this->encoder->supportsDecoding('foo'));
}
public function testDecode()
{
$expected = array('foo' => 'a', 'bar' => 'b');
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
foo,bar
a,b
CSV
, 'csv'));
}
public function testDecodeCollection()
{
$expected = array(
array('foo' => 'a', 'bar' => 'b'),
array('foo' => 'c', 'bar' => 'd'),
array('foo' => 'f'),
);
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
foo,bar
a,b
c,d
f
CSV
, 'csv'));
}
public function testDecodeToManyRelation()
{
$expected = array(
array('foo' => 'bar', 'relations' => array(array('a' => 'b'), array('a' => 'b'))),
array('foo' => 'bat', 'relations' => array(array('a' => 'b'), array('a' => ''))),
array('foo' => 'bat', 'relations' => array(array('a' => 'b'))),
array('foo' => 'baz', 'relations' => array(array('a' => 'c'), array('a' => 'c'))),
);
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
foo,relations.0.a,relations.1.a
bar,b,b
bat,b,
bat,b
baz,c,c
CSV
, 'csv'));
}
public function testDecodeNestedArrays()
{
$expected = array(
array('foo' => 'a', 'bar' => array('baz' => array('bat' => 'b'))),
array('foo' => 'c', 'bar' => array('baz' => array('bat' => 'd'))),
);
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
foo,bar.baz.bat
a,b
c,d
CSV
, 'csv'));
}
public function testDecodeCustomSettings()
{
$this->encoder = new CsvEncoder(';', "'", '|', '-');
$expected = array('a' => 'hell\'o', 'bar' => array('baz' => 'b'));
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
a;bar-baz
'hell''o';b;c
CSV
, 'csv'));
}
public function testDecodeCustomSettingsPassedInContext()
{
$expected = array('a' => 'hell\'o', 'bar' => array('baz' => 'b'));
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
a;bar-baz
'hell''o';b;c
CSV
, 'csv', array(
CsvEncoder::DELIMITER_KEY => ';',
CsvEncoder::ENCLOSURE_KEY => "'",
CsvEncoder::ESCAPE_CHAR_KEY => '|',
CsvEncoder::KEY_SEPARATOR_KEY => '-',
)));
}
public function testDecodeMalformedCollection()
{
$expected = array(
array('foo' => 'a', 'bar' => 'b'),
array('foo' => 'c', 'bar' => 'd'),
array('foo' => 'f'),
);
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
foo,bar
a,b,e
c,d,g,h
f
CSV
, 'csv'));
}
public function testDecodeEmptyArray()
{
$this->assertEquals(array(), $this->encoder->decode('', 'csv'));
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\JsonDecode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
class JsonDecodeTest extends TestCase
{
/** @var \Symfony\Component\Serializer\Encoder\JsonDecode */
private $decode;
protected function setUp()
{
$this->decode = new JsonDecode();
}
public function testSupportsDecoding()
{
$this->assertTrue($this->decode->supportsDecoding(JsonEncoder::FORMAT));
$this->assertFalse($this->decode->supportsDecoding('foobar'));
}
/**
* @dataProvider decodeProvider
*/
public function testDecode($toDecode, $expected, $context)
{
$this->assertEquals(
$expected,
$this->decode->decode($toDecode, JsonEncoder::FORMAT, $context)
);
}
public function decodeProvider()
{
$stdClass = new \stdClass();
$stdClass->foo = 'bar';
$assoc = array('foo' => 'bar');
return array(
array('{"foo": "bar"}', $stdClass, array()),
array('{"foo": "bar"}', $assoc, array('json_decode_associative' => true)),
);
}
/**
* @requires function json_last_error_msg
* @dataProvider decodeProviderException
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
public function testDecodeWithException($value)
{
$this->decode->decode($value, JsonEncoder::FORMAT);
}
public function decodeProviderException()
{
return array(
array("{'foo': 'bar'}"),
array('kaboom!'),
);
}
}

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
class JsonEncodeTest extends TestCase
{
private $encoder;
protected function setUp()
{
$this->encode = new JsonEncode();
}
public function testSupportsEncoding()
{
$this->assertTrue($this->encode->supportsEncoding(JsonEncoder::FORMAT));
$this->assertFalse($this->encode->supportsEncoding('foobar'));
}
/**
* @dataProvider encodeProvider
*/
public function testEncode($toEncode, $expected, $context)
{
$this->assertEquals(
$expected,
$this->encode->encode($toEncode, JsonEncoder::FORMAT, $context)
);
}
public function encodeProvider()
{
return array(
array(array(), '[]', array()),
array(array(), '{}', array('json_encode_options' => JSON_FORCE_OBJECT)),
);
}
/**
* @requires function json_last_error_msg
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
public function testEncodeWithError()
{
$this->encode->encode("\xB1\x31", JsonEncoder::FORMAT);
}
}

View file

@ -0,0 +1,121 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
use Symfony\Component\Serializer\Serializer;
class JsonEncoderTest extends TestCase
{
private $encoder;
private $serializer;
protected function setUp()
{
$this->encoder = new JsonEncoder();
$this->serializer = new Serializer(array(new CustomNormalizer()), array('json' => new JsonEncoder()));
}
public function testEncodeScalar()
{
$obj = new \stdClass();
$obj->foo = 'foo';
$expected = '{"foo":"foo"}';
$this->assertEquals($expected, $this->encoder->encode($obj, 'json'));
}
public function testComplexObject()
{
$obj = $this->getObject();
$expected = $this->getJsonSource();
$this->assertEquals($expected, $this->encoder->encode($obj, 'json'));
}
public function testOptions()
{
$context = array('json_encode_options' => JSON_NUMERIC_CHECK);
$arr = array();
$arr['foo'] = '3';
$expected = '{"foo":3}';
$this->assertEquals($expected, $this->serializer->serialize($arr, 'json', $context));
$arr = array();
$arr['foo'] = '3';
$expected = '{"foo":"3"}';
$this->assertEquals($expected, $this->serializer->serialize($arr, 'json'), 'Context should not be persistent');
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
public function testEncodeNotUtf8WithoutPartialOnError()
{
$arr = array(
'utf8' => 'Hello World!',
'notUtf8' => "\xb0\xd0\xb5\xd0",
);
$this->encoder->encode($arr, 'json');
}
public function testEncodeNotUtf8WithPartialOnError()
{
$context = array('json_encode_options' => JSON_PARTIAL_OUTPUT_ON_ERROR);
$arr = array(
'utf8' => 'Hello World!',
'notUtf8' => "\xb0\xd0\xb5\xd0",
);
$result = $this->encoder->encode($arr, 'json', $context);
$jsonLastError = json_last_error();
$this->assertSame(JSON_ERROR_UTF8, $jsonLastError);
$this->assertEquals('{"utf8":"Hello World!","notUtf8":null}', $result);
$this->assertEquals('0', $this->serializer->serialize(NAN, 'json', $context));
}
public function testDecodeFalseString()
{
$result = $this->encoder->decode('false', 'json');
$this->assertSame(JSON_ERROR_NONE, json_last_error());
$this->assertFalse($result);
}
protected function getJsonSource()
{
return '{"foo":"foo","bar":["a","b"],"baz":{"key":"val","key2":"val","A B":"bar","item":[{"title":"title1"},{"title":"title2"}],"Barry":{"FooBar":{"Baz":"Ed","@id":1}}},"qux":"1"}';
}
protected function getObject()
{
$obj = new \stdClass();
$obj->foo = 'foo';
$obj->bar = array('a', 'b');
$obj->baz = array('key' => 'val', 'key2' => 'val', 'A B' => 'bar', 'item' => array(array('title' => 'title1'), array('title' => 'title2')), 'Barry' => array('FooBar' => array('Baz' => 'Ed', '@id' => 1)));
$obj->qux = '1';
return $obj;
}
}

View file

@ -0,0 +1,733 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Tests\Fixtures\Dummy;
use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy;
use Symfony\Component\Serializer\Tests\Fixtures\ScalarDummy;
class XmlEncoderTest extends TestCase
{
/**
* @var XmlEncoder
*/
private $encoder;
private $exampleDateTimeString = '2017-02-19T15:16:08+0300';
protected function setUp()
{
$this->encoder = new XmlEncoder();
$serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
$this->encoder->setSerializer($serializer);
}
public function testEncodeScalar()
{
$obj = new ScalarDummy();
$obj->xmlFoo = 'foo';
$expected = '<?xml version="1.0"?>'."\n".
'<response>foo</response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
}
public function testSetRootNodeName()
{
$obj = new ScalarDummy();
$obj->xmlFoo = 'foo';
$this->encoder->setRootNodeName('test');
$expected = '<?xml version="1.0"?>'."\n".
'<test>foo</test>'."\n";
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
* @expectedExceptionMessage Document types are not allowed.
*/
public function testDocTypeIsNotAllowed()
{
$this->encoder->decode('<?xml version="1.0"?><!DOCTYPE foo><foo></foo>', 'foo');
}
public function testAttributes()
{
$obj = new ScalarDummy();
$obj->xmlFoo = array(
'foo-bar' => array(
'@id' => 1,
'@name' => 'Bar',
),
'Foo' => array(
'Bar' => 'Test',
'@Type' => 'test',
),
'föo_bär' => 'a',
'Bar' => array(1, 2, 3),
'a' => 'b',
);
$expected = '<?xml version="1.0"?>'."\n".
'<response>'.
'<foo-bar id="1" name="Bar"/>'.
'<Foo Type="test"><Bar>Test</Bar></Foo>'.
'<föo_bär>a</föo_bär>'.
'<Bar>1</Bar>'.
'<Bar>2</Bar>'.
'<Bar>3</Bar>'.
'<a>b</a>'.
'</response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
}
public function testElementNameValid()
{
$obj = new ScalarDummy();
$obj->xmlFoo = array(
'foo-bar' => 'a',
'foo_bar' => 'a',
'föo_bär' => 'a',
);
$expected = '<?xml version="1.0"?>'."\n".
'<response>'.
'<foo-bar>a</foo-bar>'.
'<foo_bar>a</foo_bar>'.
'<föo_bär>a</föo_bär>'.
'</response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
}
public function testEncodeSimpleXML()
{
$xml = simplexml_load_string('<firstname>Peter</firstname>');
$array = array('person' => $xml);
$expected = '<?xml version="1.0"?>'."\n".
'<response><person><firstname>Peter</firstname></person></response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
public function testEncodeXmlAttributes()
{
$xml = simplexml_load_string('<firstname>Peter</firstname>');
$array = array('person' => $xml);
$expected = '<?xml version="1.1" encoding="utf-8" standalone="yes"?>'."\n".
'<response><person><firstname>Peter</firstname></person></response>'."\n";
$context = array(
'xml_version' => '1.1',
'xml_encoding' => 'utf-8',
'xml_standalone' => true,
);
$this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
}
public function testEncodeRemovingEmptyTags()
{
$array = array('person' => array('firstname' => 'Peter', 'lastname' => null));
$expected = '<?xml version="1.0"?>'."\n".
'<response><person><firstname>Peter</firstname></person></response>'."\n";
$context = array('remove_empty_tags' => true);
$this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
}
public function testEncodeNotRemovingEmptyTags()
{
$array = array('person' => array('firstname' => 'Peter', 'lastname' => null));
$expected = '<?xml version="1.0"?>'."\n".
'<response><person><firstname>Peter</firstname><lastname/></person></response>'."\n";
$this->assertSame($expected, $this->encoder->encode($array, 'xml'));
}
public function testContext()
{
$array = array('person' => array('name' => 'George Abitbol'));
$expected = <<<'XML'
<?xml version="1.0"?>
<response>
<person>
<name>George Abitbol</name>
</person>
</response>
XML;
$context = array(
'xml_format_output' => true,
);
$this->assertSame($expected, $this->encoder->encode($array, 'xml', $context));
}
public function testEncodeScalarRootAttributes()
{
$array = array(
'#' => 'Paul',
'@gender' => 'm',
);
$expected = '<?xml version="1.0"?>'."\n".
'<response gender="m">Paul</response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
public function testEncodeRootAttributes()
{
$array = array(
'firstname' => 'Paul',
'@gender' => 'm',
);
$expected = '<?xml version="1.0"?>'."\n".
'<response gender="m"><firstname>Paul</firstname></response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
public function testEncodeCdataWrapping()
{
$array = array(
'firstname' => 'Paul <or Me>',
);
$expected = '<?xml version="1.0"?>'."\n".
'<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
public function testEncodeScalarWithAttribute()
{
$array = array(
'person' => array('@gender' => 'M', '#' => 'Peter'),
);
$expected = '<?xml version="1.0"?>'."\n".
'<response><person gender="M">Peter</person></response>'."\n";
$this->assertEquals($expected, $this->encoder->encode($array, 'xml'));
}
public function testDecodeScalar()
{
$source = '<?xml version="1.0"?>'."\n".
'<response>foo</response>'."\n";
$this->assertEquals('foo', $this->encoder->decode($source, 'xml'));
}
public function testDecodeBigDigitAttributes()
{
$source = <<<XML
<?xml version="1.0"?>
<document index="182077241760011681341821060401202210011000045913000000017100">Name</document>
XML;
$this->assertSame(array('@index' => 182077241760011681341821060401202210011000045913000000017100, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
}
public function testDecodeNegativeIntAttribute()
{
$source = <<<XML
<?xml version="1.0"?>
<document index="-1234">Name</document>
XML;
$this->assertSame(array('@index' => -1234, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
}
public function testDecodeFloatAttribute()
{
$source = <<<XML
<?xml version="1.0"?>
<document index="-12.11">Name</document>
XML;
$this->assertSame(array('@index' => -12.11, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
}
public function testDecodeNegativeFloatAttribute()
{
$source = <<<XML
<?xml version="1.0"?>
<document index="-12.11">Name</document>
XML;
$this->assertSame(array('@index' => -12.11, '#' => 'Name'), $this->encoder->decode($source, 'xml'));
}
public function testNoTypeCastAttribute()
{
$source = <<<XML
<?xml version="1.0"?>
<document a="018" b="-12.11">
<node a="018" b="-12.11"/>
</document>
XML;
$data = $this->encoder->decode($source, 'xml', array('xml_type_cast_attributes' => false));
$expected = array(
'@a' => '018',
'@b' => '-12.11',
'node' => array(
'@a' => '018',
'@b' => '-12.11',
'#' => '',
),
);
$this->assertSame($expected, $data);
}
public function testEncode()
{
$source = $this->getXmlSource();
$obj = $this->getObject();
$this->assertEquals($source, $this->encoder->encode($obj, 'xml'));
}
public function testEncodeWithNamespace()
{
$source = $this->getNamespacedXmlSource();
$array = $this->getNamespacedArray();
$this->assertEquals($source, $this->encoder->encode($array, 'xml'));
}
public function testEncodeSerializerXmlRootNodeNameOption()
{
$options = array('xml_root_node_name' => 'test');
$this->encoder = new XmlEncoder();
$serializer = new Serializer(array(), array('xml' => new XmlEncoder()));
$this->encoder->setSerializer($serializer);
$array = array(
'person' => array('@gender' => 'M', '#' => 'Peter'),
);
$expected = '<?xml version="1.0"?>'."\n".
'<test><person gender="M">Peter</person></test>'."\n";
$this->assertEquals($expected, $serializer->serialize($array, 'xml', $options));
}
public function testEncodeTraversableWhenNormalizable()
{
$this->encoder = new XmlEncoder();
$serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
$this->encoder->setSerializer($serializer);
$expected = <<<'XML'
<?xml version="1.0"?>
<response><foo>normalizedFoo</foo><bar>normalizedBar</bar></response>
XML;
$this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml'));
}
public function testDecode()
{
$source = $this->getXmlSource();
$obj = $this->getObject();
$this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
}
public function testDecodeCdataWrapping()
{
$expected = array(
'firstname' => 'Paul <or Me>',
);
$xml = '<?xml version="1.0"?>'."\n".
'<response><firstname><![CDATA[Paul <or Me>]]></firstname></response>'."\n";
$this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
}
public function testDecodeCdataWrappingAndWhitespace()
{
$expected = array(
'firstname' => 'Paul <or Me>',
);
$xml = '<?xml version="1.0"?>'."\n".
'<response><firstname>'."\n".
'<![CDATA[Paul <or Me>]]></firstname></response>'."\n";
$this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
}
public function testDecodeWithNamespace()
{
$source = $this->getNamespacedXmlSource();
$array = $this->getNamespacedArray();
$this->assertEquals($array, $this->encoder->decode($source, 'xml'));
}
public function testDecodeScalarWithAttribute()
{
$source = '<?xml version="1.0"?>'."\n".
'<response><person gender="M">Peter</person></response>'."\n";
$expected = array(
'person' => array('@gender' => 'M', '#' => 'Peter'),
);
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
}
public function testDecodeScalarRootAttributes()
{
$source = '<?xml version="1.0"?>'."\n".
'<person gender="M">Peter</person>'."\n";
$expected = array(
'#' => 'Peter',
'@gender' => 'M',
);
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
}
public function testDecodeRootAttributes()
{
$source = '<?xml version="1.0"?>'."\n".
'<person gender="M"><firstname>Peter</firstname><lastname>Mac Calloway</lastname></person>'."\n";
$expected = array(
'firstname' => 'Peter',
'lastname' => 'Mac Calloway',
'@gender' => 'M',
);
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
}
public function testDecodeArray()
{
$source = '<?xml version="1.0"?>'."\n".
'<response>'.
'<people>'.
'<person><firstname>Benjamin</firstname><lastname>Alexandre</lastname></person>'.
'<person><firstname>Damien</firstname><lastname>Clay</lastname></person>'.
'</people>'.
'</response>'."\n";
$expected = array(
'people' => array('person' => array(
array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
array('firstname' => 'Damien', 'lastname' => 'Clay'),
)),
);
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
}
public function testDecodeXMLWithProcessInstruction()
{
$source = <<<'XML'
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="/xsl/xmlverbatimwrapper.xsl"?>
<?display table-view?>
<?sort alpha-ascending?>
<response>
<foo>foo</foo>
<?textinfo whitespace is allowed ?>
<bar>a</bar>
<bar>b</bar>
<baz>
<key>val</key>
<key2>val</key2>
<item key="A B">bar</item>
<item>
<title>title1</title>
</item>
<?item ignore-title ?>
<item>
<title>title2</title>
</item>
<Barry>
<FooBar id="1">
<Baz>Ed</Baz>
</FooBar>
</Barry>
</baz>
<qux>1</qux>
</response>
<?instruction <value> ?>
XML;
$obj = $this->getObject();
$this->assertEquals(get_object_vars($obj), $this->encoder->decode($source, 'xml'));
}
public function testDecodeIgnoreWhiteSpace()
{
$source = <<<'XML'
<?xml version="1.0"?>
<people>
<person>
<firstname>Benjamin</firstname>
<lastname>Alexandre</lastname>
</person>
<person>
<firstname>Damien</firstname>
<lastname>Clay</lastname>
</person>
</people>
XML;
$expected = array('person' => array(
array('firstname' => 'Benjamin', 'lastname' => 'Alexandre'),
array('firstname' => 'Damien', 'lastname' => 'Clay'),
));
$this->assertEquals($expected, $this->encoder->decode($source, 'xml'));
}
public function testDecodeWithoutItemHash()
{
$obj = new ScalarDummy();
$obj->xmlFoo = array(
'foo-bar' => array(
'@key' => 'value',
'item' => array('@key' => 'key', 'key-val' => 'val'),
),
'Foo' => array(
'Bar' => 'Test',
'@Type' => 'test',
),
'föo_bär' => 'a',
'Bar' => array(1, 2, 3),
'a' => 'b',
);
$expected = array(
'foo-bar' => array(
'@key' => 'value',
'key' => array('@key' => 'key', 'key-val' => 'val'),
),
'Foo' => array(
'Bar' => 'Test',
'@Type' => 'test',
),
'föo_bär' => 'a',
'Bar' => array(1, 2, 3),
'a' => 'b',
);
$xml = $this->encoder->encode($obj, 'xml');
$this->assertEquals($expected, $this->encoder->decode($xml, 'xml'));
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
public function testDecodeInvalidXml()
{
$this->encoder->decode('<?xml version="1.0"?><invalid><xml>', 'xml');
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
*/
public function testPreventsComplexExternalEntities()
{
$this->encoder->decode('<?xml version="1.0"?><!DOCTYPE scan[<!ENTITY test SYSTEM "php://filter/read=convert.base64-encode/resource=XmlEncoderTest.php">]><scan>&test;</scan>', 'xml');
}
public function testDecodeEmptyXml()
{
if (method_exists($this, 'expectException')) {
$this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException');
$this->expectExceptionMessage('Invalid XML data, it can not be empty.');
} else {
$this->setExpectedException('Symfony\Component\Serializer\Exception\UnexpectedValueException', 'Invalid XML data, it can not be empty.');
}
$this->encoder->decode(' ', 'xml');
}
protected function getXmlSource()
{
return '<?xml version="1.0"?>'."\n".
'<response>'.
'<foo>foo</foo>'.
'<bar>a</bar><bar>b</bar>'.
'<baz><key>val</key><key2>val</key2><item key="A B">bar</item>'.
'<item><title>title1</title></item><item><title>title2</title></item>'.
'<Barry><FooBar id="1"><Baz>Ed</Baz></FooBar></Barry></baz>'.
'<qux>1</qux>'.
'</response>'."\n";
}
protected function getNamespacedXmlSource()
{
return '<?xml version="1.0"?>'."\n".
'<response xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gd="http://schemas.google.com/g/2005" xmlns:yt="http://gdata.youtube.com/schemas/2007">'.
'<qux>1</qux>'.
'<app:foo>foo</app:foo>'.
'<yt:bar>a</yt:bar><yt:bar>b</yt:bar>'.
'<media:baz><media:key>val</media:key><media:key2>val</media:key2><item key="A B">bar</item>'.
'<item><title>title1</title></item><item><title>title2</title></item>'.
'<Barry size="large"><FooBar gd:id="1"><Baz>Ed</Baz></FooBar></Barry></media:baz>'.
'</response>'."\n";
}
protected function getNamespacedArray()
{
return array(
'@xmlns' => 'http://www.w3.org/2005/Atom',
'@xmlns:app' => 'http://www.w3.org/2007/app',
'@xmlns:media' => 'http://search.yahoo.com/mrss/',
'@xmlns:gd' => 'http://schemas.google.com/g/2005',
'@xmlns:yt' => 'http://gdata.youtube.com/schemas/2007',
'qux' => '1',
'app:foo' => 'foo',
'yt:bar' => array('a', 'b'),
'media:baz' => array(
'media:key' => 'val',
'media:key2' => 'val',
'A B' => 'bar',
'item' => array(
array(
'title' => 'title1',
),
array(
'title' => 'title2',
),
),
'Barry' => array(
'@size' => 'large',
'FooBar' => array(
'Baz' => 'Ed',
'@gd:id' => 1,
),
),
),
);
}
protected function getObject()
{
$obj = new Dummy();
$obj->foo = 'foo';
$obj->bar = array('a', 'b');
$obj->baz = array('key' => 'val', 'key2' => 'val', 'A B' => 'bar', 'item' => array(array('title' => 'title1'), array('title' => 'title2')), 'Barry' => array('FooBar' => array('Baz' => 'Ed', '@id' => 1)));
$obj->qux = '1';
return $obj;
}
public function testEncodeXmlWithBoolValue()
{
$expectedXml = <<<'XML'
<?xml version="1.0"?>
<response><foo>1</foo><bar>0</bar></response>
XML;
$actualXml = $this->encoder->encode(array('foo' => true, 'bar' => false), 'xml');
$this->assertEquals($expectedXml, $actualXml);
}
public function testEncodeXmlWithDateTimeObjectValue()
{
$xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
$actualXml = $xmlEncoder->encode(array('dateTime' => new \DateTime($this->exampleDateTimeString)), 'xml');
$this->assertEquals($this->createXmlWithDateTime(), $actualXml);
}
public function testEncodeXmlWithDateTimeObjectField()
{
$xmlEncoder = $this->createXmlEncoderWithDateTimeNormalizer();
$actualXml = $xmlEncoder->encode(array('foo' => array('@dateTime' => new \DateTime($this->exampleDateTimeString))), 'xml');
$this->assertEquals($this->createXmlWithDateTimeField(), $actualXml);
}
/**
* @return XmlEncoder
*/
private function createXmlEncoderWithDateTimeNormalizer()
{
$encoder = new XmlEncoder();
$serializer = new Serializer(array($this->createMockDateTimeNormalizer()), array('xml' => new XmlEncoder()));
$encoder->setSerializer($serializer);
return $encoder;
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject|NormalizerInterface
*/
private function createMockDateTimeNormalizer()
{
$mock = $this->getMockBuilder('\Symfony\Component\Serializer\Normalizer\CustomNormalizer')->getMock();
$mock
->expects($this->once())
->method('normalize')
->with(new \DateTime($this->exampleDateTimeString), 'xml', array())
->willReturn($this->exampleDateTimeString);
$mock
->expects($this->once())
->method('supportsNormalization')
->with(new \DateTime($this->exampleDateTimeString), 'xml')
->willReturn(true);
return $mock;
}
/**
* @return string
*/
private function createXmlWithDateTime()
{
return sprintf('<?xml version="1.0"?>
<response><dateTime>%s</dateTime></response>
', $this->exampleDateTimeString);
}
/**
* @return string
*/
private function createXmlWithDateTimeField()
{
return sprintf('<?xml version="1.0"?>
<response><foo dateTime="%s"/></response>
', $this->exampleDateTimeString);
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Encoder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Encoder\YamlEncoder;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class YamlEncoderTest extends TestCase
{
public function testEncode()
{
$encoder = new YamlEncoder();
$this->assertEquals('foo', $encoder->encode('foo', 'yaml'));
$this->assertEquals('{ foo: 1 }', $encoder->encode(array('foo' => 1), 'yaml'));
}
public function testSupportsEncoding()
{
$encoder = new YamlEncoder();
$this->assertTrue($encoder->supportsEncoding('yaml'));
$this->assertFalse($encoder->supportsEncoding('json'));
}
public function testDecode()
{
$encoder = new YamlEncoder();
$this->assertEquals('foo', $encoder->decode('foo', 'yaml'));
$this->assertEquals(array('foo' => 1), $encoder->decode('{ foo: 1 }', 'yaml'));
}
public function testSupportsDecoding()
{
$encoder = new YamlEncoder();
$this->assertTrue($encoder->supportsDecoding('yaml'));
$this->assertFalse($encoder->supportsDecoding('json'));
}
public function testContext()
{
$encoder = new YamlEncoder(new Dumper(), new Parser(), array('yaml_inline' => 1, 'yaml_indent' => 4, 'yaml_flags' => Yaml::DUMP_OBJECT | Yaml::PARSE_OBJECT));
$obj = new \stdClass();
$obj->bar = 2;
$legacyTag = " foo: !php/object:O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}\n";
$spacedTag = " foo: !php/object 'O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}'\n";
$this->assertThat($encoder->encode(array('foo' => $obj), 'yaml'), $this->logicalOr($this->equalTo($legacyTag), $this->equalTo($spacedTag)));
$this->assertEquals(' { foo: null }', $encoder->encode(array('foo' => $obj), 'yaml', array('yaml_inline' => 0, 'yaml_indent' => 2, 'yaml_flags' => 0)));
$this->assertEquals(array('foo' => $obj), $encoder->decode("foo: !php/object 'O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}'", 'yaml'));
$this->assertEquals(array('foo' => null), $encoder->decode("foo: !php/object 'O:8:\"stdClass\":1:{s:3:\"bar\";i:2;}'", 'yaml', array('yaml_flags' => 0)));
}
}

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
/**
* Provides a dummy Normalizer which extends the AbstractNormalizer.
*
* @author Konstantin S. M. Möllers <ksm.moellers@gmail.com>
*/
class AbstractNormalizerDummy extends AbstractNormalizer
{
/**
* {@inheritdoc}
*/
public function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false)
{
return parent::getAllowedAttributes($classOrObject, $context, $attributesAsString);
}
/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return true;
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return true;
}
}

View file

@ -0,0 +1,23 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CircularReferenceDummy
{
public function getMe()
{
return $this;
}
}

View file

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Normalizer\DenormalizableInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class DenormalizableDummy implements DenormalizableInterface
{
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Normalizer\DenormalizableInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizableInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class Dummy implements NormalizableInterface, DenormalizableInterface
{
public $foo;
public $bar;
public $baz;
public $qux;
public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array())
{
return array(
'foo' => $this->foo,
'bar' => $this->bar,
'baz' => $this->baz,
'qux' => $this->qux,
);
}
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
$this->foo = $data['foo'];
$this->bar = $data['bar'];
$this->baz = $data['baz'];
$this->qux = $data['qux'];
}
}

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class GroupDummy extends GroupDummyParent implements GroupDummyInterface
{
/**
* @Groups({"a"})
*/
private $foo;
/**
* @Groups({"b", "c", "name_converter"})
*/
protected $bar;
private $fooBar;
private $symfony;
/**
* @Groups({"b"})
*/
public function setBar($bar)
{
$this->bar = $bar;
}
/**
* @Groups({"c"})
*/
public function getBar()
{
return $this->bar;
}
public function setFoo($foo)
{
$this->foo = $foo;
}
public function getFoo()
{
return $this->foo;
}
public function setFooBar($fooBar)
{
$this->fooBar = $fooBar;
}
/**
* @Groups({"a", "b", "name_converter"})
*/
public function isFooBar()
{
return $this->fooBar;
}
public function setSymfony($symfony)
{
$this->symfony = $symfony;
}
public function getSymfony()
{
return $this->symfony;
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
class GroupDummyChild extends GroupDummy
{
private $baz;
/**
* @return mixed
*/
public function getBaz()
{
return $this->baz;
}
/**
* @param mixed $baz
*/
public function setBaz($baz)
{
$this->baz = $baz;
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface GroupDummyInterface
{
/**
* @Groups({"a", "name_converter"})
*/
public function getSymfony();
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class GroupDummyParent
{
/**
* @Groups({"a"})
*/
private $kevin;
private $coopTilleuls;
public function setKevin($kevin)
{
$this->kevin = $kevin;
}
public function getKevin()
{
return $this->kevin;
}
public function setCoopTilleuls($coopTilleuls)
{
$this->coopTilleuls = $coopTilleuls;
}
/**
* @Groups({"a", "b"})
*/
public function getCoopTilleuls()
{
return $this->coopTilleuls;
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
class JsonSerializableDummy implements \JsonSerializable
{
public function jsonSerialize()
{
return array(
'foo' => 'a',
'bar' => 'b',
'baz' => 'c',
'qux' => $this,
);
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Annotation\MaxDepth;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class MaxDepthDummy
{
/**
* @MaxDepth(2)
*/
public $foo;
public $bar;
/**
* @var self
*/
public $child;
/**
* @MaxDepth(3)
*/
public function getBar()
{
return $this->bar;
}
public function getChild()
{
return $this->child;
}
}

View file

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Normalizer\DenormalizableInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizableInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class NormalizableTraversableDummy extends TraversableDummy implements NormalizableInterface, DenormalizableInterface
{
public function normalize(NormalizerInterface $normalizer, $format = null, array $context = array())
{
return array(
'foo' => 'normalizedFoo',
'bar' => 'normalizedBar',
);
}
public function denormalize(DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
return array(
'foo' => 'denormalizedFoo',
'bar' => 'denormalizedBar',
);
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Tests\Fixtures;
class NullableConstructorArgumentDummy
{
private $foo;
public function __construct(?\stdClass $foo)
{
$this->foo = $foo;
}
public function setFoo($foo)
{
$this->foo = 'this setter should not be called when using the constructor argument';
}
public function getFoo()
{
return $this->foo;
}
}

Some files were not shown because too many files have changed in this diff Show more