Switching from NuSoap to PHP5 Soap - need a jumpstart - php

Here's the call I'm trying to make:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<urn:SessionHeader xmlns:urn="http://www.mywebservice.com/webservices/SoapService" xmlns="http://www.mywebservice.com/webservices/SoapService" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<urn:sessionId xmlns:urn="http://www.mywebservice.com/webservices/SoapService">LOGINTOKEN=your instance name</urn:sessionId>
</urn:SessionHeader>
</soap:Header>
<soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<ns2:login xmlns:ns2="http://www.mywebservice.com/webservices/SoapService">
<wsUser>
<entityId>0</entityId>
<password>your password</password>
<username>your username</username>
</wsUser>
</ns2:login>
</soap:Body>
But I'm having trouble finding out how to set up the custom headers in PHP5's Soap. With nuSoap I could just put the whole thing into a variable and then use $client->setHeader($headerVar) but I can't find anything similar in PHP. If I could replicate this one call, I can figure the rest out. Any help would be appreciated!
Thanks in advance!
Update: I've gone through tutorial after tutorial, and read the PHP docs, but nothing seems to work. I can do what I want with curl (as well as nuSoap) but I thought the native PHP5 Soap would be easier and possibly more stable. I guess not...
Update 2
Here's the code I'm trying:
$soapurl = 'http://www.mywebservice.com/webservices/SoapService?wsdl';
$client = new SoapClient($soapurl,array('trace'=>true));
$token = "LOGINTOKEN=your instance name";
$header = new SoapHeader('http://www.mywebservice.com/webservices/SoapService', 'SessionHeader', array('sessionId' => $token));
$client->__setSoapHeaders($header);
$client->login(array("wsUser" => array('entityId'=>'0','username'=>'my username','password'=>'my password')));
And the error I get:
**Fatal error**: Uncaught SoapFault exception: [ns1:InvalidSecurity] An error was discovered processing the <wsse:Security> header in C:\www\soap\index.php:12 Stack trace: #0 C:\www\soap\index.php(12): SoapClient->__call('login', Array) #1 C:\www\soap\index.php(12): SoapClient->login(Array) #2 {main} thrown in C:\www\soap\index.php on line 12
Update 3
So it looks like the "sessionId" is being sent as "key" with the token sent as "value".
*REQUEST*:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.mywebservice.com/webservices/SoapService">
<SOAP-ENV:Header>
<ns1:SessionHeader><item><key>sessionId</key><value>LOGINTOKEN=my token</value></item>
</ns1:SessionHeader>
</SOAP-ENV:Header>
<SOAP-ENV:Body><ns1:login><wsUser><entityId>0</entityId><password>my password</password><username>my username</username></wsUser></ns1:login></SOAP-ENV:Body></SOAP-ENV:Envelope>

Have you tried the SoapHeader class to construct your header? Would something like this work?
//Assume $token holds your login token and $client holds the SoapClient object
$header = new SoapHeader('http://www.mywebservice.com/webservices/SoapService', 'SessionHeader', array('sessionId' => $token));
$client->__setSoapHeaders($header);
That should create the header and add it to the SoapClient. Every subsequent call using the SoapClient will then have that header set. As to the exact format, I wouldn't worry too much. You sample XML uses an alias for the namespace called urn. PHP probably won't arrive at exactly the same alias but it should still work. Also I don't think that declaring the xmlns in every child element is needed. I think that a child node automatically inherits the namespace of its parent in XML, but I'm not 100% certain on that. The bottom line is that as long as the right URL's are declared in the namespaces it should be fine even if the XML doesn't exactly match your example.
One other thing you could try-have you switched on tracing in the SoapClient? This is one of the parameters that can be passed to the constructor and it enables you to view the XML SOAP requests and responses. If it still doesn't work using the SoapHeader class try switching tracing on to see what's being sent and received.

Related

How does it work with the magic WSDL URI query parameter?

