Laravel : Handle findOrFail( ) on Fail - php

I am looking for something which can be like findOrDo(). Like do this when data not found. Something could be like
Model::findOrDo($id,function(){
return "Data not found";
});
Is there any similar thing in laravel that I can do this elegantly and beautifully ?
*I tried googling but could not find one

use Illuminate\Database\Eloquent\ModelNotFoundException;
// Will return a ModelNotFoundException if no user with that id
try
{
$user = User::findOrFail($id);
}
// catch(Exception $e) catch any exception
catch(ModelNotFoundException $e)
{
dd(get_class_methods($e)); // lists all available methods for exception object
dd($e);
}

Another option is to modify the default Laravel Exception Handler, found in app/Exceptions/Handler.php on the render() function I made this change:
public function render($request, Exception $e)
{
if(get_class($e) == "Illuminate\Database\Eloquent\ModelNotFoundException") {
return (new Response('Model not found', 400));
}
return parent::render($request, $e);
}
That way instead of getting a 500, I send back a 400 with a custom message without having to do a try catch on every single findOrFail()

By default, when you use an Eloquent model’s findOrFail in a Laravel 5 application and it fails, it returns the following error:
ModelNotFoundException in Builder.php line 129:
'No query results for model [App\Model]'.
So to catch the exception and display a custom 404 page with your error message like "Ooops"....
Open up the app/Exceptions/Handler.php file, and add the code shown below to the top of the render function:
public function render($request, Exception $e)
{
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException)
{
abort(404, 'Oops...Not found!');
}
return parent::render($request, $e);
}
Source: https://selftaughtcoders.com/from-idea-to-launch/lesson-16/laravel-5-findorfail-modelnotfoundexception-show-404-error-page/

An alternative process could be to evaluate a collection instead. So,
$modelCollection = Model::where('id', $id)->get();
if(!$modelCollection->isEmpty()) {
doActions();
}
I agree it isn't as elegant, a one-liner or as case specific as you or I might like, but aside from writing a try catch statement every time, it's a nice alternative.

