Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 9 additions & 3 deletions doc/reference/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -469,15 +469,17 @@ to the least super type:

.. code-block :: php

#[Serializer\Discriminator(field: 'type', disabled: false, map: ['car' => 'Car', 'moped' => 'Moped'], groups=["foo", "bar"])]
#[Serializer\Discriminator(field: 'type', disabled: false, map: ['car' => 'Car', 'moped' => 'Moped'], groups=["foo", "bar"]), default: 'Other']
abstract class Vehicle { }
class Car extends Vehicle { }
class Moped extends Vehicle { }
class Other extends Vehicle { }
...

.. note ::

`groups` is optional and is used as exclusion policy.
`default` is optional.

#[UnionDiscriminator]
~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -488,10 +490,14 @@ to an attribute that can be one of many types.
.. code-block :: php

class Vehicle {
#[UnionDiscriminator(field: 'typeField', map: ['manual' => 'FullyQualified/Path/Manual', 'automatic' => 'FullyQualified/Path/Automatic'])]
private Manual|Automatic $transmission;
#[UnionDiscriminator(field: 'typeField', map: ['manual' => 'FullyQualified/Path/Manual', 'automatic' => 'FullyQualified/Path/Automatic'], default: 'FullyQualified/Path/Other')]
private Manual|Automatic|Other $transmission;
}

.. note ::

`default` is optional.

In the case of this example, both Manual and Automatic should contain a string attribute named `typeField`. The value of that field will be passed
to the `map` option to determine which class to instantiate.

Expand Down
3 changes: 2 additions & 1 deletion doc/reference/xml_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ XML Reference
<serializer>
<class name="Fully\Qualified\ClassName" exclusion-policy="ALL" xml-root-name="foo-bar" exclude="true"
exclude-if="expr" accessor-order="custom" custom-accessor-order="propertyName1,propertyName2,...,propertyNameN"
access-type="public_method" discriminator-field-name="type" discriminator-disabled="false" read-only="false">
access-type="public_method" discriminator-field-name="type" discriminator-disabled="false"
discriminator-default-class="DefaultClassName" read-only="false">
<xml-namespace prefix="atom" uri="http://www.w3.org/2005/Atom"/>
<xml-discriminator attribute="true" cdata="false" namespace=""/>
<discriminator-class value="some-value">ClassName</discriminator-class>
Expand Down
1 change: 1 addition & 0 deletions doc/reference/yml_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ YAML Reference
discriminator:
field_name: type
disabled: false
default: DefaultClassName
map:
some-value: ClassName
groups: [foo, bar]
Expand Down
5 changes: 4 additions & 1 deletion src/Annotation/Discriminator.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ class Discriminator implements SerializerAttribute
/** @var string[] */
public $groups = [];

public function __construct(array $values = [], string $field = 'type', array $groups = [], array $map = [], bool $disabled = false)
/** @var string */
public $default;

public function __construct(array $values = [], string $field = 'type', array $groups = [], array $map = [], bool $disabled = false, ?string $default = null)
{
$this->loadAnnotationParameters(get_defined_vars());
}
Expand Down
4 changes: 4 additions & 0 deletions src/GraphNavigator/DeserializationGraphNavigator.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ private function resolveMetadata($data, ClassMetadata $metadata): ?ClassMetadata
$typeValue = $this->visitor->visitDiscriminatorMapProperty($data, $metadata);

