I'm writing a PHP application which does a SOAP request. Sometimes their server isn't up or has some sort of problem and my application blows with an error like this:
Undefined property: stdClass::$getSomethingJSONResult
There could be a number of reasons why the SOAP request fails and I want to be able to check for those error conditions so the program can handle it.
I have two SOAP functions I use in PHP. One for logging in to the SOAP server and the other for making a query to get the soapFunction. They look something like this:
// --------------------- FUNCTIONS -----------------------
function login_soap($username,$password) {
global $soapClient;
$soapURL = "http://SOAPServerSomeplace.asmx?WSDL";
$ns = 'http://microsoft.com/webservices/'; //Namespace of the WS.
$soapParameters = array('userName' => $username, 'password' => $password);
$soapClient = new SoapClient($soapURL);
$header = new SoapHeader($ns,'UserCredentials',$soapParameters,false);
$soapClient->__setSoapHeaders($header);
}
// -------------------------------------------------------
function getSomething($option) {
global $soapClient;
$soapFunctionParameters = array('this' => $option) ;
$soapFunction = "getSomethingJSON" ;
$this_json = $soapClient->$soapFunction($soapFunctionParameters);
$stdClassObject = json_decode($this_json->getSomethingJSONResult);
$this_obj = $stdClassObject->this;
return $this_obj;
}
// -------------------------------------------------------
I was wondering if I could just do this:
if (login_soap($username,$password)) {
echo "This worked." . "\n";
} else {
echo "This failed." . "\n";
}
or with getSomething($option)?
I don't know how to simulate a SOAP request failure to test this, but I think the SOAP request failing would cause PHP to abort. I'd need to capture this so it doesn't abort PHP so I can direct it to do something else.
My first concern is to make sure the SOAP request worked. Then check the JSON file it returns to make sure it has all the data needed before the program proceeds. Checking the JSON file I have figured out, but not with the SOAP request. Thanks!
SOAP will throw a SoapFault Exception if there is an error. You just have to catch the Exception and you can handle the error.
http://www.php.net/manual/de/class.soapfault.php
The SoapClient can throw exceptions if there are errors.
If throwing of exceptions is disabled, those exception objects are only returned. You'd have to check whether the return value is an instance of SoapFault, but the better approach would be to throw them, and use try/catch.
This is even more true because the SoapClient cannot detect HTTP errors otherwise. You MUST throw errors as exceptions if you want to catch those errors as well (it does not work if XDebug is enabled, though, but this should only be the case in development).
Related
I am working with a internal framework where every exception is catched by an error handler and returned in a proper JSON error response, suitable for a RESTFul API.
Then I have a suite of tests, which are API tests, that are mainly testing that the API returns the proper JSON responses with the expected error codes.
For every test, the global variables are modified (and then restored) to emulate a different HTTP request. I do it that way to avoid the overload of doing cURL tests (through Guzzle or similar), and cause under the CLI environment, the code does not know the server's url.
<?php
// ... example, part of a base ApiTestCase class:
// Override globals (should be backed up by PHPUnit)
$_SERVER['REQUEST_METHOD'] = $request->method;
$_SERVER['QUERY_STRING'] = http_build_query($request->parameters);
$_SERVER['PATH_INFO'] = $request->path;
$_SERVER['REQUEST_URI'] = $request->path . ($_SERVER['QUERY_STRING'] ? '?' : '') . $_SERVER['QUERY_STRING'];
$_SERVER['REQUEST_TIME'] = time();
$_SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
$_SERVER['HTTP_COOKIE'] = '';
// Set headers, cookies and parameters
foreach ($request->headers as $k => $v) {
$_SERVER['HTTP_' . strtoupper(str_replace('-', '_', trim($k)))] = $v;
}
if ($_SERVER['HTTP_COOKIE']) {
$GLOBALS['_COOKIE'] = http_parse_cookie($_SERVER['HTTP_COOKIE']);
} else {
$GLOBALS['_COOKIE'] = [];
}
$GLOBALS['_REQUEST'] = $request->parameters;
$responseBody = $app->start();
$response->httpCode = http_response_code();
$response->body = $responseBody ? #json_decode($responseBody) : null;
$response->headers = headers_list();
(I know that changing globals this way is not nice, and the framework should not rely on globals directly, but I have still to deal with legacy code.)
Then here comes the problem: when I try to test JSON error responses: PHPUnit intercepts the thrown exception (before the handler I mentioned in the beginning), so the framework has no chance to convert it to JSON and return the proper response.
I tried to find something in the PHPUnit manual to disable the PHPUnit error handler with no luck.
What could I do in this case? Thanks
Just to be clear, it sounds like we're not actually talking about catching exceptions here; we're talking about using PHP's set_error_handler() to intercept a fatal error before it terminates the program. This will deal with both errors and uncaught exceptions.
One thing you will not be able to do is let those errors and exceptions fall through to your error handler function -- as you've already found out, phpUnit does its own error handling that you can't override (because it's kinda fundamental to how phpUnit works).
What you're going to have to do is tell phpUnit what kind of exception or error you're expecting; your test will then pass or fail according to whether the error occurs. You won't be running the error handler, but in truth, you shouldn't need to; you can test function that separately if you need to. For error conditions, you don't need to see that the error handler produces the right output every time, just that an error occurs that will trigger the handler.
For regular PHP exceptions, you can use phpUnit's #expectedException annotation above your test function, like so:
/**
* #expectedException YourExpectedExceptionClass
*/
function testThisWillThrowAnException() {
....
}
If the PHP code is expected to produce a PHP error (ie an error, not an exception), then you would use the same idea, but phpUnit provides a helper classname for the error: PHPUnit_Framework_Error. So your code would look like this:
/**
* #expectedException PHPUnit_Framework_Error
*/
function testThisWillProduceAPHPError() {
....
}
In either case, your test will pass if the expected error/exception occurs.
You can also test for specific exception messages and codes, in case the exception class itself isn't sufficient information for you to know whether the test has done what you want it to do. See the phpUnit manual page for annotations for more info.
The example above is also correct, mine only provide Exceptions as assertions and gives you knowladge of Exceptions Works.
/**
* #dataProvider fixturesProvider // its just example
*/
public function testDataIsWrong($fixtures)
{
try
{
//Some Code
$this->fail('Exception');
}
catch(Exception $ex)
{
$this->assertEquals($ex,'Exception');
}
}
This also provide in your code possibility ,that You can Test false or inncorect data and assert it is incorrect.
The only solution I implemented that solves my problem is to not delegate the exception handler the responsibility to build and send the API error responses, but catch exceptions in the top level of your application.
In the catch I have an exception-to-error-response converter that takes care of that (or re-throws the exception when convenient), so the errors that are not critical (like the ones producing HTTP 4xx responses) are not popping up in the PHPUnit tests anymore.
My PHPUnit tests are also now able to deal with PSR-7 HTTP Response objects, instead of capturing the output buffer.
We are working on setting up a SOAP server and I am specifically working on creating automated tests for it in Behat. The problem I am running into is that I am trying to test authentication of connections to the SOAP server and specifically trying to write tests that make sure that it fails to connect when no username and password are provided.
However, every time I run the test without HTTP authentication added in (and WSDL caching disabled intentionally) I get a PHP warning that throws up an error trace all over the console screen.
It is a PHP Warning that reads:
PHP Warning: SoapClient::SoapClient(http://cti.local/api/v1/companies/d1/soap?wsdl): failed to open stream: HTTP request failed! HTTP/1.1 401 Unauthorized
I would like to be able to suppress that error and just make sure it returns NULL or negative when declaring the new client. To do this I have tried adding the "#" sign right before the "new" keyword and wrapping the call in a try/catch statement that doesn't throw exceptions. But that doesn't work. I also tried adding the "#" sign at the beginning of the line to no avail.
That was using PHP's built-in SOAP client class. It seemed like an awesome way to go until I ran into this issue. So then, after exhausting all the options I would find, I then tried setting up the Zend SOAP Client and using that. Only to discover that it seems to extend PHP's built-in SoapClient class. So that is not working either.
Unfortunately we are using HTTP auth for all calls to the API, including to the WSDL. Currently I am running a checked out copy of the API locally (since we don't have it setup on an environment yet). I know that when I put in the proper authentication it works fine. But I want to make sure that my automated Behat tests can successfully test and return a test passed message for when I verify that the connection authentication fails.
Sample of connection call:
/**
* SOAP connection initiation.
*
* #param string $soapURL
* #param string $options (optional, no implemented yet)
*
* #Given /^I connect to a soap service at "([^"]*)"$/
*/
public function iConnectToASoapServiceAt($soapURL) {
$this->soapURL = $soapURL;
try {
$this->client = #new Zend\Soap\Client($soapURL, $this->options);
$this->soapFunctions = $this->client->getFunctions();
}
catch (\Exception $e) {
#For testing when it fails we cannot actually throw an error here.
#throw new Exception("Error connecting to SOAP server.");
}
}
/**
* #Given /^soap returns an error$/
*/
public function soapReturnsAnError() {
if ($this->client !== NULL && $this->soapFunctions !== NULL) {
throw new Exception("SOAP connection did not fail as expected.");
}
}
Does anyone have any ideas? I need to be able to test both a successful connection and an unsuccessful one in an automated way that won't error out and kill the PHP call.
# only suppresses errors of a single function call.
Try changing the error reporting level before the failed authentication code is executed, and than bring it back afterwards:
$level = error_reporting(E_ERROR | E_PARSE);
// your code with failed authentication here
error_reporting($level);
Using https://github.com/abraham/twitteroauth:
function getTwitterFeed($token_array){
require_once('twitteroauth/twitteroauth.php');
$oauth_token = $token_array['access_token'];
$oauth_token_secret = $token_array['access_token_secret'];
$connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $oauth_token, $oauth_token_secret);
$response = $connection->get("statuses/user_timeline");
//...do stuff with the response
}
I want to catch errors or exceptions for authentication problems (invalid token or token secret) and/or "rate limit exceeded."
I can't find anything on error handling for this library anywhere. How can I accomplish this?
Look into the Exceptions part of the PHP manual, the library uses them extensively.
Basically they will look like this:
try {
// your code here
} catch (OAuthException $e) {
// your error handling here
}
the OauthException class is the what the library uses for every throw.
Edit0:
Unfortunately the errors returned from the actual twitter API not converted into exceptions by the library so you will have to check the return values from get() and other calls, and look for the "error" key, errors will look something like this:
object(stdClass)[5]
public 'error' => string 'Could not authenticate you.' (length=27)
public 'request' => string '/1/account/verify_credentials.json?aauth_consumer_key=CONSUMER_KEY_HERE&oauth_nonce=cfbf6a55b26683750a166f14aeb5ed84&oauth_signature=c96MciQcODQD5jUAkyrAmSxXa0g%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1342379970&oauth_token=alma&oauth_version=1.0' (length=258)
also it will set the API instance's http_code code property to the response's http status, if that's not 200 it will indicate error.
Edit1:
I've created a fork of the library that will generate exceptions for every request that returns non 200 HTTP status, the exception's code will be the http status that twitter returns and message is the message (if exists), twitter's http error code listing will help decode the errors.
Also introduced a new Exception subclass for convenience named TwitterOauthException, every exception that thrown by the library subclasses this one.
Sup guys. I'm using ajax to load in a bunch of data with php. Sometimes exceptions go uncaught. This generates some error messages which javascript cannot parse (because it's expecting json). This crashes the page, that's not cool. So I'm building a custom exception handler. Basically it should just json_encode the Exception object. But json only works on objects of type stdClass. So here's the actual question:
How do you cast an exception object to stdClass, so that I can json_encode it? I've tried all the naive stuff I could think of, like
(stdClass) $ex and (Object) ((Array) $ex)
And yeah, I know that exceptions shouldn't be uncaught to begin with, but it's more like a development feature. It makes debugging less painful ;-)
Thanks!
PHP doesn't support object casting.
Using exceptions handler in this case is an horrible idea, especially when all you have to do is:
try {
// Current code here, including other try-catch blocks
} catch (Exception $e) {
// handle uncaught exception
}
When error occurs you should setup response correctly. Set Response Code to 500 (Internal Server Error) so JavaScript could easly handle that.
Exception object provides only getMessage() and getCode() methods that could be usefull, so response body could be generated like so:
$responseBody = json_encode(array(
'message' => $e->getMessage(),
'code' => $e->getCode()
));
One option would be to set a custom error handler and make shure when you echo them to the browser not to send 200 header code , that way you can deal with errors in you're js way elegant .
Other options would be to use ob_start , or a big try catch ...
Well, here's a simple function to do it for you (turn an object to an associative array, bypassing access restrictions):
function obj2array($object) {
$r = new ReflectionObject($object);
$params = array();
foreach ($r->getProperties() as $prop) {
$prop->setAccessible(true);
$params[$prop->name] = $prop->getValue($object);
}
return $params;
}
Then to encode it:
$json = json_encode(obj2array($e));
I am trying to invoke the Web Service PersonalDetails_Update by passing an array of values to it. These values are being successfully written to the database that the web service is designed to do. However, it is also supposed to return an ID for the record written to the database. But I don't get anything back. Just a blank screen with no XML or underlying source.
When using getLastRequest, I get this error:
Fatal error: Uncaught SoapFault exception: [Client] Function ("getLastRequest") is not a valid method for this service in
Code used to pass data to web service and request/response headers:
$client->PersonalDetails_Update(array('personaldetails' => $params));
printf("<br/> Request = %s </br>", htmlspecialchars($client->getLastRequest()));
$result = $client->__getLastResponse();
$header = $client->__getLastResponseHeaders();
When using getLastResponse and getLastResponseHeaders, I don't get anything back.
you forgot the "__":
printf("<br/> Request = %s </br>", htmlspecialchars($client->__getLastRequest()));
your soap client thinks "getLastRequest" is a method of a soap service this way, not a soap client method.
also you should tell us what soap client you are using. i assume you use php built-in soap client...
use __soapCall method to be sure, you are making a request to the service:
try {
$result = $client->__soapCall('PersonalDetails_Update', array('personaldetails' => $params));
} catch (SoapFault $exception) {
echo 'soap fault occured: '.$exception->getMessage().'<br/>';
}
you should check if the returned value is a soap fault.. see the manual