laravel x lighthouseでErrorがハンドリングできない件

# はじめに
laravel x lighthouseで開発中に、デバッグメッセージが上手く表示されず困ったことがあった。その解決方法を残しておく。

| ライブラリ | バージョン |
|:-:|:-:|
|  laravel | 6.18   |
| nuwave/lighthouse | 4.16 |


# 事象

何かしらのエラーが起きたときのデバッグメッセージが次のようになってしまい、どこでどのようなエラーが起きたのかの情報が出力されない。

```json
    "message": "Argument 1 passed to App\\Exceptions\\Handler::report() must be an instance of Exception, instance of TypeError given, called in /var/www/vendor/nuwave/lighthouse/src/Execution/ReportingErrorHandler.php on line 25",
    "exception": "Symfony\\Component\\Debug\\Exception\\FatalThrowableError",
    "file": "/var/www/app/Exceptions/Handler.php",
    "line": 37,
    "trace": [
      {
...
```

# 原因

Laravelでのエラーハンドリングは全て`app/Exceptions/Handler.php`で行われる。lighthouseを使っている時のエラーハンドリングも同じである。
`Handler.php`の37行目を見ると、次のようなメソッドがある。ここが実際に例外からデバッグメッセージ記録する所になる。型宣言をみてわかる通り、このメソッドは`Exception`しか受け取れず、`Error`が渡されると上で述べたような事象が起きてしまう。

```php:app/Exceptions/Handler.php
...
    public function report(Exception $exception): void
    {
        parent::report($exception);
    }
...
```

lighthouseで発生した例外は`Nuwave\Lighthouse\Execution\ReportingErrorHandler`ミドルウェアを通して、`Handler`に渡される。
`handle`メソッド内の`$error->getPrevious()`の返り値が`Exception`ではなく`Error`であると、`Handler`の`report`メソッドに`Error`が渡ることになり、型エラーが発生してしまう。

```php
<?php

namespace Nuwave\Lighthouse\Execution;

use Closure;
use GraphQL\Error\Error;
use Illuminate\Contracts\Debug\ExceptionHandler;

/**
 * Report errors through the default exception handler configured in Laravel.
 */
class ReportingErrorHandler implements ErrorHandler
{
    public static function handle(Error $error, Closure $next): array
    {
        // Client-safe errors are assumed to be something that a client can handle
        // or is expected to happen, e.g. wrong syntax, authentication or validation
        if ($error->isClientSafe()) {
            return $next($error);
        }

        // TODO inject through constructor once handle is non-static
        /** @var \Illuminate\Contracts\Debug\ExceptionHandler $reporter */
        $reporter = app(ExceptionHandler::class);
        $reporter->report($error->getPrevious()); // @phpstan-ignore-line TODO remove when supporting Laravel 7 and upwards

        return $next($error);
    }
}
```


# 対処法

最新のLaravelでは`Handler`の`report`メソッドで`Throwable`を受け取れるようになっているので、Laravelのバージョンを上げてしまうのが一つの手である。それができない場合は、以下のように`ReportingErrorHandler`を継承したクラスを作成し、`Error`が渡ってきた時は無理やり`Exception`に変換してしまう方法がある。

```php
<?php

declare(strict_types=1);

namespace App\Exceptions;

use Closure;
use Error;
use GraphQL\Error\Error as GraphQLError;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Nuwave\Lighthouse\Execution\ReportingErrorHandler;
use RuntimeException;

class CustomReportingErrorHandler extends ReportingErrorHandler
{
    /**
     * {@inheritdoc}
     */
    public static function handle(GraphQLError $error, Closure $next): array
    {
        if ($error->isClientSafe()) {
            return $next($error);
        }

        /** @var \Illuminate\Contracts\Debug\ExceptionHandler $reporter */
        $reporter = app(ExceptionHandler::class);
        $previous = $error->getPrevious();

        if ($previous instanceof Error) {
            $previous = new RuntimeException('converted from error', 0, $previous);
        }
        $reporter->report($previous);
        return $next($error);
    }
}
```

そして、`config/lighthouse.php`を次のように書き換える。

```php:config/lighthouse.php
...

    /*
    |--------------------------------------------------------------------------
    | Error Handlers
    |--------------------------------------------------------------------------
    |
    | Register error handlers that receive the Errors that occur during execution
    | and handle them. You may use this to log, filter or format the errors.
    | The classes must implement \Nuwave\Lighthouse\Execution\ErrorHandler
    |
    */

    'error_handlers' => [
        \Nuwave\Lighthouse\Execution\ExtensionErrorHandler::class,
        // \Nuwave\Lighthouse\Execution\ReportingErrorHandler:class, <- コメントアウト
        \App\Exceptions\CustomReportingErrorHandler::class,       // <- 追加
    ],
...
```

あまり褒められた方法ではないかもしれないが、デバッグメッセージが全く出力されない状態よりはマシだと思う。