Get bad WSDL from SoapFault? - php

I'm connecting to a 3rd party service with SoapClient. Most of the time it works fine, but every once in awhile, maybe once out of every 100-150 calls, I get the error
Soap Failed: SOAP-ERROR: Parsing Schema: unexpected in complexType
My code is in a try/catch with a retry, and it will work on the next round through. But I'd like to examine the WSDL to find out why that fails, partly for my own curiosity, and in case I need to pass it along to the company I'm connecting to. Can I get that information from the SoapFault? Or would I have to call the URL to get the string? I'm afraid if I get the WSDL after the fact, it may already be fixed.
$pass = FALSE;
$this->soap = NULL;
$this->session = NULL;
do {
try {
Doc::i("Starting session");
$this->soap = new SoapClient($this->wsdl_url, ['trace' => 1]);
$pass = TRUE;
} catch (\SoapFault $e) {
Doc::e('Soap Failed: ' . $e->getMessage());
if(str_contains($e->getMessage(),'Parsing Schema') && !empty($e->detail)) {
Doc::e($e->detail); // Something new I'm trying to see if it helps
}
} catch (FatalErrorException $e) {
Doc::e("Soap failed really bad: " . $e->getMessage());
} catch (\Exception $e) {
Doc::e("Soap failed bad: " . $e->getMessage());
}
} while (!$pass);

You should be able to use $this->soap->__getLastResponse() since you are passing 'trace' => 1 option to SoapClient.
You might also consider logging $this->soap->__getLastRequest as well as the headers versions of both of these to ensure you're capturing as much information as possible at run-time.
Refer to the SoapClient method list for the possible options. Just remember the trick here is the trace option: without that, these will not return anything useful!

Related

dns_get_record(): A temporary server error occurred.

I'm querying a whole bunch of addresses, some are online and some are not. I can't seem to get around this error however, even catching the exception fails :(
dns_get_record(): A temporary server error occurred.
try {
$result = dns_get_record('_minecraft._tcp.' . $addr, DNS_SRV);
}
catch (Exception $e) {
return [$addr,$port];
}
If this error occurs, I want to continue the script, skipping the record, however currently the script just halts.
Any help appreciated!!
I can't catch this exception too. And how I understood it's a bug of php:
https://bugs.php.net/bug.php?id=73149
But I found another solution. You can use # when you call this function. This symbol kill all errors when you call this one. And it will looks like that:
$dns = #dns_get_record($domain, DNS_A);
if(!$dns){
return false;
}
I was able to get the IP (A record) for a host using the below PHP function
gethostbynamel(string $hostname): array|false
Reference: gethostbynamel — Get a list of IPv4 addresses corresponding to a given Internet host name
try this:
try {
$dns = dns_get_record($domain, DNS_A);
}
catch (Exception $e) {
if ($e->getMessage() !== 'dns_get_record(): A temporary server error occurred.') {
throw $e;
}
$dns = false;
}

How implement error in Rest API?

I want to know which is the way to implement error in RestAPI, actually if a method in my classes generate an exception I return this ...
if(mysqli_connect_errno()) {
throw new Exception("Can't connect to db.");
}
... but this is a bad practice 'cause an API should be return a json.
So my idea is create a class called Errors and, in each class, when an error is fired I simply call the relative error number for display the json error.
Someone have another idea?
Maybe something like so :
<?php
try {
// Do your stuff
if(mysqli_connect_errno()) {
throw new Exception("Can't connect to db.");
}
} catch (Exception $e) {
echo json_encode(array("success" => false, "message" => $e->getMessage()));
return;
}
I think #Gwendal answer is good but it's no enough just to return a json response, you also have to return the proper http code:
<?php
try {
// Do your stuff
} catch (Exception $e) {
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
echo json_encode(array("success" => false, "message" => $e->getMessage()));
return;
}
I think you're in the right path. There are a couple of concerns that you're dealing with in here. First one is error handling, whilst the second one is error formatting.
Error handling can be done in several ways, and throwing exceptions is one of them. In order to find out when something bad happened, you'll need to wrap your exceptions within a try/catch block:
try {
//logic
if(mysqli_connect_errno()) {
throw new Exception("Can't connect to db.");
}
//more logic
} catch (Exception $e) {
//handle the error here
}
If you're following this route, I'd suggest you to be more specific in your exceptions, so you can better build your responses in your API. It's not the same having the DB down than to not being able to find a resource, for instance:
try {
//logic
if(mysqli_connect_errno()) {
throw new DBException("Can't connect to db.");
}
if(is_null($entity)) {
throw new ResourceNotFoundException("Entity could not be found");
}
//more logic
} catch (DBException $e) {
//handle DB error here
} catch (ResourceNotFoundException $e) {
//handle resource not found error here
}
Now for the formatting part, the normal response in REST APIs are JSON responses. One way to go about it, would be to create a specific class whose sole responsibility would be to transforms your response into a valid JSON:
...
} catch (DBException $e) {
return $this->JSONResponse->format("Sorry we could not complete your request", 500);
} catch (ResourceNotFoundException $e) {
return $this->JSONResponse->format("The resource you were looking for could not be found", 404);
}
As you can see, different errors have different status codes. The implementation of the class is quite trivial:
class JSONResponse {
public function format($message, $statusCode) {
return json_encode(['message' => $message, 'code' => $statusCode]);
}
}
This does not change the status code of the response though, which is essential to good REST API design. You'll need to set the appropriate status code by using this function.
You can find a more robust and flexible implementation of this class in the Symfony HTTPFoundation Component, which extends from the normal Response class.
My RESTful API always returns a JSON of this structure:
[
'resource' : [],
'code' : [
'id' : int,
'msg' : string
],
'meta' : [],
'log' : []
]
If I return data, the data is always in resource and code['id'] is always 0 (which represents 'OK'). When an error occours, I return an empty resource and some error code. Also I provide some extra information via meta and can log some actions via log which helps me a lot with debugging.
This might also help you with future issues, for example if you want to split an answer into pages so the client should request data via GET /path/to/resource/page/:page or want to notice the client that a certain request path is deprecated.