as of Laravel v5.7, you can do this (the retrieving single model variation of #thewizardguy answer)
// $model will be null if not found
$model = Model::where('id', $id)->first();
if($model) {
doActions();
}

A little later for the party, from laravel 5.4 onward, Eloquent Builder supports macros. So, I would write a macro (in a separate provider) like follows.
Builder::macro('firstOrElse', function($callback) {
$res = $this->get();
if($res->isEmpty()) {
$callback->call($this);
}
return $res->first();
});
I can then do a retrieval as follows.
$firstMatchingStudent = DB::table('students')->where('name', $name)
->firstOrElse(function() use ($name) {
throw new ModelNotFoundException("No student was found by the name $name");
});
This will assign the first object of the result set if it is not empty, otherwise will adapt the behaviour I pass into the macro as a closure (throw Illuminate\Database\Eloquent\ModelNotFoundException in this case).
For the case of a model also this would work, except that models always return the builder instance.

In the newer version of Laravel (for me in v9.41.0) we have the Register method in Handler.php ,
so for customize the exception of findOrFail method we have to add this code to that file:
$this->renderable(function (NotFoundHttpException $exception, $request) {
if ($request->expectsJson()) {
//when you need it for API
return response()->json([
'responseCode'=> 404,
'message' => "Item not found",
'errorCode' => 1000404
], 404 );
}else{
return view("some-custom-view");
}
});
I needed NotFoundHttpException which has the namespace below (you can use any other Exception for customization) and have to be used out of the class:
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

Related

How to report ModelNotFoundException with stacktrace to Sentry.io in Laravel 6?

I am using below laravel 6 code to report exceptions to Sentry.io:
public function report(Exception $exception)
{
if ($this->shouldReport($exception) && app()->bound('sentry')) {
app('sentry')->captureException($exception);
}
parent::report($exception);
}
All the exceptions are able to report to sentry. However recently I found that ModelNotFoundException is not sending to Sentry. Also I found the reason and it is due to the presence of ModelNotFoundException in the below code of the file (Illuminate\Foundation\Exceptions\Handler.php):
protected $internalDontReport = [
AuthenticationException::class,
AuthorizationException::class,
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
SuspiciousOperationException::class,
TokenMismatchException::class,
ValidationException::class,
];
As inbuilt code and I cannot remove ModelNotFoundException in the above code.
Is there a way to send the ModelNotFoundException to sentry?
The $internalDontReport is protected so you could override it, although it's probably better not too since the list might change in the future and would require you to keep it in sync.
You can remove the $this->shouldReport($exception), however this will cause all of those ignored exception to be reported which might not be what you want.
You could do something like this in case you only care about the ModelNotFoundException:
protected $doReportToSentry = [
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
];
public function report(Exception $e)
{
if ($this->shouldReportToSentry($e) && app()->bound('sentry')) {
app('sentry')->captureException($e);
}
parent::report($e);
}
protected function shouldReportToSentry(Exception $e)
{
if ($this->shouldReport($e)) {
return true;
}
return !is_null(\Illuminate\Support\Arr::first($this->doReportToSentry, function ($type) use ($e) {
return $e instanceof $type;
}));
}
You can add to that $doReportToSentry array any class you want to make sure it is reported even though Laravel might (by default) ignore it.
Had a similar problem myself where findOrFail was return 404 responses when a model that should always exist is not found. I wanted it to return a 500 and log it instead so I can be aware of the database inconsistency.
My solution was to override a couple of methods in App\Exceptions\Handler.php like so:
protected function shouldntReport(Throwable $e): bool
{
$shouldntReport = parent::shouldntReport($e);
return $e instanceof ModelNotFoundException ? false : $shouldntReport;
}
protected function prepareException(Throwable $e): Throwable
{
if ($e instanceof ModelNotFoundException) {
return $e;
}
return parent::prepareException($e);
}
The first override shouldntReport causes the error to be written to the log.
The second override prepareException causes a 500 error to be returned instead of a 404.
If you're fine with a 404 error and just want it logged (and sent to Sentry), feel free to only override the first method.
Keep in mind that if you do this you will need to check if models exist and return 404s manually when you need to. But this will avoid, for example, the problem of a missing subentity returning a 404 and making it look like the root entity is missing.
For example if you expect all users to have a relationship to a role model, and one user doesn't have a role, my changes make it so that Laravel doesn't return a 404 error. This is a database inconsistency and should be a 500 error. A 404 error would make it look like the user wasn't found, which is false. It's the role, not the user, that wasn't found.

How to return custom response for `Illuminate\Support\ItemNotFoundException`?

In my controller, I used the method firstOrFail() for a database query with Eloquent. When in debug mode, I get a Laravel error message with the content Illuminate\Support\ItemNotFoundException.
However, I'd like to return a redirect() or mabye back() instead of showing this error screen. How can I do that?
use php try catch, to override the behaviour of error catching,
try {
// your functional code
} catch (Illuminate\Support\ItemNotFoundException $exception ) {
//your redirect command , the code here will be executed if there is an exception with type ItemNotFoundException
}
if you want to catch all errors, you need to use the General Exception class which is the parent of all Exceptions
try {
// your functional code
} catch (\Exception $exception) {
//your redirect command , the code here will be executed if there is any exception
}
if you want to get the exception message to log it or any customization , you can use the method : getMessage() , in our case will be $exception->getMessage()
Instead of firstOrFail() use first() and use condition to redirect back, for example:
$item = Item::where('slug', $slug)->first();
if (! $item) {
return redirect()->back();
}

Prevent laravel message to be displayed and redirect the user to custom page issue in Laravel 5

I would like to catch somehow the laravel error, warning message. I don't want to disable them from the config/app.php file. I am using monolog to log some information.
This is my piece of code:
public function view($id){
try {
$tag = Tags::find(12313); // tags is a model
}catch(Exception $error){
echo 'error'; exit();
$this->log->logMessage(Logger::ERROR, $error->getMessage());
return redirect()->route('admin.tags')->with(['msg' => 'Smth went wrong']);
}
}
$this->log is a class where I am using the monolog class to log information.
The fact is that right now , it doesn't go to the catch part . I don't get the error message. I'm getting this message from laravel:
Trying to get property of non-object (View: ......
I intentionally put the number 12313 there to see if it is working or not. And for some reason is not working and I am not redirected . The idea, if something happened I want to redirect the user to a specific page with a general error message. How can I achieve that ?
You can do it in laravel .You can handle erors in App\Exceptions\Handler class
public function render($request, Exception $exception)
{
if($exception instanceof NotFoundHttpException)
{
return response()->view('errors.404', [], 404);
}
if ($exception instanceof MethodNotAllowedHttpException)
{
return response()->view('errors.405', [], 405);
}
if($exception instanceof MethodNotAllowedHttpException)
{
return response()->view('errors.404', [], 405);
}
return parent::render($request, $exception);
}
find() method doesn't throw an exception if the record is not found. So do this instead:
public function view($id)
{
$tag = Tags::find(12313); // tags is a model
if (is_null($tag)) {
$this->log->logMessage(Logger::ERROR, $error->getMessage());
return redirect()->route('admin.tags')->with(['msg' => 'Smth went wrong']);
}
}
Or use findOrFail() which will throw an exception if specified record is not found.
Sometimes you may wish to throw an exception if a model is not found. This is particularly useful in routes or controllers. The findOrFail and firstOrFail methods will retrieve the first result of the query; however, if no result is found, a Illuminate\Database\Eloquent\ModelNotFoundException will be thrown

How to catch PostTooLargeException in Laravel?

To reproduce the error, simply upload a file(s) to any POST routes in Laravel that exceeds the post_max_size in your php.ini configuration.
My goal is to simply catch the error so I can inform the user that the file(s) he uploaded is too large. Say:
public function postUploadAvatar(Request $request)
try {
// Do something with $request->get('avatar')
// Maybe validate file, store, whatever.
} catch (PostTooLargeException $e) {
return 'File too large!';
}
}
The above code is in standard Laravel 5 (PSR-7). The problem with it is that the function can't execute once an error occurs on the injected request. Thereby can't catch it inside the function. So how to catch it then?
Laravel uses its ValidatePostSize middleware to check the post_max_size of the request and then throws the PostTooLargeException if the CONTENT_LENGTH of the request is too big. This means that the exception is thrown way before it even gets to your controller.
What you can do is use the render() method in your App\Exceptions\Handler e.g.
public function render($request, Exception $exception)
{
if ($exception instanceof PostTooLargeException) {
return response('File too large!', 422);
}
return parent::render($request, $exception);
}
Please note that you have to return a response from this method, you can't just return a string like you can from a controller method.
The above response is to replicate the return 'File too large!'; you have in the example in your question, you can obviously change this to be something else.
Hope this helps!
You can also redirect to a Laravel view of your choice if you wish to add a more content richer response to the user.
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Http\Exceptions\PostTooLargeException)
return response()->view('errors.post-too-large');
return parent::render($request, $exception);
}
ALSO NOTE:
For the exception to be caught you should make sure you provide the proper path to the PostTooLargeException by either importing the class using the use Illuminate\Http\Exceptions\PostTooLargeException; import statement or just writing the full path like in my example.
The exception is rendered before the Session starts so you cannot redirect back with an error message.
You can create a new blade file and redirect there like this:
public function render($request, Throwable $exception)
{
if ($exception instanceof \Illuminate\Http\Exceptions\PostTooLargeException) {
return $this->showCustomErrorPage();
}
return parent::render($request, $exception);
}
protected function showCustomErrorPage()
{
return view('errors.errors_page');
//you can also return a response like: "return response('File too large!', 422);"
}
To show a static message using sessions:
1.You have to move the ValidatePostSize class from the middleware array into the middlewareGroups array, directly after the StartSession.
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
After that in Handler.php you can do:
public function render($request, Throwable $exception)
{
if ($exception instanceof \Illuminate\Http\Exceptions\PostTooLargeException) {
return $this->showCustomErrorPage();
}
return parent::render($request, $exception);
}
protected function showCustomErrorPage()
{
return \Illuminate\Support\Facades\Redirect::back()->withErrors(['max_upload' => 'The Message']);
}
Show the message on your controller like this:
#error('max_upload')
<div class="alert" id="create-news-alert-image">{{ $message }}</div>
#enderror

