Skip to content

[Serializer] Properties extractor implementations #30980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add discriminant normalizer
  • Loading branch information
joelwurtz committed Apr 9, 2019
commit e525ad837c6eba54474d1ec8d8c21b1e492e925b
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?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\LogicException;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;

class ClassDiscriminatorNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface, NormalizerAwareInterface, DenormalizerAwareInterface, SerializerAwareInterface
{
private $denormalizer;

private $denormalizerChain;

private $classDiscriminatorResolver;

public function __construct(DenormalizerInterface $denormalizer, ClassDiscriminatorResolverInterface $classDiscriminatorResolver)
{
$this->denormalizer = $denormalizer;
$this->classDiscriminatorResolver = $classDiscriminatorResolver;
}

/**
* {@inheritdoc}
*/
public function normalize($object, $format = null, array $context = [])
{
if (!$this->denormalizer instanceof NormalizerInterface) {
throw new LogicException('Cannot normalize object because injected denormalizer is not a normalizer');
}

$data = $this->denormalizer->normalize($object, $format, $context);
$mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object);

if (null !== $mapping && (null !== ($typeValue = $mapping->getMappedObjectType($object)))) {
$data[$mapping->getTypeProperty()] = $typeValue;
}

return $data;
}

/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
if (!$this->denormalizer instanceof NormalizerInterface) {
return false;
}

return $this->denormalizer->supportsNormalization($data, $format);
}

/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = [])
{
$mapping = $this->classDiscriminatorResolver->getMappingForClass($class);

if (null === $mapping) {
return $this->denormalizer->denormalize($data, $class, $format, $context);
}

if (!isset($data[$mapping->getTypeProperty()])) {
throw new RuntimeException(sprintf('Type property "%s" not found for the abstract object "%s"', $mapping->getTypeProperty(), $class));
}

$type = $data[$mapping->getTypeProperty()];

if (null === ($mappedClass = $mapping->getClassForType($type))) {
throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s"', $type, $class));
}

return $this->denormalizerChain->denormalize($data, $mappedClass, $format, $context);
}

/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return
$this->denormalizer->supportsDenormalization($data, $type, $format)
||
(\interface_exists($type, false) && null !== $this->classDiscriminatorResolver->getMappingForClass($type))
;
}

/**
* {@inheritdoc}
*/
public function hasCacheableSupportsMethod(): bool
{
return $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod();
}

/**
* {@inheritdoc}
*/
public function setDenormalizer(DenormalizerInterface $denormalizer)
{
if ($this->denormalizer instanceof DenormalizerAwareInterface) {
$this->denormalizer->setDenormalizer($denormalizer);
}
$this->denormalizerChain = $denormalizer;
}

/**
* {@inheritdoc}
*/
public function setNormalizer(NormalizerInterface $normalizer)
{
if ($this->denormalizer instanceof NormalizerAwareInterface) {
$this->denormalizer->setNormalizer($normalizer);
}
}

/**
* {@inheritdoc}
*/
public function setSerializer(SerializerInterface $serializer)
{
if ($this->denormalizer instanceof SerializerAwareInterface) {
$this->denormalizer->setSerializer($serializer);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
<?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\Normalizer;

use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\ClassDiscriminatorNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Serializer;

class ClassDiscriminatorNormalizerTest extends TestCase
{
public function testNormalize()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$subNormalizer->method('normalize')->willReturn(['foo' => 'foo']);
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);

$dummy = new Dummy();

$data = $normalizer->normalize($dummy, 'json');
$this->assertSame(['foo' => 'foo'], $data);

$data = $normalizer->normalize($dummy, 'json');
$this->assertSame(['foo' => 'foo'], $data);
}

public function testSupportNormalization()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$subNormalizer->method('supportsNormalization')->willReturn(true);
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);

$this->assertTrue($normalizer->supportsNormalization([], 'json'));

$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);
$subNormalizer->method('supportsNormalization')->willReturn(false);
$this->assertFalse($normalizer->supportsNormalization([], 'json'));
}

public function testDenormalize()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$dummy = new DummyCircular();
$subNormalizer->method('denormalize')->willReturn($dummy);
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);

