How to use nuSOAP for messages with multiple namespaces - php

I'm trying to access a WebService using nuSOAP (because I'm bound to PHP4 here) that uses more than 1 namespace in a message. Is that possible?
An example request message would look like this:
<soapenv:Envelope ...
xmlns:ns1="http://domain.tld/namespace1"
xmlns:ns2="http://domain.tld/namespace2">
<soapenv:Header/>
<soapenv:Body>
<ns1:myOperation>
<ns2:Person>
<ns2:Firstname>..</ns2:Firstname>
..
</ns2:Person>
<ns1:Attribute>..</ns1:Attribute>
</ns1:myOperation>
</soapenv:Body>
</soapenv:Envelope>
I tried to following:
$client = new nusoap_client("my.wsdl", true);
$params = array(
'Person' => array(
'FirstName' => 'Thomas',
..
),
'Attribute' => 'foo'
);
$result = $client->call('myOperation', $params, '', 'soapAction');
in the hope that nuSOAP would try to match these names to the correct namespaces and nodes. Then I tried to use soapval() to generate the elements and their namespace - but if I call an operation, nuSOAP creates the following request:
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Body>
<queryCCApplicationDataRequest xmlns="http://domain.tld/namespace1"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
So something goes wrong during the "matching" phase.

After trying around with the matching, I found two possible solutions:
1) Don't use the WSDL to create the nusoap_client and soapval() to create the message
This has the disadvantage that the message contains a lot of overhead (the namespace is defined in each element). Not so fine.
2) Instead of relying on the matching of parameters, construct your reply in xml and put all the definition for prefixes in the first element - e.g.
$params = "<ns1:myOperation xmlns:ns1="..." xmlns:ns2="...">
<ns2:Person>
<ns2:Firstname>..</ns2:Firstname>
..
</ns2:Person>
<ns1:Attribute>..</ns1:Attribute>
</ns1:myOperation>";
Still not a very nice solution, but it works :-)

Building on Irwin's post, I created the xml manually and had nusoap do the rest. My webhost does not have the php soap extension, so I had to go with nusoap, and the web service I'm trying to consume required the namespaces on each tag (e.g. on username and password in my example here).
require_once('lib/nusoap.php');
$client = new nusoap_client('https://service.somesite.com/ClientService.asmx');
$client->soap_defencoding = 'utf-8';
$client->useHTTPPersistentConnection(); // Uses http 1.1 instead of 1.0
$soapaction = "https://service.somesite.com/GetFoods";
$request_xml = '<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<n1:GetFoods xmlns:n1="https://service.somesite.com">
<n1:username>banjer</n1:username>
<n1:password>theleftorium</n1:password>
</n1:GetFoods>
</env:Body>
</env:Envelope>
';
$response = $client->send($request_xml, $soapaction, '');
echo '<h2>Request</h2><pre>' . htmlspecialchars($client->request, ENT_QUOTES) . '</pre>';
echo '<h2>Response</h2><pre>' . htmlspecialchars($client->response, ENT_QUOTES) . '</pre>';
echo '<h2>Debug</h2><pre>' . htmlspecialchars($client->getDebug(), ENT_QUOTES) . '</pre>';
Then I had an error that said:
Notice: Undefined property: nusoap_client::$operation in ./lib/nusoap.php on line 7674
So I went the lazy route and went into nusoap.php and added this code before line 7674 to make it happy:
if(empty($this->operation)) {
$this->operation = "";
}

Another bypass this issue would be a modification to nusoap_client::call() function. Next to the this line (7359 in version 1.123) in nusoap.php:
$nsPrefix = $this->wsdl->getPrefixFromNamespace($namespace);
I added this one:
$nsPrefix = $this->wsdl->getPrefixFromNamespace("other_ns_name");
And it worked! Since I only needed this library for one project, it was OK for me to hardcode this hack. Otherwise, I would dig more and modify the function to accept array instead of string for a namespace parameter.

Yeah, i've been having this same problem (found your q via google!) and i've come across this:
http://www.heidisoft.com/blog/using-nusoap-consume-net-web-service-10-min
Here, the dev creates the xml body of the message in coe and then uses nusoap to submit.

Related

How do you access SimpleXML nodes in a namespace environment?

