xsi:type subclass
| Q | A |
|---|---|
| Version | 4.1.1 |
Support Question
Hello!
This might be a dumb question, I am not super familiar with SOAP requests. I am testing around with this client and using the wizard to automatically set up my classmap and client.
I am facing the following problem with the decoding of responses. I am trying to decode into the specific class/subclass based on the xsi:type. However, my response is always defaulting to the parent class and it does not include all the properties returned by the response.
Here is a mockup of the problem:
Given a XML response as such:
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header/>
<S:Body>
<ns2:Response xmlns:ns2="<URI>">
<return>
<responses xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:A">
<foo>foo</foo>
</responses>
<responses xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:B">
<foo>foo</foo>
<bar>bar</bar>
</responses>
</return>
</ns2:Response>
</S:Body>
</S:Envelope>
And the following class structure:
class Response
{
protected ?Return $return = null;
}
class Return
{
/**
* @var null|array<int<0,max>, A>
*/
protected ?array $responses = null;
}
class A
{
protected string $foo;
}
class B extends A
{
protected string $bar;
}
I was expecting that after decoding, I'd get an object like this:
Response {
-return: Return {
-responses: array:2 [
0 => A {
-foo: "foo"
}
1 => B {
-foo: "foo"
-bar: "bar"
}
]
}
}
However, the responses are always mapped to the super class and I am losing the bar property:
Response {
-return: Return {
-responses: array:2 [
0 => A {
-foo: "foo"
}
1 => A {
-foo: "foo"
}
]
}
}
I am not sure if this is a common problem that can be fixed with some configuration, or if this particular SOAP response using xsi:type requires me to write a custom Decoder.
I'd appreciate any type of guidance! Thanks for your time!
Thanks for reporting. I don't think decoding currently really takes into account the xsi:type of the element for object-like structures.
Can you share the WSDL so that I can take a look what is going on?
Hey @veewee, thank you for the quick response! I can't really share the WSDL, and I tried to build a mockup example just to make sure I wasn't missing something obvious on the decoding set up.
I was actually surprised because using PHP's native SoapClient with a classmap, I was getting the right subclass by default. I wonder if SoapClient does parse xsi:type properties.
I was looking at the issues and stumbled upon #536 and I gave a try to set up a complex type converter instead of using the generated classmap from the wizard but I got stuck into how I would have access to the xsi:type property in order to choose the right subclass to decode to (I've looked through the docs from the different packages but I still have a lot to learn!).
Anyways, thanks again for the quick response. If this is not intended to be supported, don't worry about it!
@diegobanos If it works in PHP's SOAP extension, but not in this package - then I'dd say it is a bug.
As mentioned in the comment before : We currently don't lookup xsi:type during decoding the XML into PHP objects, because I considered the WSDL as leading. This might be a wrong assumption that needs to be fixed.
But in order to get this fixed, I'll need a reproducable WSDL + response. That way I can compare it with how PHP deals with this specific situation. I understand sharing the WSDL might be an issue for your. There might be some ways forward though:
- You can share with me in private.
- You can extract the part that is causing the problem from the WSDL and obscure the namespaces.
Hi,
When I m getting the QueryResult, all records are typed: SObject
Could you please explore this snippet for a real solution ?
$change = $metadata->getTypes()->fetchByNameAndXmlNamespace('Application', 'Namespace');
$change->getProperties()->map(function (Property $property) {
if ($property->getName() === 'records') {
$type = $property->getType();
$reflection = new \ReflectionClass($type);
$reflection->getProperty('name')->setValue($type, 'MyWantedObject');
$reflection->getProperty('xmlTypeName')->setValue($type, 'MyWantedObject');
}
});
In order to make it works; I m currently changing it
- MyWantedObject is a Child of SObject
<? xml version = "1.0" encoding = "UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:enterprise.soap.sforce.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sf="urn:sobject.enterprise.soap.sforce.com">
<soapenv:Header>
<LimitInfoHeader>
<limitInfo>
<current>1447</current>
<limit>5000000</limit>
<type>API REQUESTS</type>
</limitInfo>
</LimitInfoHeader>
</soapenv:Header>
<soapenv:Body>
<queryResponse>
<result>
<done>true</done>
<queryLocator xsi:nil="true"/>
<records xsi:type="sf:sObject">
<sf:Id>id</sf:Id>
<sf:Name>name</sf:Name>
</records>
<size>1</size>
</result>
</queryResponse>
</soapenv:Body>
</soapenv:Envelope>
<!-- Base sObject (abstract) -->
<complexType name="sObject">
<sequence>
<element name="Id" type="tns:ID" nillable="true"/>
</sequence>
</complexType>
<complexType name="Application">
<complexContent>
<extension base="ens:sObject">
<sequence>
<element name="Name" nillable="true" type="xsd:string"/>
</sequence>
</extension>
</complexContent>
</complexType>
<!-- Shared Result Types -->
<complexType name="QueryResult">
<sequence>
<element name="done" type="xsd:boolean"/>
<element name="queryLocator" type="tns:QueryLocator" nillable="true"/>
<element name="records" type="ens:sObject" nillable="true" minOccurs="0" maxOccurs="unbounded"/>
<element name="size" type="xsd:int"/>
</sequence>
</complexType>
Regards
Hello @franbuzz,
It's surely something we need to fix from our side rather than from an implementer's side. I'll try to make some time for this after summer. Can you share the full WSDL in the meantime?
FYI I've spent most of the day trying to get this to work.
Here is the initial draft: https://github.com/php-soap/encoding/pull/39
It's still a work in progress, but I think it should cover the xsi:type declarations everywhere.
Some remarks:
- Decoding XML is doable because we have the xsi:type information in the XML, hower encoding XML turns out to be a lot harder: Encoding the objects into XML occurs based on the metadata that is available. In this case that would be the 'base type' metadata. However, we need to somehow know what context the encoder needs to use for any given object. I don't really have a solution for this, rather than doing something like this: https://github.com/php-soap/encoding/blob/b5648d4f16856492fbe663b5e7945325de5789b0/tests/PhpCompatibility/Implied/ImpliedSchema014Test.php#L56-L81 IMO : This is not optimal (AND slow because the object metadata needs to be recalculated everytime ... ) so if you have any better ideas here ... Highly appreciated :)
- I still have to fix static analysis and see how much this slows down the code.
Feel free to play around with it and let me know if this works for you.
Latest php-soap/encoding release covers this:
- https://github.com/php-soap/encoding/releases/tag/0.22.0
- https://github.com/php-soap/encoding/pull/39
Feel free to try it out. All real-world usage feedback is welcome :)