Most of the time, Web developers don’t care about errors. If something goes wrong, you’ll often see the default Laravel default prompt, such as Whoops, something went wrong, or even worse, exception code, which doesn’t help the visitor at all. So I decided to write a step-by-step article on how to handle errors gracefully and provide appropriate error information to visitors.

Tip: This article also shows examples of using dependency injection to create your own service and handle exceptions thrown by the service.

Preparation: User search task

So we have a very simple example – find the user’s form by their ID.

Creating two Routes

Route::get('/users', 'UserController@index')->name('users.index');
Route::post('/users/search', 'UserController@search')->name('users.search');Copy the code

Create two new methods in the controller

class UserController extends Controller { public function index() { return view('users.index'); } public function search(Request $request) { $user = User::find($request->input('user_id')); return view('users.search', compact('user')); }}Copy the code

Finally, let’s complete the template file

// resources/views/users/index.blade.php

<form action="{{ route('users.search') }}" method="POST">
    @csrf

    <div class="form-group">
        <input name="user_id" type="text" id="user_id" placeholder="User ID" 
            class="form-control" value="{{ old('user_id') }}">
    </div>

    <input type="submit" class="btn btn-info" value="Search">
</form>
Copy the code

If we search for an existing user and find it, we see the following results:

Searching for file templates

// resources/views/users/search.blade.php:

<h3 class="page-title text-center">User found: {{ $user->name }}</h3>

<b>Email</b>: {{ $user->email }}
<br />
<b>Registered on</b>: {{ $user->created_at }}
Copy the code

Above, this is our ideal scenario. But what if you can’t find users?

Exception handling

Let’s get out of the world. We don’t check if the user exists, we just do this in Controller:

$user = User::find($request->input('user_id'));Copy the code

If the user doesn’t find it, we’ll see:

Of course, we could set the.env file with APP_DEBUG = false and the browser would just display blank Whoops, looks like something went wrong. But this still doesn’t provide any valuable information to our visitors.

Another quick fix we can do is to use User::findOrFail() instead of find() – then if the User can’t be found, Laravel will display a 404 page Sorry, the page you are looking for could not be found with text. However, this is the default 404 page for the entire project, so it’s not very helpful for the user, is it?

So we need to catch errors, process them, and then redirect to the form with an actually understandable error message.

We need to know the exception type and class name it will return. In the case of findOrFail(), it throws a Eloquent exception ModelNotFoundException, so we need to do this:

public function search(Request $request)
{
    try {
        $user = User::findOrFail($request->input('user_id'));
    } catch (ModelNotFoundException $exception) {
        return back()->withError($exception->getMessage())->withInput();
    }
    return view('users.search', compact('user'));
}Copy the code

Now, let’s actually display an error in Blade:


<h3 class="page-title text-center">Search for user by ID</h3>

@if (session('error'))
    <div class="alert alert-danger">{{ session('error') }}</div>
@endif

<form action="{{ route('users.search') }}" method="POST">
...Copy the code

The results of

Great, we’ll display an error message! But it’s still not ideal, right? Instead of $exception->getMessage(), we need to display our own message:


return back()->withError('User not found by ID ' . $request->input('user_id'))->withInput();
Copy the code

End result:

Move error message processing to the Service Service

Now we’ve taken a very simple example of an action in the controller – just find the user. In real applications, it gets more complicated, and often the controller is calling some external service or packaging method that can fail due to various errors.

Let’s create our own service that will essentially do the same thing, but throw an exception, so the controller doesn’t even need to know the message text.

Let’s move our logic to app/Services/UserService. The PHP


namespace App\Services;

use App\User;
use Illuminate\Database\Eloquent\ModelNotFoundException;

class UserService
{

    public function search($user_id)
    {
        $user = User::find($user_id);
        if (!$user) {
            throw new ModelNotFoundException('User not found by ID ' . $user_id);
        }
        return $user;
    }

}
Copy the code

In the Controller, we need to call this service. First, we inject it into the __construct() method:


use App\Services\UserService;

class UserController extends Controller
{

    private $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

// ...
Copy the code

If you are not familiar with dependency injection (DI) and how the Laravel IOC container works, please refer to the official documentation or good articles on it.

Now, here’s what our search() method looks like:


public function search(Request $request)
{
    try {
        $user = $this->userService->search($request->input('user_id'));
    } catch (ModelNotFoundException $exception) {
        return back()->withError($exception->getMessage())->withInput();
    }
    return view('users.search', compact('user'));
}
Copy the code

Note that we can use $Exception ->getMessage() again, and that all error validation or message logic occurs in the service – this is one of the purposes of separating these operations, and the controller should not perform it.

One step further: Create our own exception class

The last part of this article – even better architecture when a service throws an exception related to a particular error, and there may be multiple exception classes depending on the error.

So how do we create our own exception class? It’s easy, with the Artisan command:

php artisan make:exception UserNotFoundExceptionCopy the code

Here is that it will be in the app/Exceptions/UserNotFoundException PHP generated content:


namespace App\Exceptions;

use Exception;

class UserNotFoundException extends Exception
{
    //
}Copy the code

There’s nothing here, is there? Let’s populate our exception with some logic. This class can have two methods:

  • Report () If you want to do some extra logging, use Report (), such as sending errors to email, Slack, etc.
  • Render () if you want to redirect errors directly from the Exception class or return HTTP responses (such as your own)BladeFile), use render()

So, in this example, we fill in the render() method:

namespace App\Exceptions; use Exception; class UserNotFoundException extends Exception { /** * Report or log an exception. * * @return void */ public function report() { \Log::debug('User not found'); }}Copy the code

Finally, this is how we call this exception from the controller:


public function search(Request $request)
{
    try {
        $user = $this->userService->search($request->input('user_id'));
    } catch (UserNotFoundException $exception) {
        report($exception);
        return back()->withError($exception->getMessage())->withInput();
    }
    return view('users.search', compact('user'));
}
Copy the code

So, that’s one aspect I want to show you about exception handling and using Services.

Of course this example is very simple and others can use Exceptions in different ways, but I hope this article gives an overview of common Exceptions and why you should use them to display errors to visitors in an elegant way.

For more information on exception and error handling, check the official Laravel documentation