I'm building a Soap server within a Symfony application. As first step I created a controller with my "hello world" Soap action and defined the route for it:
routing.yml
api.soap.foo
path: /soapapi/foo
defaults: { _controller: SoapBundle\Controller\FooController:bar }
methods: [GET, HEAD, POST]
FooController#bar(...)
protected function bar(Request $request)
{
$autodiscover = new AutoDiscover();
$autodiscover
->setClass(MyFooBarService::class)
->setUri('http://my-app.loc/soapapi/foo/bar')
->setServiceName('MyFooBarService')
;
$wsdl = $autodiscover->generate();
$wsdl->dump(__DIR__ . '/soapapi-foo-bar.wsdl');
$server = new SoapServer(__DIR__ . '/soapapi-foo-bar.wsdl');
$server->setObject($this->myFooBarService);
$response = new Response();
$response->headers->set('Content-Type', 'text/xml; charset=ISO-8859-1');
ob_start();
$server->handle();
$response->setContent(ob_get_clean());
return $response;
}
Now, when I call http://my-app.loc/soapapi/foo/bar in a browser or using cURL (so via HTTP GET), I get an error:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>Bad Request</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
But when I call http://my-app.loc/soapapi/foo/bar?wsdl, I actually get the (generated) WSDL document. Why? I have not defined anywhere, that it should work like this. Why and how does this (magic) work? Is it Symfony specific magic?
This is a great question.
No this is not Symfony specific, it's a behavior of the built-in SOAP server in PHP. When the endpoint URL is accessed with ?wsdl appended, the SOAP server will respond with the wsdl document that it was instantiated with in the constructor:
$server = new SoapServer(__DIR__ . '/soapapi-foo-bar.wsdl');
I haven't been able to find where this behavior is documented on the PHP website, but it clearly exists and is reproducible.
The code for the feature can be found in PHP's source code starting on line 1369 and ending on line 1396. The code checks if the request method is GET and checks for the presence of a 'wsdl' query parameter.

Make and Display SOAP Request via PHP

I want to learn how to make a SOAP request and then post response in PHP. Here is my request code: http://pastebin.com/PBb2i0XN
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://www.restfulwebservices.net/ServiceContracts/2008/01">
<SOAP-ENV:Body>
<ns1:GetStockQuote>
<ns1:request>Goog</ns1:request>
</ns1:GetStockQuote>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
WSDL is http://www.restfulwebservices.net/wcf/StockQuoteService.svc?wsdl
PHP has a wrapper for SOAP. Check out http://php.net/soap. This should solve all your problems.
You first instantiate a SoapClient class using the following syntax.
$client = new SoapClient("some.wsdl");
You can then use the SoapClient::__getFunctions() method to determine the different functions your WSDL gives you.
public array SoapClient::__getFunctions ( void )
This client will enable you to call the soap functions from your WSDL as if they were PHP functions
For example in order to call the GetStockQuote method from your WSDL, all you have to do is the following.
$client = new SoapClient("http://www.restfulwebservices.net/wcf/StockQuoteService.svc?wsdl");
$client->GetStockQuote();
Also when you use the getFunctions method, you will get a list of all parameters each method in the WSDL expects.
Hope this was helpful. All the best.

PHP SoapClient - Possible name spacing issues - non-wsdl mode

I previously managed to call a .NET service using PHP's SoapClient library without too many issues, but now I face a scenario where I have to access the same services in non-WSDL mode.
I seem to have created, more or less, the correct XML structure to send through to the service, but the server does not seem to be picking up the variables I am sending.
Based on output from SOAPUI, I should be attempting to generate the following SOAP call:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:int="http://someservices.com/">
<soapenv:Header/>
<soapenv:Body>
<int:getService>
<int:foo>String value for foo</int:foo>
<int:bar>String value for bar</int:bar>
</int:getService>
</soapenv:Body>
</soapenv:Envelope>
The output that I am generating with SOAPClient is:
<?xml version="1.0" encoding="UTF-8"?> <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://someservices.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
<env:Body>
<ns1:getService env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
<foo xsi:type="xsd:string">String value for foo</foo>
<bar xsi:type="xsd:string">String value for bar</bar>
</ns1:getService>
</env:Body>
</env:Envelope>
The error returned from the .NET service is "bar expected!" - indicating, at the very least, that the service is not picking up my second parameter.
I am constructing my soap call using the following:
$foo = 'String value for foo';
$bar = 'String value for bar';
$options = array(
'trace' => 1,
'exceptions' => 1,
'uri' => 'http://someservices.com',
'soapaction' => 'http://someservices.com/getService',
'soap_version' => SOAP_1_2,
'type_map' => array('type_ns' => 'int')
);
$response = $soapclient->__soapCall('getService', array( new SoapParam($foo, 'foo'), new SoapParam($bar, 'bar')), $options);
I am not sure what the issue is. Right now I am guessing that it might be an xmlns attribute issue. Note that the SOAPUI call specifies the int: in front of the function calls and parameters. I have tried to specify this xmlns but without luck. As you can see xmlns generated by Soap Client call is:
xmlns:ns1
Any help would be greatly appreciated.
As it turns out, having the namespace labelled as ns1 instead of int was not the issue.
The problem was that the getService parameter was being prefixed with the namespace ns1, whilst the variables foo and bar were not.
As such, this was resolved by appending "ns1:" to the Soap Params in the soapCall function, as follows:
$response = $soapclient->__soapCall('getService', array( new SoapParam($foo, 'ns1:foo'), new SoapParam($bar, 'ns1:bar')), $options);

How to add the namespace to all elements that get returned WSDL / SOAP

