Currently I am working on WCF Service that is using WSHttpBinding. So far the service works great with .NET applications. However when it comes to using this service in PHP it throws me an error. The error is caused because PHP sends null as the parameter to the WCF services.
The service contract looks as follows:
[ServiceContract]
public interface IWebsite : IWcfSvc
{
[OperationContract]
[FaultContract(typeof(ServiceException))]
ResponseResult LostPassword(RequestLostPassword request);
}
The data contract that is used for the parameters looks like:
[DataContract]
public class RequestLostPassword
{
[DataMember(IsRequired = true)]
public string Email { get; set; }
[DataMember(IsRequired = true)]
public string NewPassword { get; set; }
[DataMember(IsRequired = true)]
public string CardNumber { get; set; }
[DataMember(IsRequired = true)]
public DateTime RequestStart { get; set; }
}
Since I am not an expert, it took me a while to get the PHP code working, but I end up writing a script like this:
$parameters = array(
'Email' => "user#test.com",
'NewPassword' => "test",
'CardNumber' => "1234567890",
'RequestStart' => date('c')
);
$svc = 'Website';
$port = '10007';
$func = 'LostPassword';
$url = 'http://xxx.xxx.xxx.xxx:'.$port.'/'.$svc;
$client = #new SoapClient(
$url."?wsdl",
array(
'soap_version' => SOAP_1_2,
'encoding'=>'ISO-8859-1',
'exceptions' => true,
'trace' => true,
'connection_timeout' => 120
)
);
$actionHeader[] = new SoapHeader(
'http://www.w3.org/2005/08/addressing',
'Action',
'http://tempuri.org/I'.$svc.'/'.$func,
true
);
$actionHeader[] = new SoapHeader(
'http://www.w3.org/2005/08/addressing',
'To',
$url,
true
);
$client->__setSoapHeaders($actionHeader);
$result = $client->__soapCall($func, array('parameters' => $parameters));
What I don't understand is why it is not passing the parameters to the WCF service. I have another service that works just fine although that does not require parameters. Can someone explain why this happens? I am a complete PHP noob and just want to get this to work as an example for the guy who is developing the website.
We found the answer!
The line of code below:
$result = $client->__soapCall($func, array('parameters' => $parameters));
Should be changed to:
$result = $client->__soapCall($func, array('parameters' => array('request' => $parameters)));
Apparently you need to tell PHP that your parameters are nested in an array called 'request', which is nested in an array called parameters, when you want to call a WCF service with a datacontract as a request object.
Related
I am trying to get data from two soap apis here is the code for first api that is working fine.
public static function data_list()
{
ini_set('soap.wsdl_cache_enabled', 0);
ini_set('soap.wsdl_cache_ttl', 0);
$host = $host = \Drupal::request()->getSchemeAndHttpHost();
$wsdl = $host . "/modules/custom/upm_api_data/connect/empprof.wsdl";
$client = new \SoapClient($wsdl);
$params = array('ALL' => 'ALL');
$response = $client->__soapCall("ZCIG_FACULTY_STAFF_PROFILE", array($params));
return $response;
}
And there is another api that is showing:
SoapFault: Could not connect to host in SoapClient->__doRequest()
error
public static function images_api()
{
ini_set('soap.wsdl_cache_enabled',0);
ini_set('soap.wsdl_cache_ttl',0);
$host = $host = \Drupal::request()->getSchemeAndHttpHost();
$wsdl = $host . "/modules/custom/upm_api_data/connect/ImageRac.WSDL";
$client = new \SoapClient($wsdl);
$parameters = array('P_PERNR' => "00006629");
$response = $client->__soapCall("ZCIG_IMAGE_WS", array($parameters));
return $response;
}
Second api is working fine in Drupal 7 Php 5.6 but similer code is not working in php 7.3 Drupal 8
I have tried almost every thing related to this issue on internet
like adding this in options
array(
'stream_context'=> stream_context_create(
array(
'ssl'=> array(
'verify_peer'=>false,'verify_peer_name'=>false
)
)
)
)
or
array(
'trace' => 1,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'stream_context' => stream_context_create(
array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
)
)
);
But not luck I have spent many days on it and I am new to Soap. Can you guys please check what I am doing wrong.
Thank you for everyone in advance :)
I have a php class that uses guzzle to call an API and get a response:
public function getResponseToken()
{
$response = $this->myGUzzleClient->request(
'POST',
'/token.php,
[
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded'
],
'form_params' => [
'username' => $this->params['username'],
'password' => $this->params['password'],
]
]
);
return json_decode($response->getBody()->getContents())->token;
}
I am trying to test this method using guzzle mock handler, this is what I have done so far but not working:
public function testGetResponseToken()
{
$token = 'stringtoken12345stringtoken12345stringtoken12345';
$mockHandler = new MockHandler([
new Response(200, ['X-Foo' => 'Bar'], $token)
]
);
$handlerStack = HandlerStack::create($mockHandler);
$client = new Client(['handler' => $handlerStack]);
$myService = new MyService(
new Logger('testLogger'),
$client,
$this->config
);
$this->assertEquals($token, $myService->getResponseToken());
}
the error I am getting says "Trying to get property of non-object", so looks to me MyService is not using the handler to make the call. What am I doing wrong?
The class works as expected outside of the test context. Also note the client in normally injected in MyService from service.yml (I am using symfony).
Your handler work fine, you just mock the wrong response data. You should make the response as raw json.
Try
$token = 'stringtoken12345stringtoken12345stringtoken12345';
$mockHandler = new MockHandler(
[
new Response(200, ['X-Foo' => 'Bar'], \json_encode([ 'token' => $token ]))
]
);
Now it should be works
I'm writing integration tests for a SOAP API (code example s. below).
Now I got an error and want to debug my server-side code (in PhpStorm). But the debugger considers only the breakpoints in the test and ignores the server-side code.
OK, I probably roughly understand why: The call of the $soapClient->doSomething(...); starts a new HTTP request. How to get this "sub-request" (from the point of view of PhpUnit) debugged?
Integration test's code:
class UserIntegrationTest extends TestCaseBase
{
const URL = "http://my-server.loc/soapapi/user/wsdl";
public static $classMap = [];
/** #var SoapClient */
private $soapClient;
/** #var ConfigurationServiceInterface */
private $config;
public function setUp()
{
parent::setUp();
$options = [
'exceptions' => true,
'login' => 'foo',
'password' => 'pwd',
'encoding' => 'utf-8',
// 'proxy_host' => '192.168.2.96',
// 'proxy_port' => '8080',
'classmap' => [],
'connection_timeout' => 5,
];
$this->soapClient = new SoapClient(self::URL, $options);
}
/**
* #test
* #group integration
*/
public function testDoSomething()
{
$options = array(
'exceptions' => true,
'login' => 'foo',
'password' => 'pwd',
'encoding' => 'utf-8',
// 'proxy_host' => '192.168.2.96',
// 'proxy_port' => '8080',
'classmap' => [],
'connection_timeout' => 5,
);
$soapClient = new SoapClient(self::URL, $options);
$message = new MyMessage();
$message->x = 1;
$message->y = 2;
$result = $soapClient->doSomething($message);
}
protected function getDataSet()
{
return new ArrayDataSet([
'users' => [
[
'id' => 1,
'username' => 'foo',
'password' => '...',
],
],
...
]);
}
}
The solution is to append ?XDEBUG_SESSION_START=ECLIPSE_DBGP to the URI called by the SOAP client. So in the port segment of the WSDL
<port name="UserPort" binding="tns:UserBinding">
<soap:address location="http://my-server.loc/soapapi/user"/>
</port>
the location needs to be extended by the Xdebug query and look like this:
http://my-server.loc/soapapi/user?XDEBUG_SESSION_START=ECLIPSE_DBGP
Sure, the productive WSDL should not contain such location. But WSDLs are usually generated (e.g. by Zend\Soap\AutoDiscover) and the URI can be easily made dynamically configurable.
You can do this by setting the XDEBUG_SESSION cookie in your client application.
I believe this is a better alternative than the accepted answer, as it does not require changing the server-side code (WSDL).
For example, using PHP SoapClient as per the OP's code sample
$soapClient = new SoapClient(self::URL, $options);
$soapClient->__setCookie('XDEBUG_SESSION', 'PHPSTORM');
Then in PhpStorm, Start listening for PHP Debug Connections and run your test script.
$client = new SoapClient("http://soap.m4u.com.au/?wsdl", array("trace" => 1));
$params = array(
"authentication" => array(
"userId" => "user",
"password" => "pass"
),
"requestBody" => array(
"messages" => array(
"message" => array(
"recipients" => array( "recipient" => array("1" => "9799996899") ),
"content" => "Message Content"
)
)
)
);
$response = $client->__soapCall("sendMessages", array($params));
But I am getting following error
Fatal error: Uncaught SoapFault exception: [SOAP-ENV:Client] The request is either not well-formed or is not valid against the relevant schema.
I need the request format like below.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://xml.m4u.com.au/2009">
<soapenv:Header/>
<soapenv:Body>
<ns:sendMessages>
<ns:authentication>
<ns:userId>Username</ns:userId>
<ns:password>Password</ns:password>
</ns:authentication>
<ns:requestBody>
<ns:messages>
<ns:message format="SMS" sequenceNumber="1">
<ns:recipients>
<ns:recipient uid="1">61400000001</ns:recipient>
<ns:recipient uid="2">61400000002</ns:recipient>
</ns:recipients>
<ns:content>Message Content</ns:content>
</ns:message>
</ns:messages>
</ns:requestBody>
</ns:sendMessages>
</soapenv:Body>
</soapenv:Envelope>
How I can achieve this, please help.
You've got a tricky one there.
I haven't got the time to solve it fully, but it looks like the format of the WSDL means that SoapClient MUST provide an object structure.
I can't think off the top of my head how to represent the multiple node entries without using arrays (which see to then get decomposed before being sent)
I know it's not a complete solution, but I think this is moving along the right lines (as a proof of concept)
Note the use of __getLastRequest against the SoapClient to retrieve exactly what you are sending to the server. It's very useful...
<?php
class authentication {
public $userId;
public $password;
function __construct( $userId, $password ) {
$this->userId = $userId;
$this->password = $password;
}
}
class message {
public $recipients;
public $content;
function __construct( $recipients, $content ) {
$this->recipients = $recipients;
$this->content = $content;
}
}
class requestBody {
public $messages;
function __construct( $messages ) {
$this->messages = $messages;
}
}
$client = new SoapClient("http://soap.m4u.com.au/?wsdl", array("trace" => 1));
$params = array(
"authentication" => new authentication( 'user', 'pass' ),
"requestBody" => new requestBody( array( new message( array( '1' => '61400000001' )
, 'message content' )
)
)
);
try {
$response = $client->sendMessages( $params );
} catch(SoapFault $e) {
file_put_contents( 'request.xml', $client->__getLastRequest() );
throw $e;
}
?>
I think your best bet is to get in touch with the service supplier to find if anyone else has connected via PHP.
Sorry I can't be of more help - I hope this is useful!
Below is my test code to access webservice using SOAP, The services are developed using .NET .
when executed the code I am getting exception
File:
...\vendor\zendframework\zendframework\library\Zend\Soap\Client\DotNet.php:199
Message:
*.Net webservice arguments have to be grouped into array: array("a" => $a, "b" => $b, ...).*
public function testAction()
{
$client = new DotNet(
'http://ip/Services/SomeService.svc?wsdl',
array(
'encoding' => 'UTF-8',
'soap_version' => SOAP_1_2
)
);
//var_dump($client->getFunctions());
//var_dump($client->getOptions());
var_dump($client->call('ValidateUser',array('customercode'=>'Cust','username'=>'Admin','passwork'=>'Admin')));
die
}
The issue was with parameter passing
redefine the code as follows
var_dump($client->call('ValidateUser',array(array('customercode'=>'Cust','username'=>'Admin','passwork'=>'Admin'))));