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.
Related
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!
I am trying to create a subscribe method for my laravel app that uses the mailchimp api to subscribe a user to a given list. The method works fine when the email address is not already on the lsit. when it is already subscribed the mailchimp api throws the following error
Mailchimp_List_AlreadySubscribed blah#blah.co is already subscribed to
list Tc App Test List. Click here to update your profile.
with the following code being shown
public function castError($result) {
if($result['status'] !== 'error' || !$result['name']) throw new Mailchimp_Error('We received an unexpected error: ' . json_encode($result));
$class = (isset(self::$error_map[$result['name']])) ? self::$error_map[$result['name']] : 'Mailchimp_Error';
return new $class($result['error'], $result['code']);
}
I have attempted a try catch block to catch the error but it is still being returned to the browser, here is what I tried and were it says MailChimp_Error I tried with Exception as well.
public function subscribe($id, $email, $merge_vars)
{
try {
$this->mailchimp->lists->subscribe($id, $email, $merge_vars);
} catch (MailChimp_Error $e) {
$response = 'an error has occured';
}
return $response;
}
Ultimately I want to be able to run the method and then either return either a success message or a message describing the issue to the user. the 3 possible mailchimp method errors are Email_notexists, list_alreadysubscribed and list does not exist although tihs last one should not occur as I am providing the list in the source code.
edit 1; after being in touch with mailchimp api support they suggested this code but the error still gets returned to the browser in its entirety
try {
$results = $this->mailchimp->lists->subscribe($id, $email, $merge_vars);
} catch (Mailchimp_Error $e) {
if ($e->getMessage()) {
$error = 'Code:'.$e->getCode().': '.$e->getMessage();
}
}
echo $error;
You can do
try
{
$response = $this->mailchimp->lists->addListMember($list_id, [
"email_address" => $email,
"status" => "subscribed",
]);
}
catch (\EXCEPTION $e) {
return $e->getMessage();
}
The \EXCEPTION handles a sort of error for stripe
Subscribe is in a namespace Acme\Emails\Subscribe so catch(Mailchimp_Error $e) looks for Mailchimp_Error in this namespace.
Changing it to catch(\Mailchimp_Error $e) makes it look in the root namespace and then it works as intended
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.
I have a custom exception class that extends Exception and adds on the ability to pass back more data on what threw the exception. The problem now is if I want to catch my custom exception and a standard exception, but handle them with the same block of code, I don't know how to do that other than create a new function (which I don't want to do for every place I'd like to use this).
try {
} catch(QM\DebugInfoException $e) {
// I don't want to duplicate the Exception handling code up here
}catch(Exception $e){
$db->rollBack();
$return['error'] = 1;
$return['errInfo'] = array(
'code' => $e->getCode(),
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
);
// I'd rather handle both here, and just add data on to $return['errInfo']
switch ($ExceptionType) {
case 'QM\DebugInfoException':
$return['errInfo']['extraInfo'] = $e->getExtraInfo();
break;
}
}
Does anyone have any good ideas on this?
You could do a get_class($e) and that will return the string representing the class name of the exception object then use that to compare in your switch.
Another option is to put a function that encapsulates the common functionality, and call it from each of the exception blocks. That way new, unexpected exceptions not in your switch can still percolate up. I'm a big fan of explicitly catching specific exceptions.
I currently handle errors during AJAX requests in a manner similar to this:
try {
// code
if (some_error_condition) {
throw new \Exception('error');
}
// other code
if (some_other_error_condition) {
throw new \Exception('other error');
}
// more code
$response = array(
'success' => TRUE,
'data' => 'stuff here'
);
} catch (Exception $e) {
$response = array(
'success' => FALSE,
'error' => $e->getMessage()
);
}
header('Content-Type: application/json');
echo json_encode($response);
My question is: is there a better way to handle multiple possible error conditions than this, while still adhering to DRY principles? I think this method is much cleaner and easier to follow than giant nested if/else messes, but it's a little reminiscent of goto code.
Perhaps an OOP way?
it is completely valid solution for me, except you could use different exception classes for your exception and encapsulate actual logic in some object, like
class Handler {
//this function executes code and throws exception - no error handling logic.
public static function doSomeCode() {
(...)
return $response;
}
}
try {
$response = Handler::doSomeCode();
renderResponse();
} catch (SomeError $e) {
$err = 'some error';
renderError($err);
} catch (Exception $e) {
header('500 Internal Server Error'); //this is pseudo code!
}
your exception classes (except generic Exception) could handle rendering errors, Exception class would trigger 500 (it should never happend). This way you separate actual code execution from error handling, and with proper exceptions object model dont repeat yourself with error handling.