php laravel - try-catch not working - php

my api controller:
$POST /api/member/logout
public function post_logout(){
try{
member::logout();
return Response::json([], 200);
}catch(Exception $e){
print_r($e);
return Response::json($e, 500);
}
}
and my model
public static function logout(){
if(!Auth::check()){
throw new Exception('not_logged');
}
Auth::logout();
}
It is returning status 200 but never ends loading (18.3mb loaded and counting...)

You are printing Exception object before json response with status 500, so PHP automatically sends response with status code 200.
As for huge never-ending response, I'm not sure since I don't know Laravael at all, but I suspect, that somewhere you are (or this framework is) dumping an object that references itself.

Related

how to use return type a class in php 7

I tried my code as below. but in this, I return a blank object when an error occurred.
Is it ok to write return blank object? when the return type is class.
public static function createBook($book_data): BookModel
{
try {
$book = new BookModel;
$book->fill($book_data);
$book->save();
return $book;
}
catch(\Exception $e) {
log::error($e->getMessage());
$book = new BookModel;
return $book;
}
}
You're in for pain if you return empty objects:
If you use database's auto increment ids, then it wont exist on this empty model.
Any other required fields will not be filled in. (E.g., book with no author and no content?)
Although in some situations empty state objects are okay, if you have to ask then you most likely will handle it incorrectly anyway.
Since you have to check against this empty object anyway, returning null is still an okay solution. If you're on php7.1 then the return type can stay (note the questionmark).
public static function createBook($book_data): ?BookModel
{
try {
$book = new BookModel;
$book->fill($book_data);
$book->save();
return $book;
}
catch(\Exception $e) {
log::error($e->getMessage());
return null;
}
}
IMO, a better aproach would be to allow the error to propagate up and let you catch it somewhere else. Looks like you're using laravel, so the error will be logged anyway. So, for example, if you're in a database transaction, the transaction can fail safely:
public static function createBook($book_data): BookModel
{
$book = new BookModel;
$book->fill($book_data);
$book->save();
return $book;
}
I say it depends on what you want to do if (in this case) the exception is thrown.
If you want to let the app know that something went wrong, you should return false. Returning an empty BookModel is basically saying that everything went fine and it's much easier to check if something is false than checking if an attribute of an object is empty.
If it doesn't really mind that the book is empty (i.e. your code doesn't assume books are always filled), I think it is okay to return an empty book. However i'd change the code a bit:
public static function createBook($book_data): BookModel
{
$book = new BookModel();
try {
$book->fill($book_data);
$book->save();
}
catch(\Exception $e) {
log::error($e->getMessage());
}
return $book;
}

PHP function error and success pattern

What is the best way to return errors from a PHP function, when the function has executed normally?
Example
public function login($user, $pw){
if(!$this->verifyUser($user))
// return error about invalid user
}
else if (!$this->verifyPw($pw)){
// return error about invalid pw
}
else {
// return OK
}
}
Caller - Return response as JSON for web UI
public function doLogin($user,$pw){
$res = $this->login($user, $pw);
return json_encode($res);
}
On one hand I could understand returning results as an array, but I feel like this does not make sense for such low level functions. Perhaps they should return error codes and then caller must lookup the error code string?
Assuming you are in an object, you basically have three major options:
store errors in something like $this->errors array and return false
have some kind of error-collector as a dependency for object, where you
call $this->collector->addError('blah blah'); and return false
throw an exception
For the first two approaches, you will have to check the return value, and based on that, pull the list of errors. But both of those options have the benefit of being able to collect multiple errors.
The exception approach is a bit lighter on coupling, but you can only get one error.
As for what to actually return, I would recommend going with error code + description string. But that string would not be returned by your class. Instead your error should be registered using some "placeholder", that later is translated:
$this->errors[] = [
'code' => 52,
'msg' => 'authentication.login.invalid-password',
];
When you pull the errors from your object, it would be basically a list of entries like this, And then you just run them through your translation service.
In a case of exception, that same information would reside in $e->getCode() and $e->getMessage(), when your object throws InvalidPassword exception.
For an API response the answer from tereško would be along the correct lines.
For a DOM response you can do the following:
I have used a response code only in the past for something so simple:
http://php.net/manual/en/function.http-response-code.php with code 401
public function login($user, $pw) {
header_remove(); # Clear all previous headers.
if( !$this->verifyUser($user) || !$this->verifyPw($pw) ){
http_response_code(401);
exit;
}
http_response_code(200);
exit;
}
jQuery:
$.ajax({
.......
statusCode: {
200: function() {
window.location.href = '/';
},
401: function() {
alert( "Login Failed" );
}
}
});

How can I get Guzzle 6 to retry a request upon a 503 error in Laravel

I've written some code in Laravel 5.2 to retrieve results from an unrelible API source. However, it needs to be able to automatically retry the request on failed attempts, as the API call results in a 503 about a third of the time.
I'm use Guzzle to do this, and I think I know where to put the code that will intercept the 503 responses before they are processed; but I'm not sure what to actually write there.
The guzzle documentation doesn't offer much as far as retries go, and all of the examples I've come across of Guzzle 6 only show how to retrieve results (which I can already do), but not how to get it to repeat the request if needed.
I'm by no means asking anyone to do the work for me - but I think I'm approaching the limits of my understanding on this. If anybody can point me in the right direction, it'd be much appreciated :)
EDIT:
I will try and revise. Please consider the following code. In it, I want to send a GET request which should normally yield a JSON response.
DataController.php
$client = new \GuzzleHttp\Client();
$request = $client->request('GET', 'https://httpbin.org/status/503'); // URI is for testing purposes
When the response from this request is a 503, I can intercept it here:
Handler.php
public function render($request, Exception $e)
{
if ($e->getCode() == 503)
{
// Code that would tell Guzzle to retry the request 5 times with a 10s delay before failing completely
}
return parent::render($request, $e);
}
I don't know that that is the best place to put it, but the real problem is I don't know is what to write inside the if ($e->getCode() == 503)
Guzzle by default throws exceptions when a non 2** response is returned. In your case you're seeing a 503 response. Exceptions can be thought of as errors that the application can recover from. The way this works is with try catch blocks.
try {
// The code that can throw an exception will go here
throw new \Exception('A generic error');
// code from here down won't be executed, because the exception was thrown.
} catch (\Exception $e) {
// Handle the exception in the best manner possible.
}
You wrap the code that could throw an exception in the try portion of the block. Then you add your error handling code in the catch portion of the block. You can read the above link for more information on how php handles exceptions.
For your case, lets move the Guzzle call to it's own method in your controller:
public function performLookUp($retryOnError = false)
{
try {
$client = new \GuzzleHttp\Client();
$request = $client->request('GET', 'https://httpbin.org/status/503');
return $request->send();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if ($retryOnError) {
return $this->performLookUp();
}
abort(503);
}
}
Now in your controller you can execute, $this->performLookUp(true);.
Just to add some information to clarify a few points that Logan made.
Guzzle "can" throw exceptions on a Response other then 2**/3**. It all depends upon how the GuzzleHttp\HandlerStack is created.
$stack = GuzzleHttp\HandlerStack::create();
$client = new Client(['handler'=> $stack]);
$client = new Client();
// These two methods of generating a client are functionally the same.
$stack = New GuzzleHttp\HandlerStack(new GuzzleHttp\Handler\CurlHandler());
$client = new Client(['handler'=> $stack]);
// This client will not throw exceptions, or perform any of the functions mentioned below.
The create method adds default handlers to the HandlerStack. When the HandlerStack is resolved, the handlers will execute in the following order:
Sending request:
http_errors - No op when sending a request. The response status code is checked in the response processing when returning a response promise up the stack.
allow_redirects - No op when sending a request. Following redirects occurs when a response promise is being returned up the stack.
cookies - Adds cookies to requests.
prepare_body - The body of an HTTP request will be prepared (e.g., add default headers like Content-Length, Content-Type, etc.).
send request with handler
Processing response:
prepare_body - no op on response processing.
cookies - extracts response cookies into the cookie jar.
allow_redirects - Follows redirects.
4.http_errors - throws exceptions when the response status code >= 300.
When provided no $handler argument, GuzzleHttp\HandlerStack::create() will choose the most appropriate handler based on the extensions available on your system. As indicated within the Handler Documentation
By manually creating your GuzzleHttp\HandlerStack, you can add middleware to the application. Given the context of your original question "how do i repeat the request" I believe you are most interested in the Retry Middleware that is provided within Guzzle 6.1. This is a middleware that retries requests based on the result of the provided decision function.
Documentation has yet to catch up with this class.
final class HttpClient extends \GuzzleHttp\Client
{
public const SUCCESS_CODE = 200;
private int $attemptsCount = 3;
private function __construct(array $config = [])
{
parent::__construct($config);
}
public static function new(array $config = []): self
{
return new self($config);
}
public function postWithRetry(string $uri, array $options = []): Response
{
$attemptsCount = 0;
$result = null;
do {
$attemptsCount++;
$isEnd = $attemptsCount === $this->attemptsCount;
try {
$result = $this->post(
$uri,
$options,
);
} catch (ClientException $e) {
$result = $e->getResponse();
} catch (GuzzleException $e) {
if ($isEnd) {
Logger::error($e->getMessage());
$result = $e->getResponse();
}
}
} while ($this->isNeedRetry($result, $attemptsCount));
return $result;
}
private function isNeedRetry(?Response $response, int $attemptsCount): bool
{
return $response === null && $attemptsCount < $this->attemptsCount;
}

Confused about how to improve this function..?

I've got the following function in my controller that handles preparing and loading my home page.
public function index()
{
// GetBalance
$current_balance = $this->PayPal->getBalance();
if(Session::has('errors'))
{
return Redirect::to('error');
}
// TransactionSearch
$params = array(
'number_of_days' => 1
);
$recent_history = $this->PayPal->transactionSearch($params);
if(Session::has('errors'))
{
return Redirect::to('error');
}
// Make View
$data = array('current_balance' => $current_balance, 'recent_history' => $recent_history);
return View::make('index')->with('data', $data);
}
As you can see, I'm making 2 different calls to the PayPal API through my model, and after each one I'm checking for an error. When errors do occur I flash the error messages and redirect to an error page accordingly.
I'd like to improve upon that so I don't have to keep using this same snippet of code over and over again when I'm making a bunch of calls prior to loading a view.
if(Session::has('errors'))
{
return Redirect::to('error');
}
I tried moving this to its own function...
public function errorCheck()
{
if(Session::has('errors'))
{
return Redirect::to('error');
}
}
Then, I thought I could just do this within my index function...
// GetBalance
$current_balance = $this->PayPal->getBalance();
$this->errorCheck();
That doesn't work, though, I guess because errorCheck() is simply returning a value and not actually triggering the redirect, so I just end up at my home page with an error because none of the data it expects exists (since the API calls failed).
Any info on what I need to do here so that my errorCheck() function simply triggers the redirect when it should would be greatly appreciated.
Thanks!
The only way I can think of to avoid this is via the use of exceptions.
With your errorCheck() method you could try this:
// GetBalance
$current_balance = $this->PayPal->getBalance();
return $this->errorCheck();
but that's not what you want... it's going to exit your method after the first call. What you really need is a way to catch an error wherever it occurs and handle it - and that's what exceptions do.
I'm going to assume your PayPal class is a third-party package and you can't rewrite it to throw exceptions. Given that assumption, what you can do is this:
Rewrite your errorCheck() method like so:
public function errorCheck()
{
if(Session::has('errors'))
{
throw new Exception("Problem with Paypal!");
}
}
Then wrap all your Paypal access code in a try/catch block:
public function index()
{
try {
// GetBalance
$current_balance = $this->PayPal->getBalance();
$this->errorCheck();
// TransactionSearch
$params = array(
'number_of_days' => 1
);
$recent_history = $this->PayPal->transactionSearch($params);
$this->errorCheck();
// Make View
$data = array('current_balance' => $current_balance, 'recent_history' => $recent_history);
return View::make('index')->with('data', $data);
} catch(Exception $e) {
return Redirect::to('error');
}
}
Each time you call errorCheck() it will check for an error and throw an exception. In that case, execution will immediately jump to the catch block and redirect to the error page.
A better solution would be to throw the exceptions closer to the source of the error, ie. somewhere in the Paypal class when the error occurs. The idea here is that the exception includes a lot of useful information telling you what happened, like a stack trace. In the code I've given, the stack trace is going to show that the exception was thrown in the errorCheck() method which, while true, is not really helpful. If the exception could be thrown somewhere in the Paypal class, it would give you a better indication of what really went wrong.
While throwing an error is definitely the way to go, I'd say you go a step further and generalize the redirect. Instead of doing that try catch block every time the PayPal API is called, you can use App::error to do the redirect globally.
Create an exception class somewhere appropriate:
class PayPalApiException extends Exception {}
Then in your start/global.php add this (before the other App::error call):
App::error(function(PayPalApiException $exception)
{
return Redirect::to('error');
});
Then your code in the controller can become much simpler:
public function index()
{
$current_balance = $this->PayPal->getBalance();
$this->errorCheck();
$recent_history = $this->PayPal->transactionSearch([
'number_of_days' => 1
]);
$this->errorCheck();
$data = compact('current_balance', 'recent_history');
return View::make('index')->with('data', $data);
}
protected function errorCheck()
{
if (Session::has('errors'))
{
throw new PayPalApiException("Problem with Paypal!");
}
}
Why do you even need to check for the error twice? It doesnt seem to be related to each call? i.e. if doesnt seem to matter if the balance call fails, because you dont use the result in the transaction search.
I'd just do this
public function index()
{
// GetBalance
$current_balance = $this->PayPal->getBalance();
// TransactionSearch
$recent_history = $this->PayPal->transactionSearch(array('number_of_days' => 1));
if(Session::has('errors'))
{
return Redirect::to('error');
}
else
{
return View::make('index')->with('current_balance', $current_balance)
->with('recent_history', $recent_history);
}
}

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.

Categories