I'm fairly new to the SOAP and WSDL world. What do I have to do, to make sure that the namespace will always be in the return-element?
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://someurl.com">
<SOAP-ENV:Body>
<ns1:sayHelloResponse>
<return>Say Hello Kelvin</return>
</ns1:sayHelloResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
What I want is:
<ns1:sayHelloResponse>
<ns1:return>Say Hello Kelvin</ns1:return>
</ns1:sayHelloResponse>
I'm using PHP and the Zend Framework. The WSDL gets generated by Zend_Soap_AutoDiscovery. This shouldn't be a barrier though, because I will modify the output of it anyways.
Thanks for your help.
After taking a break for a while, I approached the problem once again.
This time though, I stumbled upon this nice article (credits to my former co-worker Mike). If the attribute elementFormDefault isn't declared in the schema tag, unqualified is assumed as its value. By giving it the value "qualified" the following will happen:
But if you add elementFormDefault="qualified" to all of the schemas in the document/literal wrapped WSDL, then all elements in the messages would be qualified with their parent's namespace.
In general, you do not want to use elementFormDefault="qualified"
because it bloats the messages, but a year or more ago there were
interoperability issues between various vendors, and setting this
attribute sometimes fixed the problems.
Even though I wasn't using document/literal, I wanted to try it out. I added the attribute to my schema tag and made a request to a different call. Here's the response I got:
<ns1:getUserResponse>
<return>
<ns1:firstname>First</ns1:firstname>
<ns1:lastname>Last</ns1:lastname>
</return>
</ns1:getUserResponse>
As you can see, the child elements of the "return"-element got the namespace prefixed.
At this point I got really excited, because I finally got closer to where I wanted to be.
Unfortunately, the return element didn't have the namespace prefixed. I tried the earlier call (see question post) again, but the response was the same as before.
I couldn't spend more time on this issue. It was just a prototype after all.
That's why I decided to hook into Zend_Soap_Server's handle function, to modify the response before outputting it.
class Custom_Soap_Server extends Zend_Soap_Server
{
public function __construct($wsdl = null, array $options = null)
{
parent::__construct($wsdl, $options);
// Response of handle will always be returned
$this->setReturnResponse(true);
}
public function handle($request = null)
{
$response = parent::handle($request);
echo str_replace(array('<return>', '</return>'), array('<ns1:return>', '</ns1:return>'), $response);
return;
}
}
To be honest, it's a nasty hack. I'm always assuming that there's just one namespace. The replace function could be written much better. But it was for a prototype after all and this was my first thought to make it work.
After using the new custom class instead of Zend_Soap_Server, all of the return elements had ns1 prefixed to them.

PHP method input parameters

I don't know PHP, just the basics (if so...).
I have a method in my web service like this:
$server->register(
'registerDeviceOnServer', //method name
array('uniqueIdentifier' => 'xsd:string','deviceName' => 'xsd:string', 'systemVersion' => 'xsd:string', 'deviceModel' => 'xsd:string', 'userLocation_Locality' => 'xsd:string', 'userLocation_CountryCode' => 'xsd:string' ), //input data
array('xmlReturn' => 'xsd:string'), //output data
'urn:server.AAAA', //namespace
'urn:server.AAAA#registerDeviceOnServer', //soapaction
'rpc', //style
'encoded', //use
'regista o iDevice' //info for documentation
);
function registerDeviceOnServer($uniqueIdentifier,$deviceName,$systemVersion,$deviceModel,$userLocation_Locality,$userLocation_CountryCode)
{
//some code that talks to the database
//inserting the data into a table
//
//the SQL code works fine. I've tested.
}
When I call the web service, the data does not come in that exact order as it is declared in the php method. Does it have to come in that order, or does PHP assign the data to the vars?
SOAP request:
<?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>
<registerDeviceOnServer xmlns="urn:server.AAAA">
<uniqueIdentifier>VVVVVVVVVVVVWWWWWWWW</uniqueIdentifier>
<userLocation_CountryCode>n/a</userLocation_CountryCode><deviceModel>iPhone Simulator</deviceModel><systemVersion>4.0</systemVersion><deviceName>iPhone OS</deviceName><userLocation_Locality>n/a</userLocation_Locality>
</registerDeviceOnServer>
</soap:Body>
</soap:Envelope>
So, how can I work this out?
Thanks,
RL
From your comments I can now see that you are actually using nusoap.php and not PHP's own SoapServer.
Check this forum post:
I had a similar issue recently which was caused because I didn't
provide a WSDL url to my SoapServer constructor.
Apparently the soap server can only map message arguments / parameters
when it has a valid WSDL for reference (which more or less made sense
to me afterwards).
Does this help you?
As #akirk says, maybe your problem is related to the WSDL. Make sure that the parameters are in the same order that's in the WSDL

Categories