Skip to content
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

added support for xml-attributes as discriminators #692

Merged

Conversation

twtgrowth
Copy link

Until now when using Discriminator-maps with XML, the discriminator was hard coded to be a child node. This PR allows to use an attribute instead if desired.

 /**
 * @Discriminator(field = "type", map = {"car": "Car", "moped": "Moped"})
 */
 abstract class Vehicle { }
 class Car extends Vehicle { }
 class Moped extends Vehicle { }
 $serializer->serialize(new Car(), 'xml');

would result in

<vehicle>
    <type>car</type>
</vehicle>

now you can do this:

/**
 * @Discriminator(field = "type", map = {"car": "Car", "moped": "Moped"})
 * @XmlDiscriminatorAttribute
 */
abstract class Vehicle { }
class Car extends Vehicle { }
class Moped extends Vehicle { }

$serializer->serialize(new Car(), 'xml'); 

to get:

<vehicle type="car" />

@goetas
Copy link
Collaborator

goetas commented Jan 5, 2017

Thanks for the pull request. Have a look also to #560

Some considerations:

  • instead of adding a new annotation (that requires a lot of changes...) what about properties of the @Discriminator annotation?
  • this then opens possibilities as namespaces... and many other things

@fivetide
Copy link

fivetide commented Jan 6, 2017

Actually adding this to Discriminator, just like in #560 was our first approach, but after discussing this internally we concluded Discriminator would be better off remaining format-agnostic. With additional options in mind, i would like to suggest changing the new annotation to

@XmlDiscriminator(attribute=true)

which would enable future changes like

@XmlDiscriminator(cdata=false)
@XmlDiscriminator(namespace="foo/bar")

and changing the other drivers accordingly...

@goetas
Copy link
Collaborator

goetas commented Jan 6, 2017

hmm.. sounds reasonable. even now we have almost always xml-specific annotations.

So interesting options would be:

@XmlDiscriminator(
  attribute=true, // default = false
  cdata=false, // default = true
  namespace="http://xxx"  // default = NULL
)

right?

@fivetide
Copy link

fivetide commented Jan 6, 2017

Correct, just one question: I belive the current behaviour is to use cdata, which means we should default cdata to true...?

@goetas
Copy link
Collaborator

goetas commented Jan 6, 2017

@fivetide you are right, my bad, i have updated my comment

@fivetide
Copy link

fivetide commented Jan 6, 2017

Okay, i will propably get that done on monday, will update the PR then :)

@goetas goetas self-assigned this Jan 6, 2017
@goetas goetas added the WIP label Jan 6, 2017
@fivetide
Copy link

fivetide commented Jan 9, 2017

Ok, i updated the PR with the changes we agreed on.

Copy link
Collaborator

@goetas goetas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job :)
i will just change the YAML format and few minor changes here and there...

the namespaced xml element will be cool to have, but can be implemented in a second iteration...)

@@ -58,6 +58,9 @@ class ClassMetadata extends MergeableClassMetadata
public $discriminatorMap = array();
public $discriminatorGroups = array();

public $xmlDiscriminatorAttribute = false;
public $xmlDiscriminatorCData = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be $xmlDiscriminatorCDATA or $xmlDiscriminatorCdata ?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

capitalizing CD follows the convention used in PropertyMetadata::$xmlElementCData ...

@@ -257,6 +257,15 @@ private function addClassProperties(ClassMetadata $metadata, array $config)
$metadata->xmlRootNamespace = (string) $config['xml_root_namespace'];
}