if (!isset($metadata->discriminatorMap[$typeValue])) {
if (!empty($metadata->discriminatorDefaultClass)) {
return $this->metadataFactory->getMetadataForClass($metadata->discriminatorDefaultClass);
}

throw new LogicException(sprintf(
'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
$typeValue,
Expand Down
27 changes: 21 additions & 6 deletions src/Metadata/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ class ClassMetadata extends MergeableClassMetadata
* @var string
*/
public $discriminatorBaseClass;
/**
* @var string
*/
public $discriminatorDefaultClass = null;
/**
* @var string
*/
Expand Down Expand Up @@ -134,7 +138,7 @@ class ClassMetadata extends MergeableClassMetadata
*/
public $excludeIf;

public function setDiscriminator(string $fieldName, array $map, array $groups = []): void
public function setDiscriminator(string $fieldName, array $map, array $groups = [], $defaultClass = null): void
{
if (empty($fieldName)) {
throw new InvalidMetadataException('The $fieldName cannot be empty.');
Expand All @@ -145,6 +149,7 @@ public function setDiscriminator(string $fieldName, array $map, array $groups =
}

$this->discriminatorBaseClass = $this->name;
$this->discriminatorDefaultClass = $defaultClass;
$this->discriminatorFieldName = $fieldName;
$this->discriminatorMap = $map;
$this->discriminatorGroups = $groups;
Expand Down Expand Up @@ -251,6 +256,10 @@ public function merge(MergeableInterface $object): void
$this->discriminatorDisabled = $object->discriminatorDisabled;
}

if (null !== $object->discriminatorDefaultClass) {
$this->discriminatorDefaultClass = $object->discriminatorDefaultClass;
}

if ($object->discriminatorMap) {
$this->discriminatorFieldName = $object->discriminatorFieldName;
$this->discriminatorMap = $object->discriminatorMap;
Expand Down Expand Up @@ -313,6 +322,7 @@ protected function serializeToArray(): array
$this->isList,
$this->isMap,
parent::serializeToArray(),
$this->discriminatorDefaultClass,
];
}

Expand Down Expand Up @@ -343,6 +353,7 @@ protected function unserializeFromArray(array $data): void
$this->isList,
$this->isMap,
$parentData,
$this->discriminatorDefaultClass,
] = $data;

parent::unserializeFromArray($parentData);
Expand All @@ -356,11 +367,15 @@ private function handleDiscriminatorProperty(): void
&& !$this->getReflection()->isInterface()
) {
if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
throw new InvalidMetadataException(sprintf(
'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
$this->name,
$this->discriminatorBaseClass,
));
if (! empty($this->discriminatorDefaultClass)) {
$typeValue = $this->discriminatorDefaultClass;
} else {
throw new InvalidMetadataException(sprintf(
'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
$this->name,
$this->discriminatorBaseClass,
));
}
}

$this->discriminatorValue = $typeValue;
Expand Down
2 changes: 1 addition & 1 deletion src/Metadata/Driver/AnnotationOrAttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat
if ($annot->disabled) {
$classMetadata->discriminatorDisabled = true;
} else {
$classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups);
$classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups, $annot->default);
}
} elseif ($annot instanceof XmlDiscriminator) {
$classMetadata->xmlDiscriminatorAttribute = (bool) $annot->attribute;
Expand Down
8 changes: 7 additions & 1 deletion src/Metadata/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ protected function loadMetadataFromFile(\ReflectionClass $class, string $path):
$readOnlyClass = 'true' === strtolower((string) $elem->attributes()->{'read-only'});

$discriminatorFieldName = (string) $elem->attributes()->{'discriminator-field-name'};
$discriminatorDefaultClass = (string) $elem->attributes()->{'discriminator-default-class'};
$discriminatorMap = [];
foreach ($elem->xpath('./discriminator-class') as $entry) {
if (!isset($entry->attributes()->value)) {
Expand All @@ -119,7 +120,12 @@ protected function loadMetadataFromFile(\ReflectionClass $class, string $path):
$discriminatorGroups[] = (string) $entry;
}

$metadata->setDiscriminator($discriminatorFieldName, $discriminatorMap, $discriminatorGroups);
$metadata->setDiscriminator(
$discriminatorFieldName,
$discriminatorMap,
$discriminatorGroups,
$discriminatorDefaultClass ?: null,
);
}

foreach ($elem->xpath('./xml-namespace') as $xmlNamespace) {
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ private function addClassProperties(ClassMetadata $metadata, array $config): voi
$config['discriminator']['field_name'],
$config['discriminator']['map'],
$groups,
$config['discriminator']['default'] ?? null,
);

if (isset($config['discriminator']['xml_attribute'])) {
Expand Down
9 changes: 9 additions & 0 deletions tests/Fixtures/Discriminator/Other.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Tests\Fixtures\Discriminator;

class Other extends Vehicle implements VehicleInterface
{
}
4 changes: 2 additions & 2 deletions tests/Fixtures/Discriminator/Vehicle.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
* @Serializer\Discriminator(field = "type", map = {
* "car": "JMS\Serializer\Tests\Fixtures\Discriminator\Car",
* "moped": "JMS\Serializer\Tests\Fixtures\Discriminator\Moped",
* })
* }, default="JMS\Serializer\Tests\Fixtures\Discriminator\Other")
*/
#[Serializer\Discriminator(field: 'type', map: ['car' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Car', 'moped' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Moped'])]
#[Serializer\Discriminator(field: 'type', map: ['car' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Car', 'moped' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Moped'], default: 'Fixtures\Discriminator\Other')]
abstract class Vehicle
{
/** @Serializer\Type("integer") */
Expand Down
4 changes: 2 additions & 2 deletions tests/Fixtures/Discriminator/VehicleInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
* @Serializer\Discriminator(field = "type", map = {
* "car": "JMS\Serializer\Tests\Fixtures\Discriminator\Car",
* "moped": "JMS\Serializer\Tests\Fixtures\Discriminator\Moped",
* })
* }, default="JMS\Serializer\Tests\Fixtures\Discriminator\Other")
*/
#[Serializer\Discriminator(field: 'type', map: ['car' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Car', 'moped' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Moped'])]
#[Serializer\Discriminator(field: 'type', map: ['car' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Car', 'moped' => 'JMS\Serializer\Tests\Fixtures\Discriminator\Moped'], default: 'Fixtures\Discriminator\Other')]
interface VehicleInterface
{
}
3 changes: 2 additions & 1 deletion tests/Metadata/Driver/yml/Discriminator.Vehicle.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
JMS\Serializer\Tests\Fixtures\Discriminator\Vehicle:
discriminator:
field_name: type
default: JMS\Serializer\Tests\Fixtures\Discriminator\Other
map:
car: JMS\Serializer\Tests\Fixtures\Discriminator\Car
moped: JMS\Serializer\Tests\Fixtures\Discriminator\Moped