Using additional data in php exceptions

I have php code that execute python cgi and I want to pass python trace (returned from cgi) as extra data to php exception how can I do this and how can I get that value from catch(Exception e) { (It should check if that extra value exesit or not).
I have code like this:
$response = json_decode(curl_exec($ch));
if (isset($response->error)) {
// how to send $response->trace with exception.
throw new Exception($response->error);
}
return $response->result;
and I use json-rpc library that should return that data to the user:
} catch (Exception $e) {
//catch all exeption from user code
$msg = $e->getMessage();
echo response(null, $id, array("code"=>200, "message"=>$msg));
}
Do I need to write new type of exception or can I do this with normal Exception? I would like to send everything that was thrown in "data" =>
You need to extend Exception class:
<?php
class ResponseException extends Exception
{
private $_data = '';
public function __construct($message, $data)
{
$this->_data = $data;
parent::__construct($message);
}
public function getData()
{
return $this->_data;
}
}
When throwing:
<?php
...
throw new ResponseException($response->error, $someData);
...
And when catching:
catch(ResponseException $e) {
...
$data = $e->getData();
...
}
Dynamic Property (not recommended)
Please note that this will cause deprecation error in PHP 8.2 and will stop working in PHP 9 according to one of the PHP RFC https://wiki.php.net/rfc/deprecate_dynamic_properties
As the OP asking about doing this task without extending Exception class, you can totally skip ResponseException class declaration. I really not recommend do it this way, unless you've got really strong reason (see this topic for more details: https://softwareengineering.stackexchange.com/questions/186439/is-declaring-fields-on-classes-actually-harmful-in-php)
In throwing section:
...
$e = new Exception('Exception message');
$e->data = $customData; // we're creating object property on the fly
throw $e;
...
and when catching:
catch(Exception $e) {
$data = $e->data; // Access data property
}
September 2018 edit:
As some of readers found this answer useful, I have added a link to another Stack Overflow question which explains the downsides of using dynamically declared properties.
Currently, your code converts the response text directly into an object without any intermediate step. Instead, you could always just keep the serialized (via JSON) text it and append it to the end of the Exception message.
$responseText = curl_exec($ch);
$response = json_decode($responseText);
if (isset($response->error)) {
throw new Exception('Error when fetching resource. Response:'.$responseText);
}
return $response->result;
Then you could just recover everything after "Response:" in your error log and optionally de-serialize it or just read it.
As an aside, I would also not count on the server sending JSON, you should verify that the response text was actually parseable as JSON and return a separate error for that if it isn't.

Formatting error log using PDO

I would like the error log to show each error on a new line beginning with a time stamp.
Currently the errors just show up in a connected chunk.
catch(PDOException $e) {
file_put_contents('logs/insert_errors.txt', $e->getMessage(), FILE_APPEND);
}
I'm sure this is fairly simple. Just my first time working with PDO and not sure how to accomplish this.
Preamble: As #Phil mentioned this has nothing to do with PDO.
There are two parts to your question:
a) it is all in one connected line
The reason is quite obvious, you are not adding a new line character at the end. What that character(s) is(are) depend on your OS, but PHP solves that for you with the PHP_EOL constant that you can just append to the end of your string.
b) you want a timestamp at the beginning of each line
Again, the issue is because you didn't put it there. Just adding the output of date('r') before printing out the message should be enough.
The real solution: a real logger
You may want to look at using a complete logging solution for your code. Someting like log4php, for example.
Homebrew solution
Alternatively, if that is too much, you could build your own logger. A good small example one can be found in the comments of the documentation of PHP's error_log function that I adapted for your needs:
Class Log {
const ERROR_DIR = '/home/site/error_log/db_errors.log';
// Log PDO errors
public function error($msg) {
$date = date('r');
$log = sprintf('%s: %s%s', $date, %msg, PHP_EOL);
error_log($log, 3, self::ERROR_DIR);
}
// shortcut for PDO exceptions
public function exception($e) {
$this->error($e->getMessage())
}
}
try {
$log = new log();
$log->error($msg); //use for general messages
} except (Exception $e)
$log->exception($e); //use for exceptions
}
Simply format the string to be inserted, eg
catch (PDOException $e) {
$error = sprintf('%s: %s%s', date('c'), $e->getMessage(), PHP_EOL);
file_put_contents('logs/insert_errors.txt', $error, FILE_APPEND);
// don't forget to re-throw the exception otherwise your program will
// continue execution
throw $e;
}

Check if soap connection working

Quick one,
How would you go about checking if your connection to a soap server is actually connecting?
I have this code:
$m_wsdl = "https://m2mconnect.orange.co.uk/orange-soap/services/MessageServiceByCountry?wsdl";
try {
$client = new SoapClient($m_wsdl);
$this->m_messages = $client->peekMessages('','',10,"");
} catch (Exception $e) {
echo "Exception: \n" . $e->getMessage() . "\n";
}
$this->do_parse_xml();
Obviously my username and password are in the peekmessages field where they should be, and they are both correct i am 100%.
For some reason its not returning any data at all and i dont know how to check to see if the connection is actually working??
Im getting no exceptions being echo'd
Thanks for any help
Use isSoapFault() http://php.net/manual/en/function.is-soap-fault.php
Also, the peekMessages method would probably return false or a SoapFault.
You can also set Exceptions to true on the SoapClient

Categories