How to catch errors and best practices? - laravel

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!

Related

How to exclude logging an Guzzle's 404 error in Laravel?

I use the Laravel framework. When I get an exception GuzzleHttp\\Exception\\ClientException(code: 404), this event is added to laravel.log. How can I disable adding the event about that error to laravel.log?
You can remove it from the exceptions handler file according to the docs, if can be found in your App\Exceptions\Handler so for example:-
use GuzzleHttp\Exception\ClientException; // here, import the target class
...
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var string[]
*/
protected $dontReport = [
ClientException::class, // here, add the class to the don't report list
];
...
}
to manipulate a custom error condition, you can register it in the register method in the Handler file like:-
**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
// check if the ClientExeption is a 404
$this->reportable(function (ClientException $e) {
if ($e->getCode() == 404) {
return false; // or anything else for that matter
}
});
}

Catch FTP Laravel exceptions

I'm trying to work for the first time with Laravel exceptions handling.
What I need is to catch the different exceptions I get when trying to connect to FTP server (eg. cannot connect, wrong user/password, cannot find new files, cannot open files).
I'm stuck because I see that Laravel already has a class that throw Exceptions (located in vendor/league/flysystem/src/Adapted/Ftp.php) and the Handler.php but I don't understand how they work together and how I can render different messages depending on Exception.
Many thanks for your help.
The Handler.php will encapsule any call and handle any exception that extend the native class \Exception of PHP. So, no matter where the exception is triggered, the handler will try to handle it.
You can customize the response in two ways.
Catch the exception before the handler:
Basicly, surround the line of code that can trigger an exception with a try catch
try {
connectFTP();
} catch (\Exception $e) { //better use FilesystemException class in your case
//handle it
}
Adapt the Handler.php: Here there are two ways:
Patch : just intercept in the render method the exception in question
Handler.php laravel 8.x (add the method)
public function render($request, Exception $e)
{
if ($e instance of \FtpException) {
//handle it here
}
parent::render($request, $e);
}
Use your own exception class:More info here
class FtpConnectionException extends Exception
{
/**
* Report the exception.
*
* #return bool|null
*/
public function report()
{
//
}
/**
* Render the exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function render($request)
{
return response(...);
}
}
How can you use your own exception class when the exception is triggered in the Vendor folder ? use the try catch method
try {
connectFTP();
} catch (\Exception $e) { //better use FilesystemException class in your case
throw new FtpConnectionException($e->getMessage(), $e->getCode(), $e->getPrevious());
}
NOTE: Some exceptions dont reach the Handler.php like the CSRF 419 exception and the 404 page not found exception.

How to use report() function with a specific channel in Laravel?

You can define your log channels in config/logging.php as I have already done.
Then you can send a log to your new channel like this:
Log::channel('my-channel')->error('message');
Now, when I want to catch an exception without throwing it, I usually use the report() helper function like this:
try{
throw new Exception('message');
}catch(Exception $e){
report($e);
}
That is very useful for continuing a script after an exception is thrown, but it always logs the exception to my default logging channel.
I want to use the report() function and specify that it should be reported to my-channel.
I played around with app/Exceptions/Handler.php without any luck.
So far I decided to just make a simple class to emulate report() instead of trying to use it directly:
<?php
namespace App\Exceptions;
use Log;
use Throwable;
class Reporter
{
/**
* Reports an exception to a specific channel.
*
* #param Throwable $e
* #param string $channel
* #param array $context
*/
public static function report(Throwable $e, $channel = 'daily', $context = [])
{
Log::channel($channel)->error(
$e->getMessage(),
array_merge(
Context::getContext(),
$context,
['exception' => $e],
)
);
}
}
And you would use it like this:
try{
throw new Exception('message');
}catch(Exception $e){
Reporter::report($e, 'my-channel');
}
You can also pass extra context to the log message as an array.

How to delegate exception to global exception in 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

Override exception in handler

I am using Route model binding using the id of a product and my route looks like this:
Route::get('product/{product}', ProductController#index');
I also have a custom ProductNotFoundException which I want to return when the route model binding only for this model fails to get the row (the row does not exist or is soft deleted.)
Any ideas how I could achieve that?
The two solutions I thought of are:
Remove Route Model binding (obviously :P)
Override the exception in the Exceptions/Handler
I chose to go with the second and do the following
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
*
* #return \Symfony\Component\HttpFoundation\Response
* #throws ProductNotFoundException
*/
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException && $exception->getModel() === 'App\Models\Product') {
throw new ProductNotFoundException();
}
return parent::render($request, $exception);
}
However, I am not sure whether is a good practice to throw an exception in the exception handler.
Can anyone see any other way or advice of the above solution?
Customize the binding resolution in RouteServiceProvider:
Route::bind('product', function ($value) {
$product = Product::find($value);
if ($product === null) {
throw new ProductNotFoundException();
}
return $product;
});
Explicit Model Binding

Resources