PHP native, ignores namespace in soapvar - php

I recently created a php web service using php native soap. I have created the wsdl, xsd and the php code to construct the response.
In my soapvar when I construct the soap arrayObject using the namespace prefix, some nodes have it and some don't.
What I want is all the nodes have the "ns1:" prefix or none of them.
In order to overcome the issue I removed the namespace from soapvar. So this removed the ns prefix But I always have the message from my wsdl "retrieveDataResponse" node with "ns1:" prefix and and all the rest I constructed without.
In my php I have nested foreach run in every node and children adding "XSD_STRING" or "SOAP_ENC_OBJECT" depending on the enc_type.
My soapvar in php in the foreach is :
$dataStruct[] = new SoapVar($ListOfDataStruct, SOAP_ENC_OBJECT, null, null, 'ListOfData', 'http://localhost/soap/retrieveCstData');
My XML response is
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/soap/retrieveCstData">
<SOAP-ENV:Body>
<ns1:retrieveDataResponse>
<ns1:cstData>
<ns1:Description>Discription N/A</ns1:Description>
<ns1:ListOfData>
<ns1:Customer-Data-Header>
<ns1:AssetDescription>Basic 12 Months</ns1:AssetDescription>
<ns1:AssetId>1-3QGMHQ9</ns1:AssetId>
<ns1:ProductDescription>Basic 12 Months</ns1:ProductDescription>
<ns1:ProductId>1-2E543A</ns1:ProductId>
<ns1:ProductName>Basic Product Subscription</ns1:ProductName>
<ns1:ListOfCstData-Asset>
<ns1:CstData-Asset>
<AssetIntegrationId>1-3Q3KSNI</AssetIntegrationId>
<ProductName>Basic Product Subscription</ProductName>
<ProductPartNumber>SAT0028</ProductPartNumber>
<StartDate>08/19/2015 21:00:00</StartDate>
<Status>Active</Status>
<ListOfProductXA/>
<ListOfAddress/>
<ListOfContact/>
</ns1:CstData-Asset>
</ns1:ListOfCstData-Asset>
</ns1:Customer-Data-Header>
<ns1:Customer-Data-Header>
<ns1:AssetId>1-7MRO-241</ns1:AssetId>
<ns1:ProductDescription>SubProduct A</ns1:ProductDescription>
<ns1:ProductId>1-65TVM</ns1:ProductId>
<ns1:ProductName>SubProduct A Type</ns1:ProductName>
<ns1:ProductType>Product</ns1:ProductType>
<ns1:ListOfCstData-Asset>
<ns1:CstData-Asset>
<AssetIntegrationId>1-5T126KG</AssetIntegrationId>
<ProductName>Asset-Product 1</ProductName>
<ProductPartNumber>N/A</ProductPartNumber>
<StartDate>08/16/2016 21:00:00</StartDate>
<Status>Active</Status>
<ListOfProductXA/>
<ListOfAddress/>
<ListOfContact/>
</ns1:CstData-Asset>
<ns1:CstData-Asset>
<AssetIntegrationId>W-C5PLG-11H-1</AssetIntegrationId>
<ProductName>SubProduct A Type</ProductName>
<ProductPartNumber>Data Packets</ProductPartNumber>
<RegisteredDate>02/21/1978</RegisteredDate>
<ServiceID>#56487%</ServiceID>
<StartDate>02/21/1978 00:00:00</StartDate>
<ListOfProductXA/>
<ListOfAddress>
<CutAddress>
<AddressType>Installation</AddressType>
<TEK>1651</TEK>
<Type>Old</Type>
<Country>US</Country>
<StreetNumberFrom>37</StreetNumberFrom>
<PostalCode>66857</PostalCode>
<State>CA</State>
<StreetName>
<State>JAX Avenue</State>
</StreetName>
</CutAddress>
</ListOfAddress>
<ListOfContact>
<Contact>
<ActiveStatus>Y</ActiveStatus>
<IsPrimaryMVG>Y</IsPrimaryMVG>
<CellularPhone>555687676</CellularPhone>
<FirstName>Jhon</FirstName>
<LastName>Doe</LastName>
<PreferredCommunicationMethod>SMS</PreferredCommunicationMethod>
<ContactType>Technical</ContactType>
</Contact>
</ListOfContact>
</ns1:CstData-Asset>
</ns1:ListOfCstData-Asset>
</ns1:Customer-Data-Header>
<ns1:Customer-Data-Header>
<ns1:AssetDescription>Satelite 80CM</ns1:AssetDescription>
<ns1:AssetId>1-3QGMHX9</ns1:AssetId>
<ns1:ProductDescription>Satelite 80CM</ns1:ProductDescription>
<ns1:ProductId>1-2DIYLT</ns1:ProductId>
<ns1:ProductName>TV SAT</ns1:ProductName>
<ns1:ProductType>Product</ns1:ProductType>
<ns1:ListOfCstData-Asset>
<ns1:CstData-Asset>
<SubscriberId>664668941</SubscriberId>
<Comments>Suspension/Reactivation</Comments>
<AssetIntegrationId>1-3Q3KSNJ</AssetIntegrationId>
<ProductName>TV SAT</ProductName>
<ProductPartNumber>TV_SAT</ProductPartNumber>
<ServiceID>9995654321587</ServiceID>
<StartDate>08/19/2015 21:00:00</StartDate>
<Status>Active</Status>
<ListOfProductXA/>
<ListOfAddress>
<CutAddress>
<AddressType>Installation</AddressType>
<TEK>1651</TEK>
<Type>Old</Type>
<Area>CA</Area>
<Country>US</Country>
<StreetNumberFrom>37</StreetNumberFrom>
<ResidenceType>Business</ResidenceType>
<Floor>0</Floor>
<MailBox>US</MailBox>
<PostalCode>66857</PostalCode>
<State>CA</State>
<StreetName>JAX AVENUE</StreetName>
<District>DownTown</District>
</CutAddress>
</ListOfAddress>
<ListOfContact/>
</ns1:CstData-Asset>
</ns1:ListOfCstData-Asset>
</ns1:Customer-Data-Header>
</ns1:ListOfData>
</ns1:retrieveDataResponse>
</ns1:retrieveDataResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
So as you can see prefix is not in every node.

