How to debug a SOAP call? - php

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.

Related

How to pass Guzzle Mock Handler to a PHP class to test an API call that has json response

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

App Engine PHP application failed to create SoapClient

I have a PHP application running on Google App Engine. I'm trying to make a SOAP 1.2 Web Service call from the PHP client to a remote host. I get the following error when creating the SoapClient using the code below:
$opts = array(
'https'=>array('user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36'),
'ssl' => array('verify_peer'=>false, 'verify_peer_name' => false)
);
$context = stream_context_create($opts);
$params = array (
'encoding' => 'UTF-8',
'verifypeer' => false,
'verifyhost' => false,
'soap_version' => SOAP_1_2,
'trace' => 1,
'exceptions' => 1,
'connection_timeout' => 30,
'stream_context' => $context,
'cache_wsdl' => WSDL_CACHE_MEMORY
);
libxml_disable_entity_loader(false);
$client = new \SoapClient("https://<host_ip_address>/webservice.asmx?wsdl", $params);
The error that I get is:
ERROR: SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from '<host_ip_address>/webservice.asmx?wsdl' : failed to load external entity "<host_ip_address>/webservice.asmx?wsdl"
I have confirmed that the following modules have been loaded:
php --info
Soap Client => enabled
...
XML Support => active
...
OpenSSL support => enabled
...
And my php.ini file in the root folder of the app contains:
google_app_engine.enable_functions = "libxml_disable_entity_loader"
extension = “curl.so”
My GAE project also has billing enabled. Any advise on what I can do to successfully create a SoapClient is much appreciated. I am able to connect to the web service via CURL and SoapUI so I assume that there is nothing wrong with the web service.
Try this:
<?php
declare(strict_types=1); // PHP 7.1
class SoapAgent extends \SoapClient
{
protected const WSDL_PATH = '/webservice.asmx?wsdl';
protected const USER_AGENT = 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)';
protected const SOAP_TIMEOUT = 300; // Increase this value if SOAP timeouts occur.
// options for ssl for unverified SSL certs
// allow_self_signed certs
protected const CTX_OPTS =
[
'http' => ['user_agent' => self::USER_AGENT],
'ssl' =>
[
'verify_peer' => false, // Do not verify the cert
'verify_peer_name' => false, // Or the name of the cert
'allow_self_signed' => true // Allow self signed certs
]
];
public function __construct(string $uri)
{
assert(func_num_args() === 1);
/**
* Workaround for a particularly nasty bug that 'sometimes'
* creates a racing condition internally
* for SOAP client constructors that use OpenSSL in Ubuntu.
*
* #link: https://bugs.launchpad.net/ubuntu/+source/php5/+bug/1160336
*
*/
libxml_disable_entity_loader(false);
// Get the SOAP stream context
$ctx = stream_context_create(self::CTX_OPTS);
// SOAP 1.2 client options
$options =
[
'Content-Type' => 'application/soap+xml;charset=UTF-8',
'encoding' => 'UTF-8',
'verifypeer' => false,
'verifyhost' => false,
'soap_version' => SOAP_1_2,
'features' => SOAP_WAIT_ONE_WAY_CALLS | SOAP_SINGLE_ELEMENT_ARRAYS,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP | SOAP_COMPRESSION_DEFLATE,
'trace' => true,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'cache_wsdl_ttl' => 0,
'connection_timeout' => self::SOAP_TIMEOUT,
'stream_context' => $ctx
];
// Even though we pass these in as option settings (above) -- sometimes
// they are being ignored so we force this into the INI settings as well.
ini_set('soap.wsdl_cache_enabled', (string)$options['cache_wsdl']);
ini_set('soap.wsdl_cache_ttl', (string)$options['cache_wsdl_ttl']);
ini_set('default_socket_timeout', (string)$options['connection_timeout']);
// Set the file path for where we are going to store the WSDL.
$wsdlFile = sys_get_temp_dir() . '/' . $uri . '.wsdl';
// Format the uri by affixing `https://` to the uri
$uri = "https://{$uri}/" . self::WSDL_PATH;
// We perform our own WSDL caching in case there are errors we can actually look at the file itself.
$wsdlXML = null;
// Does the WSDL file already exist?
if (!file_exists($wsdlFile)) {
// Download the WSDL for the URI provided
$wsdlXML = file_get_contents($uri, false, $options['stream_context']);
// If we had trouble getting the WSDL then the URI from the user is probably bad.
if (!isset($wsdlXML) || $wsdlXML === false || !is_string($wsdlXML)) {
new \Exception('Unable to load WSDL from: '. $uri);
}
// Save the WSDL file.
file_put_contents($wsdlFile, $wsdlXML);
}
// Call the PHP internal constructor for this using the downloaded file
// to perform the actual WDSL configuration.
parent::__construct($wsdlFile, $options);
}
}
You should be able to do this:
$soapClient = new SoapAgent('<host_ip_address>');
Note: The code is PHP 7.1 but should be easy enough to thunk down to a different version.

How do I configure default query parameters with Guzzle 6?