Laravel 5 - try catch exception not working

I am new to laravel5 and this code fails to catch all the exceptions.
I don't know what's wrong, please help.
public function delete($id)
{
$sql = $this->deleteSql();
DB::beginTransaction();
try {
$deleteData = Db::delete($sql, ['id' => $id]);
if (!$deleteData) {
return false;
}
return true;
} catch (\Exception $e) {
DB::rollback();
return $e->getMessage();
}
DB::commit();
}
It will give me :
Illuminate\Database\QueryException: SQLSTATE[22P02]:
and
Caused by
PDOException: SQLSTATE[22P02]:
Your method is not going to catch block at all since it is returning bool from try block.
Also I don't know what made you use try...catch block.
I more error:
Db::delete($sql, ['id' => $id]);
If you have defined Db, then it is okay. But if not, then it will throw error.
Also there is no need to use transactions, unless you are testing your application with any PHP Testing tool like phpUnit / phpSpec, etc.
Solution:
There is no need to use try..catch block at all.
Just call the delete method on the model that you wish to delete, and you will be done, like so:
public function delete($id)
{
YourModel::delete($id); // Replace YourModel with the model you wish to delete
\Session::flash('delete_message', 'Model has been successfully deleted.');
return redirect()->back(); // or anywhere else of your choice
}
Hope this helps you out. Happy Coding. Cheers.

Categories