How to delegate exception to global exception in Laravel? - laravel

There is typical code in controller Laravel:
public function create(CreateInvoiceRequest $request)
{
try {
$invoice = Invoice::create(['']);
return response()->json($model);
} catch (\Exception $e) {
return \Response::json(["errors" => $e->getMessage()], 400);
}
}
In exception case I catch it and show message, how to delagate (move) this in global exception Laravel? Need I do something like this?
try { } } catch (\Exception $e) { throw new Exception($e); }

Laravel has a nice solution for this. In the documentation we are told to do this kind of exception handeling in App\Exceptions\Handler.
A very simple example could be the following:
// Your controller.
try {
$invoice = Invoice::create(['']);
return response()->json($model);
} catch (\Exception $e) {
throw new CustomException('Invoice creation failed.');
}
// app\Exceptions\Handler.php
public function render($request, Exception $exception)
{
if ($exception instanceof CustomException) {
return response()->view('errors.custom', [], 500);
}
return parent::render($request, $exception);
}
I tried to find out if create would throw a specific exception. Unfortunately I could not find it out so quickly. If this was the case, you could remove the try catch and just listen to this specific exception in the render method.
Update
(not tested)
In addition, you can also overwrite the save method to prevent from having to wrap (all) database writing method calls with a try and catch.
We will need a BaseModel class:
<?php
namespace App\Models;
use App\Exceptions\ModelSaveException;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Model extends EloquentModel
{
/**
* Save the model to the database.
*
* #param array $options
* #return bool
* #throws \App\Exceptions\ModelSaveException
*/
public function save(array $options = [])
{
try {
return parent::save($options);
} catch(\Exception $e) {
throw new ModelSaveException($this);
}
}
}
Your controller will look cleaner without try catch:
$invoice = Invoice::create(['']);
return response()->json($model);
As an extra we can check if our model was being created of updated by making is of the exists property.
<?php
namespace App\Exceptions;
class ModelSaveException extends \Exception
{
/**
* ModelSaveException constructor.
*
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function __construct($model)
{
if ($model->exists) {
parent::__construct('Failed updating model.');
} else {
parent::__construct('Failed creating model.');
}
}
}
Ofcourse don't forget to extend from you newly created model:
use App\Models\Model;
class Invoice extends Model

You can create a custom renderable exception.
try {
// Your code...
} catch (\Exception $e) {
throw new \App\Exceptions\CustomException('Your message');
}
Instead of type-checking exceptions in the exception handler's report and render methods, you may define report and render methods directly on your custom exception. When these methods exist, they will be called automatically by the framework:
/**
* Report the exception.
*
* #return void
*/
public function report()
{
//
}
/**
* Render the exception into an HTTP response.
*
* #param \Illuminate\Http\Request
* #return \Illuminate\Http\Response
*/
public function render($request)
{
return response(...);
}
For more information: https://laravel.com/docs/5.8/errors#renderable-exceptions

Related

Laravel 8: render exception as html for email

I upgraded my Laravel app to the latest version Laravel 8. My problem now is that I cannot figure out how to render an exception thrown by the app to html in order to send that html as an email.
My current code (that worked for Laravel 5) to render and send an exception within the App\Exceptions\Handler class:
public function sendEmail(Exception $exception)
{
try {
$e = FlattenException::create($exception);
$handler = new SymfonyExceptionHandler();
$html = $handler->getHtml($e);
$routeName = URL::full();
Mail::send(new ExceptionEmail($html, $routeName));
} catch (Exception $ex) {
if (env("APP_DEBUG") == true) {
dd($ex);
}
}
}
The problem is that class \Symfony\Component\Debug\Exception\FlattenException does not exist anymore in my upgraded app.
What is the appropriate way to render exceptions as html now in Laravel 8?
Thank you very much in advance.
Alright i managed to receive email, here is the code
Composer requirement
"require": {
"php": "^7.3|^8.0",
.....
"jeremykenedy/laravel-exception-notifier": "^2.2",
"laravel/framework": "^8.40",
.....
},
app/Exceptions/Handler.php
<?php
namespace App\Exceptions;
use App\Mail\ExceptionOccurred;
use Exception;
use Throwable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpFoundation\Response;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
/**
* Report or log an exception.
*
* #param Exception $e
* #return void
* #throws Throwable
*/
public function report(Throwable $e)
{
if ($this->shouldReport($e)) {
$this->sendEmail($e); // sends an email
}
parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* #param Request $request
* #param Throwable $e
* #return Response
*
* #throws Throwable
*/
public function render($request, Throwable $e): Response
{
return parent::render($request, $e);
}
public function sendEmail(Throwable $exception)
{
try {
$e = FlattenException::createFromThrowable($exception);
$handler = new HtmlErrorRenderer(true);
$css = $handler->getStylesheet();
$content = $handler->getBody($e);
Mail::to('your_email_address_here')->send(new ExceptionOccurred($content,$css));
} catch (Throwable $exception) {
Log::error($exception);
}
}
}
app/Mail/ExceptionOccurred.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class ExceptionOccurred extends Mailable
{
use Queueable, SerializesModels;
private $content;
private $css;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($content,$css)
{
$this->content = $content;
$this->css = $css;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$emailsTo = str_getcsv(config('exceptions.emailExceptionsTo'), ',');
$ccEmails = str_getcsv(config('exceptions.emailExceptionCCto'), ',');
$bccEmails = str_getcsv(config('exceptions.emailExceptionBCCto'), ',');
$fromSender = config('exceptions.emailExceptionFrom');
$subject = config('exceptions.emailExceptionSubject');
if ($emailsTo[0] === null) {
$emailsTo = config('exceptions.emailExceptionsToDefault');
}
if ($ccEmails[0] === null) {
$ccEmails = config('exceptions.emailExceptionCCtoDefault');
}
if ($bccEmails[0] === null) {
$bccEmails = config('exceptions.emailExceptionBCCtoDefault');
}
if (! $fromSender) {
$fromSender = config('exceptions.emailExceptionFromDefault');
}
if (! $subject) {
$subject = config('exceptions.emailExceptionSubjectDefault');
}
return $this->from($fromSender)
->to($emailsTo)
->cc($ccEmails)
->bcc($bccEmails)
->subject($subject)
->view(config('exceptions.emailExceptionView'))
->with('content', $this->content)
->with('css', $this->css);
}
}
resources/views/emails/exception.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>{!! $css ?? '' !!}</style>
</head>
<body>
{!! $content ?? '' !!}
</body>
</html>
Please let me know if that works.

Nexmo Handling Errors in Laravel

How I can handle nexmo errors, I use try{}catch(){} but it not work, and I got this error Nexmo \ Client \ Exception \ Request (29)
Non White-listed Destination - rejected i know this error but i need handle it.
this is a code:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\NexmoMessage;
//use App\admin\Course;
class ConfirmedCourse extends Notification
{
use Queueable;
protected $course;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($course)
{
$this->course = $course;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['nexmo'];
}
public function toNexmo($notifiable)
{
try {
$message = new NexmoMessage();
$message->content($this->course)
->unicode();
return $message;
}catch (\Exception $e) {
$e->getMessage();
}
}
}
I fixed this in the past by wrapping the call to notify() in the try catch:
try {
$variableToCatch = $YourModel->notify(new ConfirmedCourse($data));
} catch (\Exception $e) {
// Do what you want here...
// Log::error('nexmo failed...');
// echo 'Caught exception: ', $e->getMessage(), "\n";
// Log::error($e->getMessage());
// dd($e->getMessage())
}
If you post the line where you are calling notify() I can update to your use exact needed statement.
Remove the try catch from ConfirmedCourse class and put it around the call in the method calling it.
Here is how I handled a slack notification failure:
try {
$slackNotification = $user->notify(new SlackNotification($slackData));
} catch (\Exception $e) {
Log::error('slack notification failed.');
}

Improve code quality while utilizing Guzzle in Laravel

I am new with laravel so please don't be harsh.
I am bulding a simple web which connects to an external API(several endpoints) via Guzzle,fetching some data,cleaning them and storing them.
At the moment -and from one endpoint- i have something the following Job:
public function handle(Client $client)
{
try {
$request= $client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key'=> env("FOOTBALL_API_KEY"),
'Accept' => 'application/json'
]
]);
$request = json_decode($request->getBody()->getContents(), true);
foreach ($request as $array=>$val) {
foreach ($val['leagues'] as $id) {
League::firstOrCreate(collect($id)->except(['coverage'])->toArray());
}
}
} catch (GuzzleException $e) {
};
}
Therefore i would like some code recommendations, how can i make my code better from design point of view.
My thoughts are:
a)Bind Guzzle as service provider.
b)use a design pattern for implementing calls to endpoints.URI builder maybe?
Any assistance will be appreciated.
May the force be with you.
Detailed feedback
Some pointers specific to the provided code itself:
A guzzle client request returns a response, which does not match the name of the parameter you assign it to
Calls to json_decode can fail in which case they'll return null. In terms of defensive programming it's good to check for those fail cases
Your case makes some assumptions about the data in the response. It's best to check if the response is in the actual format you expect before using it.
You catch all GuzzleExceptions, but do nothing in those cases. I think you could improve this by either:
Logging the exception
Throwing another exception which you will catch at a class, calling the handle() method
Both of the options above
You could choose to inject the api key, rather than fetching it directly via the env() method. This will prevent issues described in the warning block here
General feedback
It feels like your code is mixing responsibilities, which is considered bad practice. The handle() method now does the following:
Send API requests
Decode API requests
Validate API responses
Parse API responses
Create models
You could consider moving some or all of these to separate classes, like so:
ApiClient which is responsible for sending out requests
ResponseDecoder which is responsible for turning a response into \stdClass
ResponseValidator which is responsible for checking if the response has the expected data structure
RepsonseParser which is responsible for turning the response \stdClass into collections
LeagueFactory which is responsible for turning collections into League models
One could argue that the first four classes should be put into a single class called ApiClient. That's purely up to you.
So in the end you would come up with something like this:
<?php
namespace App\Example;
use Psr\Log\LoggerInterface;
class LeagueApiHandler
{
/**
* #var ApiClient
*/
private $apiClient;
/**
* #var ResponseDecoder
*/
private $decoder;
/**
* #var ResponseValidator
*/
private $validator;
/**
* #var ResponseParser
*/
private $parser;
/**
* #var LeagueFactory
*/
private $factory;
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(
ApiClient $apiClient,
ResponseDecoder $decoder,
ResponseValidator $validator,
ResponseParser $parser,
LeagueFactory $factory,
LoggerInterface $logger
) {
$this->apiClient = $apiClient;
$this->decoder = $decoder;
$this->validator = $validator;
$this->parser = $parser;
$this->factory = $factory;
$this->logger = $logger;
}
public function handle()
{
try {
$response = $this->apiClient->send();
} catch (\RuntimeException $e) {
$this->logger->error('Unable to send api request', $e->getMessage());
return;
};
try {
$decodedResponse = $this->decoder->decode($response);
} catch (\RuntimeException $e) {
$this->logger->error('Unable to decode api response');
return;
};
if (!$this->validator->isValid($decodedResponse)) {
$this->logger->error('Unable to decode api response');
return;
}
$collections = $this->parser->toCollection($decodedResponse);
foreach ($collections as $collection) {
$this->factory->create($collection);
}
}
}
namespace App\Example;
use GuzzleHttp\Client;
class ApiClient
{
/**
* #var Client
*/
private $client;
/**
* #var string
*/
private $apiKey;
public function __construct(Client $client, string $apiKey)
{
$this->client = $client;
$this->apiKey = $apiKey;
}
public function send()
{
try {
return $this->client->request('GET', 'https://api.url./something', [
'headers' => [
'X-RapidAPI-Key' => $this->apiKey,
'Accept' => 'application/json'
]
]);
} catch (GuzzleException $e) {
throw new \RuntimeException('Unable to send request to api', 0, $e);
};
}
}
namespace App\Example;
use Psr\Http\Message\ResponseInterface;
class ResponseDecoder
{
public function decode(ResponseInterface $response): \stdClass
{
$response = json_decode($response->getBody()->getContents(), true);
if ($response === null) {
throw new \RuntimeException('Unable to decode api response');
}
return $response;
}
}
namespace App\Example;
class ResponseValidator
{
public function isValid(\stdClass $response): bool
{
if (is_array($response) === false) {
return false;
}
foreach ($response as $array) {
if (!isset($array['leagues'])) {
return false;
}
}
return true;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class ResponseParser
{
/**
* #param \stdClass $response
* #return Collection[]
*/
public function toCollection(\stdClass $response): array
{
$collections = [];
foreach ($response as $array => $val) {
foreach ($val['leagues'] as $id) {
$collections[] = collect($id)->except(['coverage'])->toArray();
}
}
return $collections;
}
}
namespace App\Example;
use Illuminate\Support\Collection;
class LeagueFactory
{
public function create(Collection $collection): void
{
League::firstOrCreate($collection);
}
}

Exceptions will not render Laravel 5.7

I am using Laravel v 5.7.15.
I wrote a validation helper which validates an API request - this works successfully, and prior I was using a try/catch to surround it.
I have moved on to handling the exception in the handler, however I cannot get the function 'render' to run - it goes straight into 'report' and throws in the exception in my tinker console.
Handler: (full class as requested)
<?php
namespace App\Exceptions;
use Illuminate\Validation\ValidationException;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Log;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* #param Exception $exception
* #return mixed|void
* #throws Exception
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
dd($exception);
$log = new Log();
$log->status = 2;
// Validate API incoming data
if ($exception instanceOf ValidationException) {
foreach ($exception->errors() as $error) {
// collect multiple validation errors
$message[] = implode('', $error);
}
$message = implode('', $message);
$log->message = $message;
$log->save();
$response = [
'message' => $message,
'status' => 400,
];
} else {
$response = [
'message' => $exception->getMessage(),
'status' => '500',
];
}
return parent::render($request, $exception);
}
}
This fails to die and dump, however I can dd in the report function and this works fine. The rest of this file has been left untouched, save for the includes at the top of the file.
This is how I call my validator in my controller:
$this->validate($request, BlueparkValidatorArrays::$getOrders);
If anybody could point me in the right direction, I would be most grateful.
This may be caused by a problem in your log configuration.
The call to parent::report($exception); runs the following from the laravel source code:
public function report(Exception $e)
{
...
try {
$logger = $this->container->make(LoggerInterface::class);
} catch (Exception $ex) {
throw $e; // throw the original exception
}
...
}
Note throw $e not throw $ex. So if creating the logger implementation fails, the original exception that was being processed is thrown.
To test this, comment out parent::report($exception); in your report function and see if render() is called as expected.
If it is, your log configuration is not working. Make sure you have the correct permissions on your log location and that your .env file doesn't override any of laravel's logging settings. See How to debug Laravel error 500 with no logs, no information

How to catch errors and best practices?

I'm performing various tasks on an eloquent model.
e.g.
Flight Repository
function store($request){
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
This method is called from a controller:
public function store(FlightRepository $flight, $request){
$flight->store($request);
}
How should one approach potential errors? try/catch? Where should it be placed in the controller or repository? What would I catch anyway, what exception type?
According to Laravel 5.0 and above,
All the Exceptions thrown in any part of the Laravel App, the exceptions are catched inside the report() method of Exception/Handler.php file, like this:
UPDATED
Your Repo should throw an Exception like this:
class CustomRepository extends Repository
{
public function repoMethod($id)
{
$model = Model::find($id);
// Throw your custom exception here ...
if(!$model) {
throw new CustomException("My Custom Message");
}
}
}
And your Handler should handle the CustomException like this:
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
// Handle your exceptions here...
if($exception instanceof CustomException)
return view('your_desired_view')->with('message' => $exception->getMessage());
parent::report($exception);
}
Hope this helps!

Resources