Skip to content

Commit

Permalink
minor #30888 [serializer] extract normalizer tests to traits (dbu)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 4.3-dev branch (closes #30888).

Discussion
----------

[serializer] extract normalizer tests to traits

eufossa

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | Relates to #30818
| License       | MIT
| Doc PR        | -

As discussed with @joelwurtz, extract normalizer functionality tests into traits to ensure consistent behaviour of all normalizers.

* [x] Rebase when #30977, #30950 and #30907 are merged to master **blocker**
* [x] Clean up order of trait inclusion and methods in the tests
* [x] Clean up fixture classes of the traits. I started having one class named the same as the trait, where possible

Stuff that we should do eventually, but can also do in separate pull requests, after this one has been merged:
* [ ] Extract all features that we can (the existing normalizer tests should more or less only have the legacy tests in them, all functionality should be in trait)
* [ ] Run test coverage and increase coverage so that we cover all important features and all relevant error cases.

Commits
-------

2b6ebea [serializer] extract normalizer tests to traits
  • Loading branch information
fabpot committed Apr 27, 2019
2 parents 66fd6c5 + 2b6ebea commit 016425e
Show file tree
Hide file tree
Showing 25 changed files with 1,693 additions and 1,207 deletions.
89 changes: 79 additions & 10 deletions src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
Expand Up @@ -32,20 +32,89 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
use ObjectToPopulateTrait;
use SerializerAwareTrait;

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';
const DEFAULT_CONSTRUCTOR_ARGUMENTS = 'default_constructor_arguments';
const CALLBACKS = 'callbacks';
const CIRCULAR_REFERENCE_HANDLER = 'circular_reference_handler';
const IGNORED_ATTRIBUTES = 'ignored_attributes';
/* constants to configure the context */

/**
* How many loops of circular reference to allow while normalizing.
*
* The default value of 1 means that when we encounter the same object a
* second time, we consider that a circular reference.
*
* You can raise this value for special cases, e.g. in combination with the
* max depth setting of the object normalizer.
*/
public const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit';

/**
* Instead of creating a new instance of an object, update the specified object.
*
* If you have a nested structure, child objects will be overwritten with
* new instances unless you set DEEP_OBJECT_TO_POPULATE to true.
*/
public const OBJECT_TO_POPULATE = 'object_to_populate';

/**
* Only (de)normalize attributes that are in the specified groups.
*/
public const GROUPS = 'groups';

/**
* Limit (de)normalize to the specified names.
*
* For nested structures, this list needs to reflect the object tree.
*/
public const ATTRIBUTES = 'attributes';

/**
* If ATTRIBUTES are specified, and the source has fields that are not part of that list,
* either ignore those attributes (true) or throw an ExtraAttributesException (false).
*/
public const ALLOW_EXTRA_ATTRIBUTES = 'allow_extra_attributes';

/**
* Hashmap of default values for constructor arguments.
*
* The names need to match the parameter names in the constructor arguments.
*/
public const DEFAULT_CONSTRUCTOR_ARGUMENTS = 'default_constructor_arguments';

/**
* Hashmap of field name => callable to normalize this field.
*
* The callable is called if the field is encountered with the arguments:
*
* - mixed $attributeValue value of this field
* - object $object the whole object being normalized
* - string $attributeName name of the attribute being normalized
* - string $format the requested format
* - array $context the serialization context
*/
public const CALLBACKS = 'callbacks';

/**
* Handler to call when a circular reference has been detected.
*
* If you specify no handler, a CircularReferenceException is thrown.
*
* The method will be called with ($object, $format, $context) and its
* return value is returned as the result of the normalize call.
*/
public const CIRCULAR_REFERENCE_HANDLER = 'circular_reference_handler';

/**
* Skip the specified attributes when normalizing an object tree.
*
* This list is applied to each element of nested structures.
*
* Note: The behaviour for nested structures is different from ATTRIBUTES
* for historical reason. Aligning the behaviour would be a BC break.
*/
public const IGNORED_ATTRIBUTES = 'ignored_attributes';

/**
* @internal
*/
const CIRCULAR_REFERENCE_LIMIT_COUNTERS = 'circular_reference_limit_counters';
protected const CIRCULAR_REFERENCE_LIMIT_COUNTERS = 'circular_reference_limit_counters';