Well I found the problem.
In one of the many foreach loops I used a previous declared arrayObject instead of the array key.
So soapvar does not ingone the namespace :)

Related

Array-Properties with the SeralizedPath-Attribute can't be serialized by Serializer

I have the problem that Properties with SerializedPath attributes, which I convert to an XML via Symfony Serializer (SerializerInterface, symfony/serializer-pack), does not have an expected behavior with arrays. Instead, the AbstractObjectNormalizer throws an exception. Symfony and related packages are using v6.2.5.
My goal is to create an XML like this one, with e.g. the following Object:
<?xml version="1.0"?>
<response>
<payment>
<allowedMethods>
<include name="Method A"/>
<include name="Method B"/>
</allowedMethods>
</payment>
</response>
// This is not working
class Payment
{
#[SerializedPath('[payment][allowedMethods][include][#name]')]
public array $methods = ['Method A', 'Method B'];
}
// ...
$this->serializer->serialize(new Payment(), 'xml');
But it results to the following Exception: The element you are trying to set is already populated: "[payment][allowedMethods][include][#name]".
which is thrown in the AbstractObjectNormalizer of Symfony:
if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath()) {
$propertyAccessor = PropertyAccess::createPropertyAccessor();
if ($propertyAccessor->isReadable($data, $serializedPath) && null !== $propertyAccessor->getValue($data, $serializedPath)) {
throw new LogicException(sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath));
}
$propertyAccessor->setValue($data, $serializedPath, $attributeValue);
return $data;
}
My expected behavior would be that it behaves identically to the scalar values and therefore recognizes the # as an XML attribute and the rest as nodes:
// This is working great!
class Payment
{
#[SerializedPath('[payment][usedMethod][#name]')]
public string $usedMethod = 'Method A';
}
<?xml version="1.0"?>
<response>
<payment>
<usedMethod name="Method A"/>
</payment>
</response>
I've been trying to identify the exact problem and find a solution for some time, but since SerializedPath is very new (since Symfony 6.2), there is no great documentation for it. Internally it uses the PropertyAccessor, but where I could not find a suitable solution either.
Can someone maybe explain me the problem in more detail or even know a solution? Or even an alternative way. I'm trying to do it via the SerializedPath to avoid all the nested DTOs that are just there to map the (not always plausible) data model of the XML.
Another quite interesting fact is, if you comment out the thrown Exception in the AbstractObjectNormalizer it kind of works (for json fully, for xml partially, because the array can only hold one entry because of the annotation key):
class Payment
{
#[SerializedPath('[payment][allowedMethods][include]')]
public array $methods = [
'#name' => 'Method A'
];
}
<?xml version="1.0"?>
<response>
<payment>
<allowedMethods>
<include name="Method A"/>
</allowedMethods>
</payment>
</response>

