-
-
Notifications
You must be signed in to change notification settings - Fork 586
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
Setting xsi:type attribute on node #433
Comments
Hi, i am facing the same problem with xsi:type. I´ve defined all the namespaces but I`ve got no clue how to add the attribute to the node. Any news on this? Patrick |
Hi Patrick, I didn't find the solution, unfortunately I ended up doing some str_replace and preg_replace on the generated XML to achieve this, dirty and hacky, but I needed to get it out of the door. Simon |
I´ve found a solution based on the Attributes-Annotation. I will send you the sample on Friday.. out of office at the moment. The trick is to define namespaces and add an attribute with the name XSI:TYPE to the object. You can´t do it with normal annotation but if you´re using the array annotation for attributes... everything is fine. Maybe not the best solution but it works for me... |
Sorry... I was sick for a few days... So here´s my solution XSIStringField.php <?php
namespace domain;
use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\XmlRoot;
use JMS\Serializer\Annotation\XmlValue;
use JMS\Serializer\Annotation\XmlElement;
use JMS\Serializer\Annotation\XmlNamespace;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\XmlAttributeMap;
/**
* @XmlNamespace(uri="http://www.w3.org/2001/XMLSchema-instance", prefix="xsi")
* @XmlNamespace(uri="http://www.w3.org/2001/XMLSchema", prefix="xs")
*/
class XSIStringField {
public function __construct($value) {
$this->value = $value;
}
/**
* @Type("string")
* @XmlAttributeMap
*/
public $attrib = ['xsi:type' => 'xs:string'];
/**
* @Type("string")
* @SerializedName("key")
* @XmlValue
*/
public $value;
} GenericPluginEntry.php <?php
namespace domain;
use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\XmlRoot;
use JMS\Serializer\Annotation\XmlValue;
use JMS\Serializer\Annotation\XmlElement;
use JMS\Serializer\Annotation\XmlNamespace;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\XmlAttributeMap;
class GenericPluginEntry {
public function __construct($key, $value) {
$this->key = new XSIStringField($key);
$this->value = new XSIStringField($value);
}
/**
* @Type("domain\XSIStringField")
* @SerializedName("key")
*/
public $key;
/**
* @Type("domain\XSIStringField")
* @SerializedName("value")
*/
public $value;
} This allows me to set the XSI:Type stuff on my key/value pairs and result into something like that... <entry>
<key xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string"><![CDATA[Blup]]></key>
<value xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string"><![CDATA[Bla]]></value>
</entry> Hope that it will help someone in the future :-) |
I implemented this for myself as listener. This is currently most likely POC but maybe you want to adapt the approach (and maybe even include it in the serializer meta data):
services:
jms_xsi_type_handling_listener:
class: Application\EventListener\JmsXsiTypeHandlingListener
arguments:
# Provide any xsi:type override here.
- { namespace: http://example.org/some/namespace, type: Type1, class: PhpNamespace\Type1Class }
- { namespace: http://example.org/some/namespace, type: Type2, class: PhpNamespace\Type2Class } <?php
namespace Application\EventListener;
use DOMElement;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
use JMS\Serializer\XmlSerializationVisitor;
use SimpleXMLElement;
/**
* This class implements XML schema instance type handling.
*
* When serializing and deserializing, one can override classes with other types.
*/
class JmsXsiTypeHandlingListener
{
/**
* The XML schema type indicator overrides.
*
* @var array
*/
private $override = [];
/**
* The XML schema type indicator reverse overrides.
*
* @var array
*/
private $reverse = [];
/**
* The XMLSchema instance namespace.
*/
const URI_XSI = 'http://www.w3.org/2001/XMLSchema-instance';
/**
* The XML namespace namespace.
*/
const URI_XMLNS = 'http://www.w3.org/2000/xmlns/';
/**
* Create a new instance.
*
* You may pass as many override parameters as desired.
*
* @param array $override The type(s) to override.
*/
public function __construct(array $override = [])
{
foreach (func_get_args() as $override) {
$this->addOverride($override['namespace'], $override['type'], $override['class']);
}
}
/**
* Add a schema override.
*
* @param string $namespace The namespace the type name is located in.
* @param string $typeName The type name to change.
* @param string $newClass The new class name.
*
* @return void
*/
public function addOverride(string $namespace, string $typeName, string $newClass)
{
if (!isset($this->override[$namespace])) {
$this->override[$namespace] = [];
}
$this->override[$namespace][$typeName] = $newClass;
$this->reverse[$newClass] = [$namespace, $typeName];
}
/**
* Add the xsi:type attribute to the element if needed.
*
* @param ObjectEvent $event The event being dispatched.
*
* @return void
*/
public function postSerialize(ObjectEvent $event)
{
$visitor = $event->getVisitor();
// We only handle XML serializing in here.
if (!($visitor instanceof XmlSerializationVisitor)) {
return;
}
if ($override = ($this->reverse[$event->getType()['name']] ?? null)) {
/** @var DOMElement $element */
$element = $visitor->getCurrentNode();
// Obtain prefix or add it if not defined yet.
$prefix = $this->lookupPrefixOrAdd($element, $override[0]);
$element->setAttributeNS(self::URI_XSI, 'type', $prefix . ':' . $override[1]);
}
}
/**
* Change destination class if there is a schema type declared and we can handle it.
*
* @param PreDeserializeEvent $event The event being dispatched.
*
* @return void
*/
public function preDeSerialize(PreDeserializeEvent $event)
{
/** @var SimpleXMLElement $data */
$data = $event->getData();
$attributes = $data->attributes(self::URI_XSI);
if (isset($attributes['type'])) {
$override = $this->tryTypeOverride($attributes['type'], $data);
$event->setType($override, $event->getType()['params']);
}
}
/**
* Lookup the namespace prefix or add it to the document if not found.
*
* @param DOMElement $element The XML element to add the prefix for.
* @param string $namespace The namespace to look up.
*
* @return string
*/
private function lookupPrefixOrAdd(DOMElement $element, $namespace)
{
// Obtain prefix or add it if not defined yet.
if ($prefix = ($element->lookupPrefix($namespace))) {
return $prefix;
}
$element->ownerDocument->documentElement->setAttributeNS(
self::URI_XMLNS,
'xmlns:ns-' . crc32($namespace),
$namespace
);
return $element->ownerDocument->documentElement->lookupPrefix($namespace);
}
/**
* Try to override the type.
*
* @param string $typeName The type name.
* @param SimpleXMLElement $element The element.
*
* @return string
*
* @throws \RuntimeException When an unknown XML schema type is encountered.
*/
private function tryTypeOverride(string $typeName, SimpleXMLElement $element)
{
if (false === strpos($typeName, ':')) {
// Try to find a xsi:schemaLocation in parents.
$override = $this->tryOverrideViaRootNamespace($typeName, $element);
} else {
$override = $this->tryOverrideViaNamespacePrefix($typeName, $element);
}
if (!$override) {
throw new \RuntimeException('Invalid XML schema type: ' . $typeName);
}
return $override;
}
/**
* Try to find a schema location in the parent elements and return the correct type then.
*
* @param string $type The type name.
* @param SimpleXMLElement $element The element to start from.
*
* @return string|null
*/
private function tryOverrideViaRootNamespace(string $type, SimpleXMLElement $element)
{
$namespaces = $element->getNamespaces(true);
// We have a proper root NS, use it.
if (isset($namespaces[''])) {
return ($this->override[$namespaces['']][$type] ?? null);
}
return null;
}
/**
* Try to find a schema location in the parent elements and return the correct type then.
*
* @param string $typeName The type name.
* @param SimpleXMLElement $element The element to start from.
*
* @return string|null
*/
private function tryOverrideViaNamespacePrefix(string $typeName, SimpleXMLElement $element)
{
$xsiType = explode(':', $typeName, 2);
return ($this->override[$element->getDocNamespaces(true)[$xsiType[0]]][$xsiType[1]] ?? null);
}
} This way there is no need to define any virtual property or real property. |
@discordier Thanks for the elaborate example, I've used a similar approach in my project. |
Hi,
I hope it's ok for me to ask for advice here, I am not sure where to go otherwise. I have a simple object that I am using JMS/Serializer to convert to XML.
I have got this working to this stage
This is my config in YML
However I wish to set an attribute on the DirParty node that looks like
xsi:type='AxdEntity_DirParty_DirOrganization'
Could some one point me in the right direction?
Thanks
The text was updated successfully, but these errors were encountered: