In the Laravel documentation there is this part:
You may also dispatch a closure and chain the afterResponse method onto the dispatch helper to execute a closure after the HTTP response has been sent to the browser:
use App\Mail\WelcomeMessage;
use Illuminate\Support\Facades\Mail;
dispatch(function () {
Mail::to('taylor#example.com')->send(new WelcomeMessage);
})->afterResponse();
This works perfectly fine when the mail is successfully sent. However, when there is an error in the mail sending function, for example, if I intentionally misconfigure the mailing service credentials in .env, then Laravel appends a {"message": "Server Error" } to the response.
Here's an excerpt from the actual code:
public function register() {
try {
dispatch(function () use ($user) {
Mail::to($user)->send(new Register($user));
})->afterResponse();
} catch (Exception $e) {
return response()->json(['success' => true]);
}
return response()->json(['success' => true]);
}
When the mail succeeds, the JSON data {success: true} is returned for the ajax call to process. However, when there is an error in the mail sending part, the following message is returned:
{"success":true}{
"message": "Server Error"
}
The try-catch blocks didn't seem to help.
Any ideas why is that message appended to the response data?
Related
I am building api in laravel and I am catching exceptions if they occur and I am returning them as response. The problem is that I only want to show them in my dev enviroment. Setting APP_DEBUG=false doesn't solve the problem, error message is still in response.
public function foo()
{
try{
$this->bar();
}catch(\Exception $e){
return $this->error($e->getMessage(),'Something went wrong', 403);
}
}
This returns json for example:
{
"error": "No query results for model [App\\Models\\User].",
"message": "Something went wrong"
}
I want to have this in my dev enviroment, but on production I would like to have only the message like:
{
"error": "500 Server Error",
"message": "Something went wrong"
}
How do I achieve this and is this a good practice to do? I haven't found any solution yet, except to overwrite message if APP_ENV=production, but I have a feeling that there is a better way to do this.
protected function error($error, $message = '', $code = 400)
{
$this->response['error'] = $error;
$this->response['message'] = $message;
if(env('APP_ENV') === 'production'){
$this->response['message'] = '500 Server Error'
}
return response()->json($this->response, $code);
}
Agree with #dummy.learn's answer, the best way to handle exception is inside App\Exceptions\Handler, laravel's all exception will pass through App\Exceptions\Handler before it render or report, even those you didn't catched.
You can handle exception like this:
$this->renderable(function (Throwable $e, $request) {
return new ExceptionResource($e);
});
ExceptionResource is an API resources from Illuminate\Http\Resources\Json\JsonResource which you can see document from here: https://laravel.com/docs/9.x/eloquent-resources.
And I prefer to check enviroment from app helper not env or config instead, the helper will return a bool, that I think readability is better.
Example in ExceptionResource, showing error if environment is local dev or unit test:
public function toArray($request)
{
if(app()->environment(['local', 'testing']))
$error = $this->resource->getMessage();
else
$error = 'Server Error';
return [
'error' => $error,
'message' => 'Something went wrong'
];
}
You can catch it inside App\Exceptions\Handler
Use the render function to intercept all exceptions with json as response (for api only)
And within add check which env is it currently being deployed.
This is the only way to make sure your error overwrite will work only in production.
Also use config(app.env) === production is best practice instead of using env.
Now you can throw any kind (custom) exceptions , and all can be "filtered" by this code.
Extra note about config as best place as pointed out by #dbf (thanks mate)
This is based on how mr Otwell himself use config() instead of env(), in core and all of his laravel related oackages
dont forget also that config is cached, meaning faster in load time compare to raw env,
have a look at this link https://github.com/alexeymezenin/laravel-best-practices#do-not-get-data-from-the-env-file-directly ... As one of many recommended way to use config() instead of env()
I would like to make an AJAX login request, where the authentication fails, the authentication error message should be returned in a JSON object. What is the nicest way to do this?
Thanks for the answers in advance!
EDIT:
I generate the auth with the php artisan make:auth command.
There's 2 possibilities for the authentication to fail:
The request fails validation
The credentials are not valid
When the request fails validation the error returned adapts based on what the request expects so if it was JSON it will return the error in the JSON response.
The problem is for the 2nd case, in that case you can simply do:
class LoginController {
use AuthenticatesUsers {
sendFailedLoginResponse as protected traitSendFailedLoginResponse;
}
protected function sendFailedLoginResponse(Request $request) {
if ($request->expectsJson()) {
return response()->json([ "errors" => Lang::get('auth.failed') ]);
} else {
return $this->traitSendFailedLoginResponse($request);
}
}
}
I have created the following function into the main Controller.
public function noContent() : JsonResponse
{
return response()->json([], Response:HTTP_NO_CONTENT);
}
When deleting some data, I am returning the above function but I get a message that "Could not get any response".
I also have tried to pass a message (even if this is not recommended for a 204) in the array but still, I receive the same error. I am using the same function to return 200 or 404 messages and there worked as expected.
Is there another solution to make this work?
You should do:
return response('', 204);
I asked a very specific question located here: Laravel 5 catching PayPal PHP API 400 errors on localhost:8000
As nobody could help I want to make it a more open question about catching a 400 error from the PayPal API.
I am making requests to PayPal, when a successful request is made everything works perfectly and I get a lovely response object, which I can work with and action accordingly.
When for instance incorrect card details are entered Laravel throws a 400 error and fails to catch the error for me to action accordingly.
Snippet:
try {
// ### Create Payment
// Create a payment by posting to the APIService
// using a valid ApiContext
// The return object contains the status;
$payment->create($this->_apiContext);
//will not catch here :( throws Laravel 400 error! want to redirect with message!
} catch (\PPConnectionException $ex) {
return Redirect::back()->withErrors([$ex->getMessage() . PHP_EOL]);
}
//if we get an approved payment ! This fires perfectly when succesful!!! woo!!
if($payment->state == 'approved') {
//if success we hit here fine!!!
} else {
//won't get here, dies before catch.
}
Here is the error in Laravel debug mode:
When I look in the PayPal API sandbox logs I should be getting a nice object so I can action accordingly.
{
"status": 400,
"duration_time": 60,
"body": {
"message": "Invalid request. See details.",
"information_link": "https://developer.paypal.com/webapps/developer/docs/api/#VALIDATION_ERROR",
"details": [
{
"field": "payer.funding_instruments[0].credit_card.number",
"issue": "Value is invalid."
}
],
"name": "VALIDATION_ERROR",
"debug_id": "XXXXXXXXXXXXXX"
},
"additional_properties": {},
"header": {
"Date": "Thu, 25 May 2017 14:44:43 GMT",
"paypal-debug-id": "2f88f18d519c3",
"APPLICATION_ID": "APP-XXXXXXXXXXXX",
"Content-Language": "*",
"CALLER_ACCT_NUM": "XXXXXXXXXXXXX"
}
}
If any Laravel wizard can help you would be my hero.
Nick.
Right guys,
It would appear that Laravel's default Exception method was interfering with the PayPal API PayPalConnectionException. So I modified the code to catch general Exception errors only as it contained all required error objects. The \ before Exception was critical! as it needs the correct namespace (in my case anyway, your application may be different).
try {
// ### Create Payment
// Create a payment by posting to the APIService
// using a valid ApiContext
// The return object contains the status;
$payment->create($this->_apiContext);
} catch (\Exception $ex) {
return Redirect::back()->withErrors([$ex->getData()])->withInput(Input::all());
}
This link that #rchatburn posted was highly useful, the application always seemed to catch at the point \Exception and NOT \PayPalConnectionException once I had everything namespaced correctly.
In my investigations I came across app/Exceptions/Handler.php. Here you can extend the render method to grab a PayPalConnectionException and handle the errors uniquely to that specific exception . See code:
//Be sure to include the exception you want at the top of the file
use PayPal\Exception\PayPalConnectionException;//pull in paypal error exception to work with
public function render($request, Exception $e)
{
//check the specific exception
if ($e instanceof PayPalConnectionException) {
//return with errors and with at the form data
return Redirect::back()->withErrors($e->getData())->withInput(Input::all());
}
return parent::render($request, $e);
}
Either work great, but for me it felt neater to just change the catch method to a lookout for a general Exception, where I am testing if a payment was successful.
Hope this helps anyone facing similar issues :D!!!
Nick.
I can't figure out how I can throw an exception from Guzzle future response handler.
Here's my code:
<?php
require 'vendor/autoload.php';
$client = new \GuzzleHttp\Client();
$req = $client->createRequest('GET', 'http://www.google.com', array(
'future' => true,
));
echo "Sending request\n";
$response = $client->send($req);
try {
$response->then(function ($data) {
echo "Response is received\n";
throw new Exception('Test');
})->then(function () {
// success handler
}, function (Exception $exception) {
echo "Error handler invoked\n";
throw $exception;
});
} catch (Exception $e) {
echo "Exception catched\n";
}
echo "Finish\n";
The catch block is never reached in this case.
You are working with promises when using asynchronous Guzzle requests. Using the then() function off of a FutureResponse will create a promise that is fulfilled or rejected when the request completes. If an error occurs while sending, the promise is rejected, which means the second callback provided to the then function is invoked. When a request completes successfully, it is resolved, and the first callback provided to the then function is invoked. When an exception is thrown in any of the promise functions, the exception is caught inside of the promise and forwarded to the next error handler in the chain. In your example, if the request succeeds, then you throw an exception which will trigger the error callback. Throwing an exception in the error callback will either forward the exception to the next error callback in the promise chain, or silently eat the error (in your case, there are no further error callbacks to trigger).
The React Promises library that is used by Guzzle has more documentation on resolution and rejection forwarding of promises: https://github.com/reactphp/promise#how-promise-forwarding-works. The author of this library is looking into adding a done() function that can be used as a terminal promise handler that actually throws unhandled exceptions.
Asynchronous means that your script will not wait for the response to come back from the server, rather will just send the request and continue executing. In this case, the script reaches its end of life before the response returns, so none of the callbacks are ever executed.
Add this line after the catch to block the script's execution until the response comes back.
$response->getStatusCode();
If you provide more info on what you want to achieve, we might be able to help you further.