LaravelのExceptionをSlackで通知する

## 概要
Laravelの通知と例外処理の勉強のために実装してみました。

Notificationに関する記事は数多くあり、公式ドキュメントでも解説されていますが、備忘録としてかんたんな流れを残しておきます。

## 環境
- Laravel 6.0
- PHP 7.2

## 事前準備
Guzzleとslack-notification-channelのインストール

```bash
$ composer install guzzlehttp/guzzle
$ composer install laravel/slack-notification-channel
```

## SlackのWebhookURLを取得する
今回はenvの値をconfig経由で使用します。
webhookURL取得手順については割愛しますね...

```php:config\slack.php
return [
    'name' => env('SLACK_NAME'),
    'channel' => env('SLACK_CHANNEL'),
    'webhook_url' => env('SLACK_WEBHOOK_URL'),
];
```
## 通知用クラスの作成
ひとまずslackにメッセージを送信できるように実装します。
初期状態ではメール送信がデフォルトになっているので、slack用に変更してあげます。

```php:App\Notifications\Slack.php;
<?php

namespace App\Notifications;

use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;

class Slack extends Notification
{
    use Queueable;

    protected $name;
    protected $channnel;
    protected $content;
     

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($message)
    {
        $this->name = config(slack.name);
        $this->channel = config(slack.channnel);
        $this->content = $message;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['slack'];
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }

    /**
     * @param $notifiable
     * @return $this
     */
    public function toSlack($notifiable)
    {
        return (new SlackMessage)
            ->from($this->name)
            ->to($this->channel)
            ->content($this->content);
    }
}
```
コンストラクタでmessageを受け取り、toSlack()でそのまま送信するシンプルな処理です。
通知を配信するチャンネルをslackにするため、via()の中を'slack'に変更します。

## Slack通知用のルートを作成する
```php:App\Service\Slack\SlackNotifiable.php
<?php

namespace App\Service\Slack;

use Illuminate\Notifications\Notifiable;
use App\Notifications\SlackNotification;
use Exception;
use Illuminate\Notifications\RoutesNotifications;

class SlackNotifiable
{
  use Notifiable;

  protected function routeNotificationForSlack()
  {
    return config('slack.webhook_url');
  }
}
```
実際にSlack通知をルートするために、routeNotificationForSlack()を定義しWebhookURLを返しています。
Notifiableトレイトを読み込み、notify()で通知を送信できます。

これでSlackにメッセージを送信する準備はOKです！

## 例外処理に通知を組み込む
Laravelのエラーハンドリングは基本的にapp\Exceptions\Handler.phpが担っています。
ざっくりした流れとしては

1. report()で例外をキャッチ
2. 親クラスであるIlluminate\Foundation\Exceptions\Handler.phpでrenderレスポンスのために条件分岐
     ログインエラーなのか,バリデーションエラーなのか等々...
3. render()でレスポンスを返す。

今回は、例外検知時に通知を送りたいので、report()を編集します。

```php:App\ExceptionsHandler.php
public function report(Exception $exception)
    {
        $slackHook = new SlackNotifiable();
        $slackHook->notify(new Slack($exception));
        parent::report($exception);
    }
```
SlackNotifiableのインスタンスを作成し,notify()メソッドを実行することで通知を送っています。
引数にキャッチした例外$exceptionを渡しています。

## Slack通知用クラスの修正
最後にApp\Notifications\Slack.phpで例外内容を通知するように修正します。

```php:App\Notifications\Slack.php;
<?php

namespace App\Notifications;

use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;

class Slack extends Notification
{
    use Queueable;

    protected $name;
    protected $channnel;
    protected $exception;
     

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct(Exception $e)
    {
        $this->name = config(slack.name);
        $this->channel = config(slack.channnel);
        $this->exception = $e;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['slack'];
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
    /**
     * @param $notifiable
     * @return $this
     */
    public function toSlack($notifiable)
    {
        $exception = $this->exception;

        return (new SlackMessage)
            ->from($this->name)
            ->to($this->channel)
            ->error()
            ->content('エラーを検知しました')
            ->attachment(function ($attachment) use ($exception) {
                $attachment
                    ->title(get_class($exception))
                    ->content($exception->getMessage());
            });
    }
}
```
コンストラクタでExceptionを受け取るように修正しています。

これでエラー内容をSlackで確認することができるようになりました！
<img width="600" alt="スクリーンショット 2020-05-19 2.04.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636972/cc3adcb7-366a-342c-4915-df4c61358c9e.png">



## おわりに
最後まで読んでくださってありがとうございました。
至らぬ点など有りましたら、コメントで指摘して頂けるとありがたいです...

Laravelの通知処理を理解するのに良い勉強になりました。
次はキューとか使った非同期処理もやってみたいなぁ。

## 参考記事
- https://tsyama.hatenablog.com/entry/laravel-slack-notification
- https://qiita.com/kd9951/items/b1bccc4666976ec90dcc