Troubles parsing XML object in PHP (laravel 5.8) - php

I'm having troubles parsing an XML object in PHP. I'm using Laravel 5.8
Here is one of my tries:
$xml = new \SimpleXMLElement($formatted, LIBXML_BIGLINES);
var_dump($xml->children('soapenv', true)->Envalop->Body->children('ns3', true)->getAddressBookResponse->addressBook[0]->businessUnit);
And I'm getting the following error:
Call to a member function children() on null
I have tried different variations of creating and accessing the SimpleXMLElement but always with the same result. I'm getting back an empty object of typeSimpleXMLElement
Here is a sample XML that I'm using as input (coming from SOAP API call):
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns3:getAddressBookResponse xmlns:ns2="http://oracle.e1.bssv.JPRCUST0/"
xmlns:ns3="http://oracle.e1.bssv.JPR01000/">
<e1MessageList>
<e1Messages>
<message>Description: All records for the query have not been returned.
</message>
</e1Messages>
</e1MessageList>
<addressBook>
<businessUnit>123456</businessUnit>
<categoryCodesAddressBook>
<categoryCode001>XXX</categoryCode001>
<categoryCode002>XXX</categoryCode002>
</categoryCodesAddressBook>
<description1>MOHAMEDHASSANALI</description1>
</addressBook>
<addressBook>
<businessUnit>789789</businessUnit>
<categoryCodesAddressBook>
<categoryCode001>YYY</categoryCode001>
<categoryCode002>YYY</categoryCode002>
</categoryCodesAddressBook>
<description1>ALIHASSANAHMED</description1>
</addressBook>
</ns3:getAddressBookResponse>
</soapenv:Body>
</soapenv:Envelope>

That is not just XML, but SOAP. I suggest using a SOAP library (like ext/soap).
It's Envelope not Envalop. And this is the element in you $xml variable.
If you want to treat it as just XML, do not rely on the namespaces prefixes but the actual namespace URIs (the values of the xmlns attributes). Prefixes can change. A prefix like ns3 is auto generated by a SOAP library, just adding/removing an element with another namespace could change it. So define constants or variables for the XMLNS values and use them.
const XMLNS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
const XMLNS_ADDRESSES = 'http://oracle.e1.bssv.JPR01000/';
$envelope = new \SimpleXMLElement($formatted, LIBXML_BIGLINES);
var_dump(
$envelope->children(XMLNS_SOAP)->Body->children(XMLNS_ADDRESSES)->getAddressBookResponse->children('')->addressBook[0]->businessUnit
);
Xpath expressions (SimpleXMLElement::xpath()) allow for conditional fetching. This avoids problems if an element is missing, but you will have to register your own prefixes for the namespaces.
const XMLNS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/';
const XMLNS_ADDRESSES = 'http://oracle.e1.bssv.JPR01000/';
$envelope = new \SimpleXMLElement($formatted, LIBXML_BIGLINES);
$envelope->registerXpathNamespace('s', XMLNS_SOAP);
$envelope->registerXpathNamespace('a', XMLNS_ADDRESSES);
var_dump(
$envelope->xpath('(s:Body/a:getAddressBookResponse/addressBook)[1]/businessUnit')
);

Related

Access Property of nested namespace in XML document