How do you access PHP SimpleXML nodes in a namespace environment?
I want to pragmatically add <param>value</param> under <request>. Not just edit it in a string.
$xml = \SimpleXMLElement(
'<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL
. '<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/">' . PHP_EOL
. ' <soap:Body>' . PHP_EOL
. ' <Function xmlns="https://webservices.sveaekonomi.se/webpay">' . PHP_EOL
. ' <request />' . PHP_EOL
. ' </Function>' . PHP_EOL
. ' </soap:Body>' . PHP_EOL
. '</soap:Envelope>'
);
I have attempted the following:
#1
$xml->Envelope->Body->Function->request->addChild('param', 'value');
#2
$xml->children('https://webservices.sveaekonomi.se/webpay')->request->addChild('param', 'value');
#3
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('/swp:request')->addChild('param', 'value');
You've made a few common mistakes here, so I'll go through each in turn.
Let's start with the beginning of your first attempt:
$xml->Envelope->Body->...
SimpleXML doesn't have a separate object for the document, only the root element - in this case, Envelope. So you don't need to say ->Envelope, you're already there. Without namespaces being involved, you would write:
$xml->Body->...
However, the object doesn't automatically "select" the namespace of that element, so you have to either immediately call ->children(), or pass the namespace you want to pre-select into the constructor:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body->...
// Or
$xml = new SimpleXMLElement($xmlString, 0, false, 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->Body->...
With that in mind, we get to:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body->Function->...
This fails because Function isn't in the same namespace as Body. The way I like to think of it is that the ->children() method is a "switch namespace" method, so we switch to the right namespace and carry on:
$xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body
->children('https://webservices.sveaekonomi.se/webpay')->Function->request
->addChild('param', 'value');
This will work!
Your second attempt makes a different mistake:
$xml->children('https://webservices.sveaekonomi.se/webpay')->request->...
The "children" method doesn't let you jump deep into the document - as its name suggests, it gives you the direct children of an element, not the grand-children, great grand-children, and so on. The $xml variable points to the "Envelope" node, and that doesn't have a child called "request".
There isn't a built-in method for "any descendant of", except by using XPath...
Although seemingly completely different, your third attempt actually fails for the same reason:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('/swp:request')->...
The / operator in XPath similarly means "children of"; there is an operator for "any descendant of", which is //, so you would have got nearly the right result with this:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('//swp:request')->...
That will fail for a slightly more subtle reason: the xpath() method of SimpleXML always returns an array, not an object, so you have to ask for the first element ([0]) of that array.
So the working XPath code is this:
$xml->registerXPathNamespace('swp', 'https://webservices.sveaekonomi.se/webpay');
$xml->xpath('//swp:request')[0]->addChild('param', 'value');
Since you are dealing with xml one way to approach it is to use xpath and local-name():
$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:Body>
<Function xmlns="https://webservices.sveaekonomi.se/webpay">
<request/>
</Function>
</soap:Body>
</soap:Envelope>
';
$doc = new SimpleXMLElement($xml);
$destination = ($doc->xpath('//*[local-name()="request"]'));
$destination[0]->addChild('param', 'value');
echo $doc->asXML();
Output:
<?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:Body>
<Function xmlns="https://webservices.sveaekonomi.se/webpay">
<request><param>value</param></request>
</Function>
</soap:Body>
</soap:Envelope>

PHP SOAPCall null result

I have deployed a SOAP WS with AXIS. I use SOAPClient library and PHP 5.5.9 on Ubuntu 14.04 to call the exposed operations, but when I make the "__SoapCall" it returns nothing. I have also tried with "NuSOAP" library, but I obtain the same result.
But when I call "__getLastResponse", It returns the good response (although duplicated), as you can see:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<idXMLReturn xmlns="http://aemetproyecto">PD94bWwgdm...</idXMLReturn>
</soapenv:Body>
</soapenv:Envelope>
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<idXMLReturn xmlns="http://aemetproyecto">PD94bWwgdm...</idXMLReturn>
</soapenv:Body>
</soapenv:Envelope>
Here is the WSDL
My PHP code:
<?php
function generarTabla($id, $formato){
try{
// Create the SoapClient instance
$url = "http://localhost:8080/axis/services/AemetProyect?wsdl";
$client = new SoapClient($url, array("trace" => 1, "exception" => 1));
// Creo XML
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
$xml .= "<!DOCTYPE id [\n";
$xml .= "<!ELEMENT id (#PCDATA)>\n";
$xml .= "]>\n";
$xml .= "<id>".$id."</id>";
// Codifico XML en Base64
$Base64xml = base64_encode($xml);
// Llamada SOAP a DescargarInfoTiempo
$resultado = $client -> __soapCall("DescargarInfoTiempo",
array($Base64xml));
//$resultado = $client -> __getLastResponse();
//echo $resultado;
//$resultado = $client -> __getLastRequest();
//echo $resultado;
echo $resultado;
} catch (SoapFault $ex){
$error = "SOAP Fault: (faultcode: {$ex->faultcode}\n"
."faultstring: {$ex->faultstring})";
echo $error;
} catch (Exception $e){
$error = "Exception: {$e->faultstring}";
echo $error;
}
}
?>
I've noticed that the name of return element ("idXMLReturn") is not the same that the name described in WSDL ("DescargarInfoTiempoReturn").
Can this be the problem?
Update
I've tried to do:
$argumens['idXML'] = $Base64xml;
$resultado = $client -> __soapCall("DescargarInfoTiempo",
array($arguments));
and
$arguments['idXML'] = $Base64xml;
$resultado = $client ->DescargarInfoTiempo($arguments);
But then I get "Notice: Array to string conversion" when I do the call.
Your error is in a little detail.
For a call using the SoapClient, you must send the arguments in a array of arguments, instead to send a xml (why you need to do a codification base64? Is some rule of your WebService?). See the PHP docs for get the right way
arguments
An array of the arguments to pass to the function. This can be either an ordered or an associative array. Note that most SOAP servers require parameter names to be provided, in which case this must be an associative array.
So, your array arguments* must to be something like this:
$arguments['id'] = $id// if you need base 64, use base64_encode($id) or something
$resultado = $client -> __soapCall("DescargarInfoTiempo",array($arguments));
Other option to do the same thing, is call the function directly:
$arguments['id'] = $id// if you need base 64, use base64_encode($id) or something
$resultado = $client ->DescargarInfoTiempo($arguments);// note that we don't need array($arguments), just $arguments
See other examples in the comments on the doc page.
Finally, if you still want to create your XML to send, I advise you to do it with other methos like file_get_contents or CURL and don't forget to create your XML with a soap envelop, is needed for the Soap Protocol
*Some old WebServices needs a array with name "parameters"
UPDATE
Look, you're trying to send your $arguments in a array that contains only one element: The XML in your $Base64xml var. I think that The problem still here.
According to PHP manual, you can't send XML in your SoapCall. The var must be a associative array with your vars, so try to do something like this:
$arguments['id'] = $id// this $id var is your function argument(int, string or somenthing else), forget the created XML
$resultado = $client -> __soapCall("DescargarInfoTiempo",array($arguments));
About the base64 that you need, I never needed it before, but see the marcovtwout comment in this page of PHP manual
If your WSDL file containts a parameter with a base64Binary type, you should not use base64_encode() when passing along your soap vars. When doing the request, the SOAP library automatically base64 encodes your data, so otherwise you'll be encoding it twice.
So, I belive that you don't need to encode your vars.
In short, forget the XML and send only your vars. The PHP SoapClient create the Soap envelop with the corrected encodes and all these things.
If you still with problems doing this, try to enclose your var with some SoapVars. Maybe your WSDL configuration needs this treatment.
I hope this helps
The solution is to specify the document literal style as it is explained in
Creating a SOAP call using PHP with an XML body
As I said above, "I've noticed that the name of return element ("idXMLReturn") is not the same that the name described in WSDL ("DescargarInfoTiempoReturn")"
So, that's the problem: AXIS doesn't generate a Envelope that fits to the WSDL schema. It seems java:RPC provider doesn't worry about the return names of operations.
For now, I've solved it by renaming the parameter from "idXML" to "DescargarInfoTiempo", so SOAP response will be "DescargarInfoTiempoReturn" and it will fit with the WSDL schema and PHP will map the response correctly.

PHP SoapClient sends escaped XML characters

I create an XML document using PHP's XMLWriter.
$xmlWriter = new \XMLWriter();
$xmlWriter->openMemory(); //generate XML in-memory, not on disk
//Please keep the indentation intact to preserve everybody's sanity!
$xmlWriter->startElement('RootElement');
// ...
$xmlWriter->endElement();
$myXml = $xmlWriter->outputMemory(true);
Now I connect to a SOAP service in non-WSDL mode.
$soapClient = new \SoapClient(null, array(
"location" => "https://theservice.com:1234/soap/",
"uri" => "http://www.namespace.com",
"trace" => 1
)
);
$params = array(
new \SoapParam($myXml, 'param')
);
$result = $soapClient->__soapCall('method', $params);
The problem is that the SOAP message received by the SOAP service contains my data as escaped XML characters. (Warning : dummy SOAP message ahead!)
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope" ...>
<SOAP-ENV:Header>
...
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:method>
<Request xsi:type="xsd:string">
<Root>
(escaped data)
</Root>
</Request>
</ns1:method>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The SOAP service will not work with escaped data but, on the other hand, I don't escape my data, the SoapClient does so. How do I make my SoapClient send unescaped data?
Escaping characters is the expected behavior of the XmlWriter::writeElement method.
It took me a while to figure it out but the answer was simple : put a SoapVar with the XSD_ANYXML parameter into the SoapParam :
$params = array(
new \SoapParam(new \SoapVar($myXml, XSD_ANYXML), 'param')
);
The SoapVar documentation mentions that its second parameter (encoding) can be "one of the XSD_... constants" which are not documented in the PHP docs.

Recreate a working SOAP request from SoapUI to PHP

I have created a soap client in PHP that signs on, but for the second request I want to make I cannot seem to get the PHP to structure the request properly.
This is request that works in SoapUI
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v1="http://v1.productapi.gs1ca.org" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header/>
<soapenv:Body>
<v1:searchProducts>
<sessionId>f53c5450-392e-4ca4-b592-adbb436cfe1f</sessionId>
<searchCriteria>
<v1:AttributeValue>
<v1:attribute>dateupdated</v1:attribute>
<v1:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">08/01/2013</v1:value>
</v1:AttributeValue>
<v1:AttributeValue>
<v1:attribute>dateupdatedcompare</v1:attribute>
<v1:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">1</v1:value>
</v1:AttributeValue>
</searchCriteria>
<includeImageAttributes>0</includeImageAttributes>
<sortOrder>dateupdated</sortOrder>
</v1:searchProducts>
</soapenv:Body>
</soapenv:Envelope>
How would I use PHP to format the XML the same way as the working request?
Some progress has been made.
I have been able to recreate the xml up to a point now. The request looks like this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://v1.productapi.gs1ca.org">
<SOAP-ENV:Body>
<ns1:searchProducts>
<sessionId>2a7d0294-8d96-428d-abd8-08add9cfc427</sessionId>
<searchCriteria>
<ns1:AttributeValue>
<ns1:attribute>dateupdated</ns1:attribute>
<ns1:value>01/01/2013</ns1:value>
</ns1:AttributeValue>
<ns1:AttributeValue>
<ns1:attribute>dateupdatedcompare</ns1:attribute>
<ns1:value>1</ns1:value>
</ns1:AttributeValue>
</searchCriteria>
<includeImageAttributes>false</includeImageAttributes>
<sortOrder>dateupdated</sortOrder>
</ns1:searchProducts>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The PHP creating that request is:
$args0 = array(
'sessionid'=>$session,
'searchcriteria'=> array(array('attribute'=>'dateupdated','value'=>'01/01/2013'),array('attribute'=>'dateupdatedcompare','value'=>'1')),
'includeimageattributes'=>0,
'sortorder'=>'dateupdated');
$result = $client->__soapCall('searchProducts',$args0);
The error this throws is:
Error: SoapFault exception: [a:DeserializationFailed] The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://v1.productapi.gs1ca.org:searchCriteria. The InnerException message was 'Element value from namespace http://v1.productapi.gs1ca.org cannot have child contents to be deserialized as an object. Please use XmlNode[] to deserialize this pattern of XML.
I am still missing a portion of the envelope:
xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
And I need the value tags to look like this:
<v1:value i:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">
Any ideas on how I can add those portions in?
Okay! I Finally figured this out. It is ugly but it works.
try {
$args = array(
'sessionid'=>$session,
'searchcriteria'=> new SoapVar('<searchCriteria><ns1:AttributeValue>
<ns1:attribute>dateupdated</ns1:attribute>
<ns1:value xsi:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">01/01/2013</ns1:value>
</ns1:AttributeValue>
<ns1:AttributeValue>
<ns1:attribute>dateupdatedcompare</ns1:attribute>
<ns1:value xsi:type="b:string" xmlns:b="http://www.w3.org/2001/XMLSchema">1</ns1:value>
</ns1:AttributeValue></searchCriteria>
', XSD_ANYXML, "http://www.w3.org/2001/XMLSchema-instance"),
'includeimageattributes'=>0,
'sortorder'=>'dateupdated');
$result = $client->__soapCall('searchProducts',$args);
} catch (SoapFault $e) {
echo "Error: {$e}";
}

SimpleXML Parsing Nested Namespaces - PHP

I am having a small problem parsing the tags in the nested namespace called "q0:", located below.
//Tag that I am trying to parse
<q0:CustomerTransactionId>
I am able to get to all of the "v12:" namespaced tags but not the "q0:" ones for some reason.
Thanks!
Here is the XML
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<soapenv:Body>
<v12:ProcessShipmentReply
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:v12="http://fedex.com/ws/ship/v12">
<v12:HighestSeverity>ERROR</v12:HighestSeverity>
<v12:Notifications>
<v12:Severity>ERROR</v12:Severity>
<v12:Source>ship</v12:Source>
<v12:Code>3058</v12:Code>
<v12:Message>Recipient Postal code or routing code is required</v12:Message>
<v12:LocalizedMessage>Recipient Postal code or routing code is required</v12:LocalizedMessage>
</v12:Notifications>
<q0:TransactionDetail xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:q0="http://fedex.com/ws/ship/v12">
<q0:CustomerTransactionId>21445634</q0:CustomerTransactionId>
</q0:TransactionDetail><q0:Version xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:q0="http://fedex.com/ws/ship/v12">
<q0:ServiceId>ship</q0:ServiceId>
<q0:Major>12</q0:Major>
<q0:Intermediate>0</q0:Intermediate>
<q0:Minor>0</q0:Minor>
</q0:Version>
</v12:ProcessShipmentReply>
</soapenv:Body>
</soapenv:Envelope>
And here is my parse
$xml = simplexml_load_string($result, NULL, NULL, 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->registerXPathNamespace('env', 'http://schemas.xmlsoap.org/soap/envelope/');
$xml->registerXPathNamespace('v12', 'http://fedex.com/ws/ship/v12');
$xml->registerXPathNamespace('q0', 'http://fedex.com/ws/ship/v12');
$bodies = $xml->xpath('env:Body');
foreach($bodies as $body){
$reply = $body->children('v12', TRUE)->ProcessShipmentReply;
$reply2 = $body->children('q0', TRUE)->TransactionDetail;
$custInfoArr['status'] = (string) $reply->HighestSeverity;
if(strtolower($custInfoArr['status'])=="error"){
$custInfoArr['invoiceNum'] = (string)$reply2->CustomerTransactionId;
$custInfoArr['notificationSeverity']= (string) $reply->Notifications->Severity;
$custInfoArr['notificationSource']= (string) $reply->Notifications->Source;
$custInfoArr['notificationCode']= (string) $reply->Notifications->Code;
$custInfoArr['notificationMessage']= (string) $reply->Notifications->Message;
$custInfoArr['notificationLocalizedMessage']= (string) $reply->Notifications->LocalizedMessage;
}
$custInfoArr['trackingCode'] = (string) $reply->CompletedShipmentDetail->CompletedPackageDetails->TrackingIds->TrackingNumber;
$custInfoArr['labelCode'] = (string) $reply->CompletedShipmentDetail->CompletedPackageDetails->Label->Parts->Image;
$custInfoArr['labelCode'] = base64_decode($custInfoArr['labelCode']);
}
<q0:TransactionDetail> is not a child of <env:Body>, it is a child of <v12:ProcessShipmentReply>, so you need to look for it inside $reply, not $body: $reply2 = $reply->children('q0', TRUE)->TransactionDetail;
The important thing to remember is that both the ->TagName operator and the ->children() method only look at immediate children of a particular node, they are not doing a "deep search" like XPath.
In fact, looking, both v12: and q0: are aliases for the same namespace ('http://fedex.com/ws/ship/v12'), so the next line can just be $reply2 = $reply->TransactionDetail. If you just say $reply = $body->children('http://fedex.com/ws/ship/v12')->ProcessShipmentReply rather than relying on the alias, this becomes clearer (and safer, since those aliases may change).
Incidentally, unless you're using it for something else, you can also get rid of all your XPath code (including all of the registerXPathNamespace calls) and just write $body = $xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body (I'm pretty sure SOAP only allows one Body per message).

Categories