Change Namespace of Childern Nodes SimpleXMLElement

<ret2:formFields xsi:type="ret1:FormFieldsType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
I want to know how can I do the following? The namespace of children are getting changed by setting the xsi:type attribute.
My Code:
$ret2FormFields = $ret2FileBody->addChild('ret2:formFields', null, 'http://www.w3.org/2001/XMLSchema-instance');
$ret2FormFields->addAttribute("xsi:type", "ret1:FormFieldsType", 'http://www.w3.org/2001/XMLSchema-instance');
$ret2FormFields->addChild('ret1:isReverseReplace', false);
$ret2FormFields->addChild('ret1:payDayDate', '2018-04-10'); /** #todo date will be dynamic */
Expected XML:
<ret2:formFields xsi:type="ret1:FormFieldsType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ret1:isReverseReplace>false</ret1:isReverseReplace>
<ret1:payDayDate>2018-04-10</ret1:payDayDate>
</ret2:formFields>
My Incorrect XML:
<ret2:formFields xmlns:ret2="http://www.w3.org/2001/XMLSchema-instance" ret2:type="ret1:FormFieldsType">
<ret1:isReverseReplace>false</ret1:isReverseReplace>
<ret1:payDayDate>2018-04-10</ret1:payDayDate>
</ret2:formFields>
I am stuck how to change the children namespaces ret1 without changing the parent namespace ret2
You are passing the wrong namespace to addChild here:
addChild('ret2:formFields', null, 'http://www.w3.org/2001/XMLSchema-instance');
This tells SimpleXML that you want the element to have prefix ret2 and namespace URI http://www.w3.org/2001/XMLSchema-instance, so it will generate:
<ret2:formFields xmlns:ret2="http://www.w3.org/2001/XMLSchema-instance">
You don't show in your example what the prefix ret2 has been assigned elsewhere in the document, but that's what you need to provide, e.g.:
addChild('ret2:formFields', null, 'http://example.org/mynamespaces/ret2');

Deserialize nested xml nodes