I need to access the value of an attribute located within a nested namespace in a XML document. Here is a sample of the XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp:epp xmlns:epp="urn:ietf:params:xml:ns:epp-1.0">
<epp:response>
<epp:result code="1000">
<epp:msg>Domain Check Command completed successfully</epp:msg>
</epp:result>
<epp:msgQ count="4" id="OTE-EPP-155DF5E824E-1BC86"/>
<epp:resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="1">example.com</domain:name>
</domain:cd>
</domain:chkData>
</epp:resData>
<epp:trID>
<epp:clTRID>123</epp:clTRID>
<epp:svTRID>456</epp:svTRID>
</epp:trID>
</epp:response>
</epp:epp>
In the above, I need to access the "avail" attribute which from what I can gather is located in a nested namespace domain within the epp namespace.
I am using simplexml_load_string($xml) to access the properties. I am able to access the epp namespace but my lack of XML knowledge is failing me on this one :)
Thank you in advance!
EDIT
As the question is marked as a duplicate I want to clear up that I am looking for a value of a property located within a nested namespace.
I have tried to access the property via xpath but have only received undefined namespace prefix errors:
echo $data->xpath('/domain:name')->attributes()->avail;
echo $data->xpath('/epp:response/epp:resData/domain:chkData/domain:cd/domain:name')->attributes()->avail;
I have also tried the above without the epp namespace but the same result. I know how to get the value from a single namespace but not from a nested namespace.
Another Edit:
I am still struggling. I really cannot get xpath to find the associated namespace element within the xml document. The below code is what I'm trying.
I have tried to also just do a vardump on $sxe->xpath('//d:name')) but get an empty array, indicating no values?
Please can someone help a brother out :)
$sxe = simplexml_load_string($result, "SimpleXMLElement", 0, "epp", true);
$code = $sxe->response->result->attributes()->code;
if ($code == 1000) {
$sxe->registerXPathNamespace('d','ietf:params:xml:ns:domain-1.0');
echo $sxe->xpath('//d:name')->attributes()->avail;
}
exit;
YET ANOTHER EDIT
Following another answer I changed my simplexml_load_string() function declaration and attempted to declare 2 xpath namespaces (feels like the closest I have gotten so far) - I am able to get the value in the epp namespace but still no luck with the domain namespace.
$sxe = simplexml_load_string($result,NULL,NULL,'urn:ietf:params:xml:ns:epp-1.0');
$sxe->registerXPathNamespace('epp', 'ietf:params:xml:ns:epp-1.0');
$sxe->registerXPathNamespace('domain', 'ietf:params:xml:ns:domain-1.0');
$code = $sxe->xpath('//epp:result')[0]->attributes()->code;
if ($code == 1000) {
print_r($sxe->xpath('domain:name'));
print_r($sxe->xpath('//domain:name'));
}
exit;

How do I call SOAP methods in PHP using namespaces (in params and methods) and nested structures (in params)?

Assume I have this XML (it is a SOAP call)
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ext="http://api.movilway.net/schema/extended">
<soapenv:Header/>
<soapenv:Body>
<ext:GetBalanceRequest>
<ext:AuthenticationData>
<ext:Username>Foo</ext:Username>
<ext:Password>Bar$123!password</ext:Password>
<ext:SessionID>as3hy4ri37g2f345</ext:SessionID>
</ext:AuthenticationData>
<ext:DeviceType>3</ext:DeviceType>
</ext:GetBalanceRequest>
</soapenv:Body>
</soapenv:Envelope>
(Foo, Bar$123!password, as3hy4ri37g2f345 and 3 are just sample values)
Usually, when I want to do simple SOAP calls, I use a SoapClient like this:
$sc = new SoapClient('http://my.url/my/service?wsdl');
$result = $sc->someMethod(array('some' => 'params'));
But this one seems to use xsd namespaces and nested structures.
Q: How do I call methods with namespaces prefixes (ext:, in this case) AND parameters with namespaces prefixes (and nested structures)?
Edit: What I tried involves including the namespace as the uri option. And got an exception like this:
SoapFault : Function ("GetBalanceRequest") is not a valid method for this service
The code I tried was this:
try {
$client = new SoapClient('http://THEURLHERE/Path/To/The/Service?wsdl', array('uri' => 'http://api.movilway.net/schema/extended'));
print_r($client->GetBalanceRequest(
array(
'AuthenticationData' => array(
'Username' => 'MYUSERHERE',
'Password' => 'MYPASSWORDHERE'
),
'DeviceType' => 1
)
));
} catch(Exception $e) {
print_r($e);
}
Assume there's no error nor typo since I got the required XML directly from the documentation.
Q+: What must I add to the code to send such request?
Firstly, the correct term is not "extension", but "namespace" - it's just coincidence that the namespace here is called "extended" and has been given the alias ext: in the example.
Secondly, an XML namespace is simply a way of saying "these elements and attributes are of a particular type"; it does not automatically imply any special structure beyond normal XML - it has no automatic relationship to an XSD, for instance. A namespace is uniquely identified by a URI, which needn't actually point anywhere (see this previous answer for more on that). Within a document, it is given an arbitrary prefix, so that you don't have to write the URI next to every element.
SOAP itself uses the namespace http://schemas.xmlsoap.org/soap/envelope/ for the elements that represent the SOAP "envelope", here given the alias soapenv. The "body" of the SOAP message is not in that namespace, so it is common for SOAP services to declare their elements as part of some other specific namespace; if they didn't, they would be in the default, nameless, namespace.
So, so much for theory. On to practice:
If you are using a WSDL to load the web service, and that WSDL is properly formed, the SOAPClient class should add the appropriate namespace to your request automatically. Since the request exists entirely inside that namespace, there is no need to distinguish between "AuthenticationData in namespace http://api.movilway.net/schema/extended" and just "AuthenticationData".
If this doesn't work for some reason, or you have no WSDL, you may need to create SoapVar objects with the appropriate namespace assigned to them.
However, based on the error message you just edited into your question, all of the above may be completely irrelevant, because the problem might have nothing to do with namespaces at all - you are operating in WSDL mode, and the client is telling you that the method doesn't exist. So, the obvious question to me is, is that method definitely defined in that WSDL file?