if (isset($config['xml_discriminator'])) {
if (isset($config['xml_discriminator']['attribute'])) {
$metadata->xmlDiscriminatorAttribute = $config['xml_discriminator']['attribute'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast to boolean please

$metadata->xmlDiscriminatorAttribute = $config['xml_discriminator']['attribute'];
}
if (isset($config['xml_discriminator']['cdata'])) {
$metadata->xmlDiscriminatorCData = $config['xml_discriminator']['cdata'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast to boolean please

@@ -99,6 +100,9 @@ public function loadMetadataForClass(\ReflectionClass $class)
} else {
$classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups);
}
} elseif ($annot instanceof XmlDiscriminator) {
$classMetadata->xmlDiscriminatorAttribute = $annot->attribute;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast to boolean please

@@ -99,6 +100,9 @@ public function loadMetadataForClass(\ReflectionClass $class)
} else {
$classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups);
}
} elseif ($annot instanceof XmlDiscriminator) {
$classMetadata->xmlDiscriminatorAttribute = $annot->attribute;
$classMetadata->xmlDiscriminatorCData = $annot->cdata;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cast to boolean please

<serializer>
<class name="JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlAttributeDiscriminatorParent"
discriminator-field-name="type"
xml-discriminator-attribute="true"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this attribute (i mean xml-discriminator-attribute)?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, forgot to remove that one...

child: JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlAttributeDiscriminatorChild
xml_discriminator:
attribute: true
cdata: false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about having a structure as:

JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlAttributeDiscriminatorParent:
    discriminator:
        field_name: type
        map:
            child: JMS\Serializer\Tests\Fixtures\Discriminator\ObjectWithXmlAttributeDiscriminatorChild
        xml_attribute: true
        xml_element:
            cdata: false
            namespace: http://www.w3.org/2005/Atom

this is not a big deal, but is just to keep consistency with the current YAML metadata format.
(see http://jmsyst.com/libs/serializer/master/reference/yml_reference the xml_attribute property example)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the YAML format being wildly different to annotations and xml feels odd to me, but if you insist, i will change it...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea is just to keep it consistent. I would like to keep consistency within a format mainly.
Having consistency across formats is nice to have.

Since XML and YAML formats have already differences, and users use only one of them, will try to keep them consistent with the rest of the format itself.

public function testDiscriminatorAsXmlAttribute()
{
$xml = simplexml_load_string($this->serialize(new ObjectWithXmlAttributeDiscriminatorChild()));
$this->assertEquals('type="child"', trim($xml->xpath('//result/@type')[0]->saveXML()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of "re-loading" the xml into a simplexml object, why not re-use the $this->getContent('template_name') method? the whole test case will be:

$xml = $this->serialize(new ObjectWithXmlAttributeDiscriminatorChild());
$this->assertEquals($this->getContent('xml_discriminator_attribute'), $xml);
$this->assertInstanceOf(
    ObjectWithXmlAttributeDiscriminatorChild::class,
    $this->deserialize(
         $xml,
        ObjectWithXmlAttributeDiscriminatorParent::class
     )
);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the benefit when comparing to a predefined xml? I assumed the test should only cover if the discriminator is rendered as an attribute, while other aspects of xml-generation (whitespace, xml-header, if the tag is rendered as self-closing...) should not break the test, which might happen when comparing to predefined xml..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting topic about testing, and how much we want to do "unit" testing...

Your test currently tests only of the type is rendered as attribute... but what if for some other change, make the type to be rendered using both strategies.. I mean as attribute and as element? This test will not detect it.

Of course, testing the whole xml content, has some drawbacks, as example will give some "false" failures if we decide to change indentation or other format specific differences... that generally should not make fail your testcase.

To be honest, I prefer to have a false failure, instead of having a not detected failure.

(this means less "unit" testing, and going more in direction of functional or integration tests... (event if the border between this definitions is really blurry)

What is your opinion on it?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally and totally subjective, i like to have my tests represent actual requirements and i like to be able to treat them as much. When working with foreign code, i can not determine of a false failure is indeed false, or if there is an actual requirement... But since in this case, it is pretty obvious, i feel okay doing it your way.

@@ -261,6 +261,11 @@ private function resolveMetadata($data, ClassMetadata $metadata)
$typeValue = (string) $data->{$metadata->discriminatorFieldName};
break;

// Check XML attribute for discriminatorFieldName
case is_object($data) && $metadata->xmlDiscriminatorAttribute && isset($data[$metadata->discriminatorFieldName]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will put this statement before the previous one

Björn Bösel added 2 commits January 9, 2017 12:23
removed old attribute from test fixture
changed order of metadata-resolution
changed syntax of yml configuration
@goetas goetas added this to the v1.5 milestone Jan 9, 2017
@goetas goetas added To Merge and removed WIP labels Jan 9, 2017
Copy link
Collaborator

@goetas goetas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry missed that small PSR thing. Will try to include this in 1.5.0 (next feature release).

@@ -278,6 +278,17 @@ private function addClassProperties(ClassMetadata $metadata, array $config)
}
$groups = isset($config['discriminator']['groups']) ? $config['discriminator']['groups'] : array();
$metadata->setDiscriminator($config['discriminator']['field_name'], $config['discriminator']['map'], $groups);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PSR standards, max one empty line

@goetas goetas merged commit a1f4e04 into schmittjoh:master Jan 10, 2017
@fivetide
Copy link

Thanks a lot! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants