CakePHP4 エラーメール処理

# CakePHP4におけるエラーメール処理機能の追加について

- エラー発生時にメールを配信する機能追加をおこなった。
- ErrorHandlerMiddleware をオーバーライドして対応。
- 404エラーはメール送信しない。

## ErrorHandlerMiddlewareをオーバーライド

### 新規ファイル追加

src/Middleware/ExtErrorHandlerMiddleware.php

### ExtErrorHandlerMiddlewareのソース

【概要】
- handleExceptionの最初にメール処理を組み込む
- メール不要なコンテンツはコードで判断して処理を行わないよにする。


```php
<?php
/** @noinspection PhpUnused */

namespace App\Middleware;

use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Chronos\Chronos;
use Cake\Mailer\Mailer;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

/**
 * Class ExtErrorHandlerMiddleware
 * @package App\Middleware
 */
class ExtErrorHandlerMiddleware extends ErrorHandlerMiddleware
{
    /**
     * @param Throwable $exception
     * @param ServerRequestInterface $request
     * @return ResponseInterface
     */
    public function handleException(Throwable $exception, ServerRequestInterface $request): ResponseInterface
    {
        $this->_sendMail($request, $exception);
        return parent::handleException($exception, $request);
    }

    /**
     * エラーメール送信処理
     * @param ServerRequestInterface $request
     * @param Throwable $exception
     */
    protected function _sendMail(ServerRequestInterface $request, Throwable $exception): void
    {
        //メール不要なコンテンツはここでブロックする。
        switch ($exception->getCode()) {
            case '400':
            case '403':
            case '404':
                return;
        }
        $message = $this->getMessage($request, $exception);
        $now = Chronos::now()->format('Y/m/d H:i:s');
        $Mailer = new Mailer();
        $Mailer->viewBuilder()
            ->setTemplate('system_error')
            ->setLayout('default');
        $Mailer->setFrom(env('SYSTEM_MAIL'))
            ->setViewVars(compact('message', 'now'))
            ->setTo(env('SYSTEM_MAIL'))
            ->setSubject('【' . env('APP_NAME') . '】 システムエラー報告')
            ->send();
    }

    /**
     * @param ServerRequestInterface $request
     * @param Throwable $exception
     * @return array
     */
    protected function getMessage(ServerRequestInterface $request, Throwable $exception): array
    {
        $results = [];
        $results['class'] = get_class($exception);
        $results['code'] = $exception->getCode();
        $results['file'] = $exception->getFile();
        $results['line'] = $exception->getLine();
        $results['message'] = $exception->getMessage();
        $results['request'] = $request->getAttributes();
        return $results;
    }
}
```

### メールのテンプレート
#### 新規ファイル追加
templates/email/text/system_error.php

#### テンプレートのソース
```php
<?php
/**
 * @var string $now
 * @var array $message
 */
?>
システムエラーが発生しました。

発生時刻：<?= $now . PHP_EOL; ?>
エラークラス：<?= $message['class'] . PHP_EOL; ?>
発生ファイル：<?= $message['file'] . PHP_EOL; ?>
行番号：<?= $message['line'] . PHP_EOL; ?>
エラーコード：<?= $message['code'] . PHP_EOL; ?>
エラーメッセージ：<?= $message['message'] . PHP_EOL; ?>
リクエストデータ:<?= print_r($message['request']) . PHP_EOL; ?>

```


## 設定ファイル調整

ErrorHandlerMiddlewareからExt ErrorHandlerMiddlewareに切り替える。

#### 対象ファイル
src/Application.php

#### 変更箇所

```php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
   {
       $middlewareQueue
           // Catch any exceptions in the lower layers,
           // and make an error page/response
           #【下記の行コメントアウト】 
           #->add(new ErrorHandlerMiddleware(Configure::read('Error')))
           #【下記の行を追加する】 
           ->add(new ExtErrorHandlerMiddleware(Configure::read('Error')))

           // Handle plugin/theme assets like CakePHP normally does.
           ->add(new AssetMiddleware([
               'cacheTime' => Configure::read('Asset.cacheTime'),
           ]))

           // Add routing middleware.
           // If you have a large number of routes connected, turning on routes
           // caching in production could improve performance. For that when
           // creating the middleware instance specify the cache config name by
           // using it's second constructor argument:
           // `new RoutingMiddleware($this, '_cake_routes_')`
           ->add(new RoutingMiddleware($this))

           // Add the middleware to the middleware queue.
           // Authentication should be added *after* RoutingMiddleware.
           // So that subdirectory information and routes are loaded.
           ->add(new AuthenticationMiddleware($this));

       return $middlewareQueue;
   }
```