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.
Related
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.
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
So I have soap connection to some services. The App I'm creating is fully object oriented. The data for messages I'm sending are therefore in PHP in stdClass objects (it is extended by my Classes - representants of data in future xml document with predeclared public $variables). Everything is fine as long as I have to create documents like:
<Request>
<paramsList>
<someParam></someParam>
<someParam></someParam>
<someParam></someParam>
<someParam></someParam>
</paramsList>
<moreData></moreData>
</Request>
Now the problem is to create structure like:
<Request>
<paramsList>
<someParam id="3"></someParam>
<someParam id="4"></someParam>
<someParam id="18"></someParam>
<someParam id="512"></someParam>
</paramsList>
<moreData isImportant="false"></moreData>
</Request>
Is it possible (and if - how?) to add these things to stdObject so I don't have to change execution of SOAP functions from this simple form:
$client->functionToExecute($stdObjectOrientedData);
Example object and usage:
class Request extends \stdClass
{
public $paramsList;
public $moreData;
}
$objectToSend = new \stdObject();
$request = new Request();
$request->paramsList = $someParamsObjectsArray;
$request->moreData = $someData;
$objectToSend->Request = $request;
$client->functionToExecute($objectToSend);
In case someone visit this question, here is the answer.
If WSDL file is created properly, PHP will generate response with using normal StdClass objects. just name properties as they are named in request.
If WSDL has errors, only thing you can do is to make requests manualy from templates.
I'm trying to connect to a WSDL webservice using PHP SOAP. Now I need to remove the SOAP elements from the XML which is send. What I have now is the following:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://service.com">
<SOAP-ENV:Body>
<ns1:Calculate>
//Content
</ns1:Calculate>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
What I want is the following:
<?xml version="1.0" encoding="UTF-8"?>
<ns1:Calculate xmlns:ns1="http://service.com">
//Content
</ns1:Calculate>
Is there any way to accomplish this? Is SOAP the one I need to send such requests?
SOAP is protocol, and if you want to SOAP request both client and server side must accept and send back requests according to this protocol.
Just check these settings :
$client = new SoapClient("some.wsdl", array('soap_version' => SOAP_1_2));
Server may uses 1_2 or 1_1 version.
And you should contact with Web Service provider to get documentation about implementation of web service.
Override SoapClient class with a customized __doRequest. The new method removes the namespace references from the XML then calls the parent's __doRequest to fulfill the request with the modified XML. A simple but effective solution.
class MySoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way=0)
{
$request = str_replace('ns1:', '', $request);
parent::__doRequest($request, $location, $action, $version, $one_way);
}
}
# Invoke derived class instead of base class SoapClient
$client = new MySoapClient($wsdl, $options);
If you want to make a SOAP request, these tags are required. Is it really SOAP server on your opposite trying to make request?
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.