protected $defaultContext = [
self::ALLOW_EXTRA_ATTRIBUTES => true,
Expand Down
Expand Up @@ -33,13 +33,60 @@
*/
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';
const SKIP_NULL_VALUES = 'skip_null_values';
const MAX_DEPTH_HANDLER = 'max_depth_handler';
const EXCLUDE_FROM_CACHE_KEY = 'exclude_from_cache_key';
const DEEP_OBJECT_TO_POPULATE = 'deep_object_to_populate';
/**
* Set to true to respect the max depth metadata on fields.
*/
public const ENABLE_MAX_DEPTH = 'enable_max_depth';

/**
* How to track the current depth in the context.
*/
private const DEPTH_KEY_PATTERN = 'depth_%s::%s';

/**
* While denormalizing, we can verify that types match.
*
* You can disable this by setting this flag to true.
*/
public const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement';

/**
* Flag to control whether fields with the value `null` should be output
* when normalizing or omitted.
*/
public const SKIP_NULL_VALUES = 'skip_null_values';

/**
* Callback to allow to set a value for an attribute when the max depth has
* been reached.
*
* If no callback is given, the attribute is skipped. If a callable is
* given, its return value is used (even if null).
*
* The arguments are:
*
* - mixed $attributeValue value of this field
* - object $object the whole object being normalized
* - string $attributeName name of the attribute being normalized
* - string $format the requested format
* - array $context the serialization context
*/
public const MAX_DEPTH_HANDLER = 'max_depth_handler';

/**
* Specify which context key are not relevant to determine which attributes
* of an object to (de)normalize.
*/
public const EXCLUDE_FROM_CACHE_KEY = 'exclude_from_cache_key';

/**
* Flag to tell the denormalizer to also populate existing objects on
* attributes of the main object.
*
* Setting this to true is only useful if you also specify the root object
* in OBJECT_TO_POPULATE.
*/
public const DEEP_OBJECT_TO_POPULATE = 'deep_object_to_populate';

private $propertyTypeExtractor;
private $typesCache = [];
Expand Down
Expand Up @@ -19,4 +19,20 @@ class DeepObjectPopulateChildDummy
public $foo;

public $bar;

// needed to have GetSetNormalizer consider this class as supported
public function getFoo()
{
return $this->foo;
}

public function setFoo($foo)
{
$this->foo = $foo;
}

public function setBar($bar)
{
$this->bar = $bar;
}
}
Expand Up @@ -42,4 +42,9 @@ public function getChild()
{
return $this->child;
}

public function getFoo()
{
return $this->foo;
}
}
Expand Up @@ -10,7 +10,6 @@
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Tests\Fixtures\AbstractNormalizerDummy;
use Symfony\Component\Serializer\Tests\Fixtures\NullableConstructorArgumentDummy;
use Symfony\Component\Serializer\Tests\Fixtures\ProxyDummy;
use Symfony\Component\Serializer\Tests\Fixtures\StaticConstructorDummy;
use Symfony\Component\Serializer\Tests\Fixtures\StaticConstructorNormalizer;

Expand Down Expand Up @@ -99,18 +98,6 @@ public function testGetAllowedAttributesAsObjects()
$this->assertEquals([$a3, $a4], $result);
}

public function testObjectToPopulateWithProxy()
{
$proxyDummy = new ProxyDummy();

$context = [AbstractNormalizer::OBJECT_TO_POPULATE => $proxyDummy];

$normalizer = new ObjectNormalizer();
$normalizer->denormalize(['foo' => 'bar'], 'Symfony\Component\Serializer\Tests\Fixtures\ToBeProxyfiedDummy', null, $context);

$this->assertSame('bar', $proxyDummy->getFoo());
}

public function testObjectWithStaticConstructor()
{
$normalizer = new StaticConstructorNormalizer();
Expand Down
Expand Up @@ -23,8 +23,6 @@
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Tests\Fixtures\DeepObjectPopulateChildDummy;
use Symfony\Component\Serializer\Tests\Fixtures\DeepObjectPopulateParentDummy;

class AbstractObjectNormalizerTest extends TestCase
{
Expand Down Expand Up @@ -163,58 +161,6 @@ public function testExtraAttributesException()
'allow_extra_attributes' => false,
]);
}

public function testSkipNullValues()
{
$dummy = new Dummy();
$dummy->bar = 'present';

$normalizer = new ObjectNormalizer();
$result = $normalizer->normalize($dummy, null, [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
$this->assertSame(['bar' => 'present'], $result);
}

public function testDeepObjectToPopulate()
{
$child = new DeepObjectPopulateChildDummy();
$child->bar = 'bar-old';
$child->foo = 'foo-old';

$parent = new DeepObjectPopulateParentDummy();
$parent->setChild($child);

$context = [
AbstractObjectNormalizer::OBJECT_TO_POPULATE => $parent,
AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true,
];

$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor());

$newChild = new DeepObjectPopulateChildDummy();
$newChild->bar = 'bar-new';
$newChild->foo = 'foo-old';

$serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerDenormalizer')->getMock();
$serializer
->method('supportsDenormalization')
->with($this->arrayHasKey('bar'),
$this->equalTo(DeepObjectPopulateChildDummy::class),
$this->isNull(),
$this->contains($child))
->willReturn(true);
$serializer->method('denormalize')->willReturn($newChild);

$normalizer->setSerializer($serializer);
$normalizer->denormalize([
'child' => [
'bar' => 'bar-new',
],
], 'Symfony\Component\Serializer\Tests\Fixtures\DeepObjectPopulateParentDummy', null, $context);

$this->assertSame('bar-new', $parent->getChild()->bar);
$this->assertSame('foo-old', $parent->getChild()->foo);
}
}

class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
Expand Down

0 comments on commit 016425e

Please sign in to comment.