Set the value of SOAP-ENV Nusoap

How to change my Soapenv:Envelope parameter from this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ejb="http://ejb.gateway.ebpp.fawryis.com/"
xmlns:ns3678="http://tempuri.org">
to this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ejb="http://ejb.gateway.ebpp.fawryis.com/">
How to set the nusoap client Soapenv so it can be the same as the second format?
Also in every element there is xmlns empty attribute:
<process xmlns="http://ejb.gateway.ebpp.fawryis.com/">
<arg0 xmlns=""><Request xmlns="">
How to remove those xmlns attributes?
Here is my code:
$client = new nusoap_client($wsdl_path, TRUE);
$client->namespaces = array(
'SOAP-ENV'=>"http://schemas.xmlsoap.org/soap/envelope/",
'ejb'=>"http://ejb.gateway.ebpp.fawryis.com/"
);
$parameters = array(//the parameters );
$result = $client->call("process", $parameters);
Thank you
I understand you could fork Nusoap project and customize nusoap.php source code to return exactly what you whish. But notice that some people are telling us we don't need to bother with this prefix SOAP-ENV. If you do not care about which one is being serialized (SOAP-ENV or soap prefix), keep using the original nusoap project. Remember to enter the same namespace.
Here some extra information:
You can use any string you like as a namespace prefix, just as long as
it maps to the appropriate namespace name. The reason why the spec
bothers with telling us the notational conventions is that prefix
names (e.g., SOAP-ENV) aren't normative.
Source: SOAP-ENV vs. soapenv
...as long as soap and SOAP-ENV refer to the same namespace URI, then
everything will be OK.
Source: SOAP-ENV: vs. soap:Envelope
...can be any allowed string - it does not matter to the XML parser as
long as the correct namespace URL has been given.
Source: SOAP Envelope namespace

cannot generate proper soap xml request via php

I'm trying to create proper wsdl based soap request but with no success, here is example of what I need:
soap.xml:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<AuthMember xmlns="http://tempuri.org/">
<somefield>string</somefield>
</AuthMember>
</soap:Header>
<soap:Body>
<AuthenticateMember xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
My result is:
soap.xml:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://tempuri.org/" xmlns:ns2="http://schemas.xmlsoap.org/ws/2002/07/utility">
<SOAP-ENV:Header>
<ns2:AuthMember>
<somefield>somefieldvalue</somefield>
</ns2:AuthMember>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:AuthenticateMember/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
this is my php code:
class SoapHeaderAuthMember
{
public $somefield;
public function __construct($somefield)
{
$this->somefield = $somefield;
}
}
$client = new SoapClient( 'https://www.somepage.com/service.asmx?WSDL',
array("exceptions"=>0, "trace" => 1 )
);
$authMember = new SoapHeaderAuthMember('somefieldvalue');
$soapHeaders[] = new SoapHeader('http://schemas.xmlsoap.org/ws/2002/07/utility', 'AuthMember', $authMember);
$client->__setSoapHeaders($soapHeaders);
$client->__soapCall('AuthenticateMember',array());
see,
1.it generates SOAP-ENV:Envelope instead of SOAP-ENV:Envelope
2.in header: I have ns2:AuthMember instead of AuthMember
3.in body I have ns1:AuthenticateMember instead of AuthenticateMember xmlns="http://tempuri.org/"
How can I get proper xml ? I've looked through php functions manuals and cannot find the answer, googling did not give me success results for my case.
Could you please help ?
The result that has been generated is what has been requested in the code, but an explanation of some attributes of namespaces is in order:
Each of the tags in an XML document has a fully-qualified form which is defined by the namespace and the tag name (even if it is in the 'default' namespace with no explicit namespace declaration). The fully-qualified version is typically written as {namespace}tag. By resolving the fully-qualified name it is possible to determine whether the representation of two elements is the same.
Namespaces are associated with elements in multiple ways including explicit namespace inclusion with the element
Example:
<AuthMember xmlns="http://tempuri.org/">
resolving to fully-qualified name {http://tempuri.org/}AuthMember
and via namespace-associated prefix
Example:
<... xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
resolving to {http://schemas.xmlsoap.org/soap/envelope/}Header
The prefix is in scope for the element in which the prefix was declared and any contained elements. The actual prefix used is not important (soap, SOAP-ENV or randomprefix could be used for the http://schemas.xmlsoap.org/soap/envelope/ namespace), although good practice is to use something meaningful.
Given that, the fully-qualified elements in the 'desired' and 'actual' documents are almost the same, the anomaly being the header element AuthMember. In your desired you indicate a namespace of http://tempuri.org/ for a fully-qualified {http://tempuri.org/}AuthMember. In the actual the prefix association results in a fully-qualified {http://schemas.xmlsoap.org/ws/2002/07/utility}AuthMember.
This is the result of the SoapHeader instantiation specifying the ...utility namespace for AuthMember. Changing that statement in your code to use the http://tempuri.org/ namespace should result in the actual document matching the desired.

PHP simpleXML: Handling Unknown Namespaces in SOAP Requests

There are a ton of existing questions about PHP's simpleXML and processing XML with namespaces. All of the questions I've looked at have made a fundamental assumption: The code knows in advance what namespaces are going to be included in an incoming SOAP request. In my case, I've seen inconsistent namespaces in SOAP requests.
Specifically, I've been working on implementing a web service to talk to the Quickbooks Web Connector(pdf) and some of the example requests I've seen look like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:dev="http://developer.intuit.com/">
<soapenv:Header/>
<soapenv:Body>
<dev:authenticate>
<dev:strUserName>username</dev:strUserName>
<dev:strPassword>password</dev:strPassword>
</dev:authenticate>
</soapenv:Body>
</soapenv:Envelope>
...and some look like this:
<s11:Envelope
xmlns:s11='http://schemas.xmlsoap.org/soap/envelope/'
xmlns:ns1='http://developer.intuit.com/'>
<s11:Header/>
<s11:Body>
<ns1:authenticate>
<ns1:strUserName>username</ns1:strUserName>
<ns1:strPassword>password</ns1:strPassword>
</ns1:authenticate>
</s11:Body>
</s11:Envelope>
...or this:
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://developer.intuit.com/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns1:authenticate>
<ns1:strUserName>username</ns1:strUserName>
<ns1:strPassword>password</ns1:strPassword>
</ns1:authenticate>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I understand using xpath() to select elements, but that assumes you know what namespace to look in. Without any consistency in the namespacing, I'm having a hard time figuring out how to correctly and programmatically select the contents of the node to process.
The namespacing is completely irrelevant in this application-- could I just run the raw XML through a regex to remove the whatever: from <whatever:mytag> first?
First off, if you plan to use SOAP a lot, you may want to take a look at PHP's SOAP extension if you haven't already. I've never used it, though.
Back to your question, you said "In my case, I've seen inconsistent namespaces in SOAP requests." Get ready because I'm about to blow your mind: no you haven't. :)
In those three examples, the two namespaces are the same: there's http://schemas.xmlsoap.org/soap/envelope/ and there's http://developer.intuit.com/ -- What's different here is their prefix. The good news is the prefix doesn't really matter. See it as an alias to the namespace. The prefixes used in the document are automatically registered for use in XPath, but you can also register your own.
Here's an example of how to use the prefixes that were defined in the document (good if you already know what they are) or register your own prefixes and use those.
$xml = '<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:dev="http://developer.intuit.com/">
<soapenv:Header/>
<soapenv:Body>
<dev:authenticate>
<dev:strUserName>username</dev:strUserName>
<dev:strPassword>password</dev:strPassword>
</dev:authenticate>
</soapenv:Body>
</soapenv:Envelope>';
$Envelope = simplexml_load_string($xml);
// you can register and use your own prefixes
$Envelope->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
$Envelope->registerXPathNamespace('auth', 'http://developer.intuit.com/');
$nodes = $Envelope->xpath('/soap:Envelope/soap:Body/auth:authenticate/auth:strUserName');
$username = (string) $nodes[0];
// or you can use the prefixes that are already defined in the document
$nodes = $Envelope->xpath('/soapenv:Envelope/soapenv:Body/dev:authenticate/dev:strPassword');
$password = (string) $nodes[0];
var_dump($username, $password);
There are couple helpful simplexml element methods that can help you determine and make use of the proper namespaces when querying with the xpath method. The first two are getNamespaces and getDocNamespaces. getNamespaces will return all of the namespaces used in the document (specify the recursive parameter), while getDocNamespaces will return all namespaces declared by the document.
Once you have the array of namespaces available, you can use registerXPathNamespace to register each namespace to the simplexml_element that you are going to use the xpath method.
I'm a new user so I can't post the links to the other methods in php's documentation.

Categories