properties:
km:
type: integer
type: integer
5 changes: 3 additions & 2 deletions tests/Serializer/BaseSerializationTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
use JMS\Serializer\Tests\Fixtures\Discriminator\Car;
use JMS\Serializer\Tests\Fixtures\Discriminator\ImagePost;
use JMS\Serializer\Tests\Fixtures\Discriminator\Moped;
use JMS\Serializer\Tests\Fixtures\Discriminator\Other;
use JMS\Serializer\Tests\Fixtures\Discriminator\Post;
use JMS\Serializer\Tests\Fixtures\Discriminator\Serialization\ExtendedUser;
use JMS\Serializer\Tests\Fixtures\Discriminator\Serialization\User;
Expand Down Expand Up @@ -1753,7 +1754,7 @@ public function testPolymorphicObjects()

public function testNestedPolymorphicObjects()
{
$garage = new Garage([new Car(3), new Moped(1)]);
$garage = new Garage([new Car(3), new Moped(1), new Other(1)]);
self::assertEquals(
static::getContent('garage'),
$this->serialize($garage),
Expand All @@ -1772,7 +1773,7 @@ public function testNestedPolymorphicObjects()

public function testNestedPolymorphicInterfaces()
{
$garage = new VehicleInterfaceGarage([new Car(3), new Moped(1)]);
$garage = new VehicleInterfaceGarage([new Car(3), new Moped(1), new Other(1)]);
self::assertEquals(
static::getContent('garage'),
$this->serialize($garage),
Expand Down
2 changes: 1 addition & 1 deletion tests/Serializer/JsonSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ protected static function getContent($key)
$outputs['post'] = '{"type":"post","title":"Post Title"}';
$outputs['image_post'] = '{"type":"image_post","title":"Image Post Title"}';
$outputs['image_post_without_type'] = '{"title":"Image Post Title"}';
$outputs['garage'] = '{"vehicles":[{"km":3,"type":"car"},{"km":1,"type":"moped"}]}';
$outputs['garage'] = '{"vehicles":[{"km":3,"type":"car"},{"km":1,"type":"moped"},{"km":1,"type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\Discriminator\\\Other"}]}';
Comment thread
ivan1986 marked this conversation as resolved.
$outputs['tree'] = '{"tree":{"children":[{"children":[{"children":[],"foo":"bar"}],"foo":"bar"}],"foo":"bar"}}';
$outputs['nullable_arrays'] = '{"empty_inline":[],"not_empty_inline":["not_empty_inline"],"empty_not_inline":[],"not_empty_not_inline":["not_empty_not_inline"],"empty_not_inline_skip":[],"not_empty_not_inline_skip":["not_empty_not_inline_skip"]}';
$outputs['object_with_object_property_no_array_to_author'] = '{"foo": "bar", "author": "baz"}';
Expand Down
4 changes: 4 additions & 0 deletions tests/Serializer/xml/garage.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@
<km>1</km>
<type><![CDATA[moped]]></type>
</entry>
<entry>
<km>1</km>
<type><![CDATA[JMS\Serializer\Tests\Fixtures\Discriminator\Other]]></type>
</entry>
</vehicles>
</result>
Loading