I've got an xml response from an api formatted as follows:
<?xml version='1.0' encoding='UTF-8'?>
<response success="true">
<messages>
<message type="WARNING" key="warning-unpublished-changes" values="" parentId="1">
You have unpublished changes. Your changes will not be visible every where until it is published.</message>
</messages>
<output>
<accounts>
<account
id="1"
code="AssetsChild"
name="AssetsChild"
description="Total Assets Child"
displayAs="CURRENCY"
accountTypeCode="A"
decimalPrecision="0"
isAssumption="0"
suppressZeroes="1"
isDefaultRoot="1"
shortName=""
exchangeRateType="E"
balanceType="DEBIT"
formula=""
isLinked="0"
owningSheetId=""
isSystem="0"
isIntercompany="0"
dataEntryType=""
planBy="DELTA"
timeRollup="LAST"
timeWeightAcctId=""
levelDimRollup="SUM"
levelDimWeightAcctId=""
rollupText=""
startExpanded="1"
hasSalaryDetail=""
dataPrivacy="PRIVATE"
isBreakbackEligible=""
subType="CUMULATIVE"
enableActuals="1"
isGroup="0"
/>
</accounts>
</output>
</response>
I'd like to have it deserialized to a response object defined as:
class Response
{
protected $success;
protected $messages;
protected $accounts;
}
I've been able to successfully get the success value and message array using the config below. Is it possible to get the hydrate the accounts property with the list of account nodes?
Response\AccountResponse:
xml_root_name: response
properties:
success:
type: boolean
xml_attribute: true
xml_value: false
messages:
type: array<Entity\Message>
xml_list:
entry_name: message
Use SimpleXml. specifically, use simple_xml_load_string to transform the string into a SimpleXmlElement the use the class methods to navigate and extract the data.

Add attributes to XML tags in Yii 2 Response

Yii2. Action (method) in controller:
public function actionGet()
{
Yii::$app->response->format = Response::FORMAT_XML;
return [
'items' => [
['id' => 'data'],
['id' => 'body'],
],
];
}
At output get XML:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<items>
<item>
<id>data</id>
</item>
<item>
<id>body</id>
</item>
</items>
</response>
How can add attributes in XML tags? And remove response tag:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item update="true">
<id>data</id>
</item>
<item update="false" new="true">
<id>body</id>
</item>
</items>
Documentation not show this case.
You can't.
This feature is currently not supported and isn't on the roadmap. You'll have to build your own ResponseFormatter (implementing http://www.yiiframework.com/doc-2.0/yii-web-responseformatterinterface.html) to achieve this.
See: https://github.com/yiisoft/yii2/issues/5996
Also, removing the response tag isn't possible. You can rename the root tag by setting the rootTag value for the formatter. http://www.yiiframework.com/doc-2.0/yii-web-xmlresponseformatter.html#%24rootTag-detail

Simple PHP SOAP request to ClickandBuy?

ClickandBuy provides lots of samples, but they are poorly coded and old. So I think there should be an easy PHP 5 SOAP solution for a simple SOAP payRequest with the PHP5 build in SoapClient class.
$client = new SoapClient('https://api.clickandbuy.com/webservices/pay_1_1_0.wsdl', array('encoding' => 'UTF-8'));
$client->payRequest(array('authentication'=>array(...), 'details'=>array(...)));
Works, but it returns an error:
SOAP-ERROR: Encoding: object has no 'description' property
The arguments of the payRequest method should be fine. Did anyone try to implement ClickandBuy without NuSOAP?
Try adding description property into details array:
$client->payRequest(array('authentication'=>array(...),
'details' => array(
'description' => 'paying for shoes'
)
));
Sample request:
<?xml version="1.0" encoding="UTF-8"?>
<payRequest_Request xmlns="http://api.clickandbuy.com/webservices/pay_1_1_0/">
<authentication>
<merchantID>4000</merchantID>
<projectID>1</projectID>
<token>20100623104511::9E9C3E21FE38851B8913469F13619BD645BA1DD6</token>
</authentication>
<details>
<amount>
<amount>1</amount>
<currency>EUR</currency>
</amount>
<orderDetails>
<text>My Cart</text>
</orderDetails>
<successURL>http://www.mydomain.com/success.php</successURL>
<failureURL>http://www.mydomain.com/failure.php</failureURL>
<externalID>Test123</externalID>
</details>
</payRequest_Request>

Categories