$data = $normalizer->denormalize([], 'type', 'json');

$this->assertSame($dummy, $data);
}

public function testSupportDenormalization()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$subNormalizer->method('supportsDenormalization')->willReturn(true);
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);

$this->assertTrue($normalizer->supportsDenormalization([], 'type', 'json'));

$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class])
->getMock()
;

$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);
$subNormalizer->method('supportsDenormalization')->willReturn(false);
$this->assertFalse($normalizer->supportsDenormalization([], 'type', 'json'));
}

public function testHasCacheableSupportMethod()
{
$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class, CacheableSupportsMethodInterface::class])
->getMock()
;

$subNormalizer->method('hasCacheableSupportsMethod')->willReturn(true);
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);

$this->assertTrue($normalizer->hasCacheableSupportsMethod());

$subNormalizer = $this
->getMockBuilder([NormalizerInterface::class, DenormalizerInterface::class, CacheableSupportsMethodInterface::class])
->getMock()
;

$subNormalizer->method('hasCacheableSupportsMethod')->willReturn(false);
$normalizer = new ClassDiscriminatorNormalizer($subNormalizer, $resolver);
$this->assertFalse($normalizer->hasCacheableSupportsMethod());
}

public function testDiscriminantNormalize()
{
$childNormalizer = new DummyChildNormalizer();
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($childNormalizer, $resolver);
$serializer = new Serializer([$normalizer]);

$data = $normalizer->normalize(new DummyFooChild(), 'json');

$this->assertSame([
'foo' => 'foo',
'type' => 'foo',
], $data);

$data = $normalizer->normalize(new DummyBarChild(), 'json');

$this->assertSame([
'bar' => 'bar',
'type' => 'bar',
], $data);

$data = $normalizer->normalize(new DummyParent(), 'json');

$this->assertSame([], $data);
}

public function testDiscriminantDenormalize()
{
$childNormalizer = new DummyChildNormalizer();
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($childNormalizer, $resolver);
$serializer = new Serializer([$normalizer]);

$data = $normalizer->denormalize(['type' => 'foo'], DummyParent::class, 'json');
$this->assertInstanceOf(DummyFooChild::class, $data);

$data = $normalizer->denormalize(['type' => 'bar'], DummyParent::class, 'json');
$this->assertInstanceOf(DummyBarChild::class, $data);
}

/**
* @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
*/
public function testUnknowValue()
{
$childNormalizer = new DummyChildNormalizer();
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($childNormalizer, $resolver);
$serializer = new Serializer([$normalizer]);

$data = $normalizer->denormalize(['type' => 'unknow'], DummyParent::class, 'json');
}

/**
* @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
*/
public function testNoType()
{
$childNormalizer = new DummyChildNormalizer();
$resolver = new ClassDiscriminatorFromClassMetadata(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$normalizer = new ClassDiscriminatorNormalizer($childNormalizer, $resolver);
$serializer = new Serializer([$normalizer]);

$data = $normalizer->denormalize([], DummyParent::class, 'json');
}
}

/**
* @DiscriminatorMap(mapping={
* "foo"="Symfony\Component\Serializer\Tests\Normalizer\DummyFooChild",
* "bar"="Symfony\Component\Serializer\Tests\Normalizer\DummyBarChild"
* }, typeProperty="type")
*/
class DummyParent
{
}

class DummyFooChild extends DummyParent
{
}

class DummyBarChild extends DummyParent
{
}

class DummyChildNormalizer implements NormalizerInterface, DenormalizerInterface
{
public function denormalize($data, $class, $format = null, array $context = [])
{
if ($class === DummyFooChild::class) {
return new DummyFooChild();
}

return new DummyBarChild();
}

public function supportsDenormalization($data, $type, $format = null)
{
return true;
}

public function normalize($object, $format = null, array $context = [])
{
if ($object instanceof DummyFooChild) {
return ['foo' => 'foo'];
}

if ($object instanceof DummyBarChild) {
return ['bar' => 'bar'];
}

return [];
}

public function supportsNormalization($data, $format = null)
{
return true;
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy