در مقاله فعلی ارورهای لاراول و مدیریت ارورها و چگونگی پیاده سازی آن را بررسی کردیم. چگونه ارورها را پیدا کنید ، با آنها چگونه برخورد کنید و چگونه آنها را منحصر به فرد حل کنید.

بیشتر اوقات برنامه نویسان به ارور ها اهمیتی نمیدهند. اگر اتفاق بدی رخ داد شما معمولا همان ارور همیشگی لاراول را میبینید :

“Whoops, something went wrong”

یا حتی بدتر از این قسمتی از کد که منجر به ارور شده است نمایان میشود، که به هیچ عنوان برای بازدید کننده سایت جذاب نیست. به همین دلیل من تصمیم گرفتم در یک مقاله قدم به قدم در مورد نحوه برخورد با ارور و نمایش عنوان مناسب به بازدید کننده به شما توضیح دهم .

آمادگی : کاربر یک موضوع را سرچ میکند 


در اینجا ما یک مثال خیلی ساده داریم : یک فرم برای جستجو کاربر بر اساس id
مدیریت ارورهای لاراول Laravel Exceptions

ما دو route داریم :

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


و یک 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'));
    }

}


در نهایت 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>


اگر ما یک کاربر را جستجو کنیم و او را پیدا کنیم نتیجه زیر به نمایش در میاید :
مدیریت ارورهای لاراول Laravel Exceptions
همه آن چیزی که در 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 }

بررسی Exception

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


خب این برای زمانی که همه چیز خوب پیش برود ، حالا اگر کاربر پیدا نشد چه اتفاقی می افتد:
مدیریت ارورهای لاراول Laravel Exceptions

یا البته میتوانیم در قسمت .env بخش APP_DEBUG=false قرار بدهیم تا مرورگر فقط یک صفحه سفید با پیام Whoops , looks like something went wrong را نمایش دهد . اما هیچ کدام این دو پیام باارزشی برای کاربر نخواهد بود.
یک راه سریع دیگر استفاده از User:findOrFail() به جای find() است در این حالت اگر کاربری پیدا نشد لاراول صفحه 404 را به نمایش خواهد گذاشت با چنین متنی : “Sorry, the page you are looking for could not be found.” اما چنین متنی برای تمامی ارورهای یک سایت قابل قبول نخواهد بود .
برای همین نیاز است ما ارورها را گرفته و با ریدایرکت به صفحه مورد نظر پیام درست را به کاربر نمایش دهیم.
برای اینکار ما باید نوع ارور و نام کلاس مربوط به آن را بدانیم برای مثال در مورد findOrFail() ارور از نوع Eloquent exception،ModelNotFoundException خواهد بود.

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'));
}


حالا بیاید ارور را در 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">
...


نتیجه :
مدیریت ارورهای لاراول Laravel Exceptions
خیلی خوب ، حالا ما پیام ارور را به نمایش گذاشته ایم اما هچنان عالی نیست نه ؟
به جای $exception->getMessage()  ما باید پیام خود را قرار دهیم :

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


نهایتا :
مدیریت ارورهای لاراول Laravel Exceptions
جابجایی پیام ارور به سرویس :


تا الان ما یک مثال خیل ساده از یک action در کنترلر زدیم  برای یافتن یک کاربر  اما در پروژه های واقعی موضوع پیچیده تر از این خواهد بود ،معمولا کنترلر یک پکیج یا سرویس را صدا میکند که در این حالت امکان برخورد با انواع ارورها وجود خواهد داشت . 
حالا بیاید ما نیز سرویس مخصوص خود را بوجود بیاوریم تا کنترلر حتی مجبور نباشد از نوع پیام ارور مطلع باشد :
بیاید منطق کد خود را در این قسمت قرار دهیم : app/Services/UserService.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;
    }

}


حالا در کنترلر این سرویس را در __construct() صدا میکنیم :

use App\Services\UserService;

class UserController extends Controller
{

    private $userService;

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

// ...


اگر با مبحث dependency injection آشنایی ندارید میتوانید از مستندات لاراول یا از این مقاله مفید استفاده کنید .
حالا متد search() اینگونه خواهد بود :

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'));
}


حالا اگر دقت کنید ما دوباره میتوانیم از $exception->getMessage() و  منطق همه پیام های ارور و  اعتبارسنجی ها در سرویس اتفاق می افتد، این یکی از اهداف است : جدا کردن action هایی که کنترلر نباید با آن درگیر شود


یک قدم فراتر : کلاس ارور مخصوص خود را بوجود اورید


بهتر از چیزی که گفته شد زمانی است که سرویس شما ارور به خصوص خود را نمایش دهد و حتی میشود برای یک ارور کلاس های مختلفی میتوانند به کار برده شوند . یک مثال خوب از چنین نوع معماری کتابخانه stripe است استفاده از آن به صورت زیر است :

try {
  // Use Stripe's library to make requests...
} catch(\Stripe\Error\Card $e) {
  // Since it's a decline, \Stripe\Error\Card will be caught
  $body = $e->getJsonBody();
  $err  = $body['error'];

  print('Status is:' . $e->getHttpStatus() . "\n");
  print('Type is:' . $err['type'] . "\n");
  print('Code is:' . $err['code'] . "\n");
  // param is '' in this case
  print('Param is:' . $err['param'] . "\n");
  print('Message is:' . $err['message'] . "\n");
} catch (\Stripe\Error\RateLimit $e) {
  // Too many requests made to the API too quickly
} catch (\Stripe\Error\InvalidRequest $e) {
  // Invalid parameters were supplied to Stripe's API
} catch (\Stripe\Error\Authentication $e) {
  // Authentication with Stripe's API failed
  // (maybe you changed API keys recently)
} catch (\Stripe\Error\ApiConnection $e) {
  // Network communication with Stripe failed
} catch (\Stripe\Error\Base $e) {
  // Display a very generic error to the user, and maybe send
  // yourself an email
} catch (Exception $e) {
  // Something else happened, completely unrelated to Stripe
}


خب چگونه میتوانیم کلاس مربوط به ارور مخصوص خودمان را بسازیم ؟ ساده است ، یک artisan command

php artisan make:exception UserNotFoundException


چنین چیزی ساخته میشود : app/Exceptions/UserNotFoundException.php

namespace App\Exceptions;

use Exception;

class UserNotFoundException extends Exception
{
    //
}


چیزی اینجا نیست نه؟ خب بیاید با مقداری منطق کلاس خود را کامل کنیم. در متد در این کلاس قابل استفاده هستند:
report() : اگر شما بخواید کار بیشتری روی ارور انجام دهید مانند ایمیل کردن آن و...
render() : زمانی استفاده میشود که شما میخواهید ارور را به یک صفحه خاص ریدایرکت کنید .

بنابراین برای مثال مورد نظر ما بخش render() را کدنویسی میکنیم :

namespace App\Exceptions;

use Exception;

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


نهایتا به این صورت در کنترلر فراخوانی میکنیم :

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'));
}

خب این همه آن چیزی بود که من میخواستم راجع به  Exception handling و البته سرویس ها به عنوان یک مسله جانبی به شما اموزش دهم.

این مسله که مثال بالا شاید خیلی ساده بود را درک میکنم اما امیدوارم این مقاله درباره چرایی استفاده از error handling و نمایش پیام مناسب به کاربر به شما کمک کرده باشد .
بیشتر در مورد Error Handling

لاراول
فقط
خوش آمدید!
ایجاد حساب کاربری