Migrating from 5 to 6, and I've run into a snag and can't find the relevant docs.
Guzzle docs here, http://guzzle.readthedocs.io/en/latest/quickstart.html#creating-a-client, site that we can add "any number of default request options".
I want to send "foo=bar" with every request. E.g.:
$client = new Client([
'base_uri' => 'http://google.com',
]);
$client->get('this/that.json', [
'query' => [ 'a' => 'b' ],
]);
This will generate GET on http://google.com/this/that.json?a=b
How do I modify the client construction so that it yields:
http://google.com/this/that.json?foo=bar&a=b
Thanks for your help!
Alright, so far, this works here:
$extraParams = [
'a' => $config['a'],
'b' => $config['b'],
];
$handler = HandlerStack::create();
$handler->push(Middleware::mapRequest(function (RequestInterface $request) use ($extraParams) {
$uri = $request->getUri();
$uri .= ( $uri ? '&' : '' );
$uri .= http_build_query( $extraParams );
return new Request(
$request->getMethod(),
$uri,
$request->getHeaders(),
$request->getBody(),
$request->getProtocolVersion()
);
}));
$this->client = new Client([
'base_uri' => $url,
'handler' => $handler,
'exceptions' => false,
]);
If anyone knows how to make it less sinister-looking, I would say thank you!
I found a nice solution here.
Basically, anything defined in the first array of arguments, become part of the config for the client.
this means you can do this when initialising:
$client = new Client([
'base_uri' => 'http://google.com',
// can be called anything but defaults works well
'defaults' => [
'query' => [
'foo' => 'bar',
]
]
]);
Then, when using the client:
$options = [
'query' => [
'nonDefault' => 'baz',
]
];
// merge non default options with default ones
$options = array_merge_recursive($options, $client->getConfig('defaults'));
$guzzleResponse = $client->get('this/that.json', $options);
It's woth noting that the array_merge_recursive function appends to nested arrays rather than overwrites. If you plan on changing a default value, you'll need a different utility function. It works nicely when the default values are immutable though.
A "less sinister-looking" example based on the answer by #Saeven and the comment from #VladimirPak.
$query_defaults = [
'a' => $config['a'],
'b' => $config['b'],
];
$handler = \GuzzleHttp\HandlerStack::create();
$handler->push(\GuzzleHttp\Middleware::mapRequest(function (\Psr\Http\Message\RequestInterface $request) use ($query_defaults) {
$query = \GuzzleHttp\Psr7\Query::parse($request->getUri()->getQuery());
$query = array_merge($query_defaults, $query);
return $request->withUri($request->getUri()->withQuery(\GuzzleHttp\Psr7\Query::build($query)));
}));
$this->client = new \GuzzleHttp\Client([
'base_uri' => $url,
'handler' => $handler,
'exceptions' => false,
]);
I'm not sure how less sinister-looking it is though. lol
the solution proposed in github looks pretty ugly. This does not look much better, but at least is more readable and also works. I'd like feedback if anyone knows why should not be used:
$query = $uri . '/person/id?personid=' . $personid . '&name=' . $name;
return $result = $this->client->get(
$query
)
->getBody()->getContents();

PHP calling WCF service using WSHttpBinding

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.

Enabling BjyProfiler module in zend framework 2

I just installed zend developer tools module and BjyProfiler module in my application. Zend developer tools is working fine and both zend developer tools module and BjyProfiler module showing in module list in toolbar. But BjyProfiler not working properly, when i click database button of zend developers toolbar then it show like following:
I go through BjyProfiler readme in github. After reading readme, I am not sure where to put this code to enable BjyProfiler for query debugging.
$profiler = $sl->get('Zend\Db\Adapter\Adapter')->getProfiler();
$queryProfiles = $profiler->getQueryProfiles();
In summary, my problem is where to put this code and how to enable BjyProfiler successfully to debug query error. Thanks for your kind attention.
Add to aplication.config.php:
'modules' => array(
//...
'BjyProfiler',
'ZendDeveloperTools',
'MyOtherModule',
//...
),
Create bjyprofiler.local.php in config/autoload & add:
$dbParams = array(
'database' => 'ZF2sample',
'username' => 'root',
'password' => '123456',
'hostname' => 'localhost',
// buffer_results - only for mysqli buffered queries, skip for others
'options' => array('buffer_results' => true)
);
return array(
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => function ($sm) use ($dbParams) {
$adapter = new BjyProfiler\Db\Adapter\ProfilingAdapter(array(
'driver' => 'pdo',
'dsn' => 'mysql:dbname='.$dbParams['database'].';host='.$dbParams['hostname'],
'database' => $dbParams['database'],
'username' => $dbParams['username'],
'password' => $dbParams['password'],
'hostname' => $dbParams['hostname'],
));
if (php_sapi_name() == 'cli') {
$logger = new Zend\Log\Logger();
// write queries profiling info to stdout in CLI mode
$writer = new Zend\Log\Writer\Stream('php://output');
$logger->addWriter($writer, Zend\Log\Logger::DEBUG);
$adapter->setProfiler(new BjyProfiler\Db\Profiler\LoggingProfiler($logger));
} else {
$adapter->setProfiler(new BjyProfiler\Db\Profiler\Profiler());
}
if (isset($dbParams['options']) && is_array($dbParams['options'])) {
$options = $dbParams['options'];
} else {
$options = array();
}
$adapter->injectProfilingStatementPrototype($options);
return $adapter;
},
),
),
);
F5 in browser!

Categories