LaravelでSlackのSlash Commandを複数使う

#はじめに
[前回の記事](https://qiita.com/shindex512/items/5933cac813931eb36b91)で、Laravel Slack Slash Commandの大まかな説明をした。
インストールや簡単な使い方などを説明しているので、読んでいない人はそちらを先に読んでほしい。
今回は、複数のコマンドを扱う方法について説明する。

#今回の目標
<img width="1147" alt="スクリーンショット 2019-03-01 20.05.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/ecf2e259-87f4-6cc3-db30-fc4b9546c654.png">
と送信すると、
<img width="1149" alt="スクリーンショット 2019-03-01 20.08.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/1cf7a640-022b-3219-996d-da11b3530a23.png">
と応答してくれる。
***
<img width="1148" alt="スクリーンショット 2019-03-01 20.06.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/14dfd4a5-b407-b660-b844-d6f278240b11.png">
と送信すると、
<img width="1152" alt="スクリーンショット 2019-03-01 20.06.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/08c67286-3c59-6f51-f42d-4be9c7e8350f.png">
と応答してくれる。

#Slack側の設定
<img width="1033" alt="スクリーンショット 2019-03-01 20.11.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/91a119b6-906c-f68d-596d-822fca047cc7.png">
こんな感じで、2つのコマンド`/hello`と`/how-are-you?`を用意する。

###`/hello`
<img width="978" alt="スクリーンショット 2019-03-01 20.20.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/babcfb11-86a8-276f-4086-b32a9a84887b.png">

###`/how-are-you?`
<img width="985" alt="スクリーンショット 2019-03-01 20.20.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/0df4376a-2fb6-232e-4286-06415e2b2689.png">

2つのコマンドで、1つのURLを共有している。

#Laravel側の設定
今回の一番悩ましいところは、**2つのトークンをどう記述するか**だ。
トークンは前回同様、`config/laravel-slash-command.php`に記述する。
結論から言うと、トークンは**配列**で記述すると良い。

```php:config/laravel-slash-command.php
<?php

return [

  /*
   * At the integration settings over at Slack you can configure the url to which the
   * slack commands are posted. Specify the path component of that url here.
   *
   * For example `http://example.com/slack` you would put `slack` here.
   */
  'url' => 'slack',

  /*
   * The token generated by Slack with which to verify if a incoming slash command request is valid (deprecated).
   */
  'token' => ['/helloのトークン', '/how-are-you?のトークン'],      //コマンドのトークンを配列で記述する

  /*
   * The signing_secret generated by Slack with which to verify if a incoming slash command request is valid.
   */
  'signing_secret' => env('SLACK_SIGNING_SECRET'),

  /*
   * Verify requests from slack with signing_secret signature
   */
  'verify_with_signing' => false,

  /*
   * The handlers that will process the slash command. We'll call handlers from top to bottom
   * until the first one whose `canHandle` method returns true.
   */
  'handlers' => [
      //add your own handlers here
      App\SlashCommandHandlers\Hello::class,             //コマンド/helloの処理を書くクラス
      App\SlashCommandHandlers\HowAreYou::class,       //コマンド/how-are-you?の処理を書くクラス

      //this handler will display instructions on how to use the various commands.
      Spatie\SlashCommand\Handlers\Help::class,
 
      //this handler will respond with a `Could not handle command` message.
      Spatie\SlashCommand\Handlers\CatchAll::class,
  ],
];
```
つまり、今後コマンドを増やしたかったら、そのトークンを配列に入れていけば良いわけだ。
とっても楽で便利である。
前回はenv関数を使っていたが、それだと複数のトークンを扱えないのだ。

##なぜ配列で記述できるのか?
`$ php artisan route:list`
を実行すると、その中に
<img width="1351" alt="スクリーンショット 2019-03-01 21.03.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/349288/50045897-e501-960c-d086-2ba3e314e9aa.png">
があるように、
/slackにslackからリクエストがPOSTされると、画像にあるコントローラーのメソッドが呼ばれる事がわかる。
Spatieの[github](https://github.com/spatie/laravel-slack-slash-command/blob/master/src/Controller.php#L31-L84)か、下記のファイルを確認する。

```php:vender/spatie/laravel-slack-slash-command/src/Controller.php
//該当する部分のみを抜き出している
class Controller extends IlluminateController
{
    /** @var \Spatie\SlashCommand\Request */
    protected $request;

    /** @var \Illuminate\Support\Collection */
    protected $config;

    public function __construct(IlluminateRequest $request, Repository $config)
    {
        $this->request = Request::createFromIlluminateRequest($request);
        $this->config = collect($config->get('laravel-slack-slash-command'));
    }

    public function getResponse(IlluminateRequest $request): IlluminateResponse
    {
        $this->guardAgainstInvalidRequest($request);

        $handler = $this->determineHandler();

        try {
            if ($handler instanceof SignatureHandler) {
                $handler->validate();
            }
            $response = $handler->handle($this->request);
        } catch (SlackSlashCommandException $exception) {
            $response = $exception->getResponse($this->request);
        } catch (Exception $exception) {
            $response = $this->convertToResponse($exception);
        }

        return $response->getIlluminateResponse();
    }

    protected function guardAgainstInvalidRequest(IlluminateRequest $request)
    {
        if ($this->config->get('verify_with_signing')) {
            $this->verifyWithSigning($request);
        } else {
            $this->verifyWithToken($request);
        }
    }

    protected function verifyWithSigning(IlluminateRequest $request)
    {
        $signature = app(RequestSignature::class)->create($request);

        if ($request->header('X-Slack-Signature') !== $signature) {
            throw InvalidRequest::invalidSignature($signature);
        }
    }

    protected function verifyWithToken(IlluminateRequest $request)
    {
        if (! $request->has('token')) {
            throw InvalidRequest::tokenNotFound();
        }

        $validTokens = $this->config->get('token');

        if (! is_array($validTokens)) {
            $validTokens = [$validTokens];
        }

        if (! in_array($this->request->get('token'), $validTokens)) {
            throw InvalidRequest::invalidToken($this->request->get('token'));
        }
    }
```
いま、
`$this->config->get('token')` → `config/laravel-slack-slash-command`に書いたトークン
`$this->request->get('token')` → slack command の設定時に、slack側から発行されたトークン
となっている。

関数`verifyWithToken`では、`$this->config->get('token')`を`$validTokens`とした上で、

```php
if (! is_array($validTokens)) {
   $validTokens = [$validTokens];
}
```
**`$validTokens`が配列じゃないなら配列にする**という処理を行っている。
その後、

```php
if (! in_array($this->request->get('token'), $validTokens)) {
   throw InvalidRequest::invalidToken($this->request->get('token'));
}
```
`$this->request->get('token')`が配列`$validTokens`の中に存在するかを調べている。

つまり、`config/laravel-slack-slash-command`に記述するトークンは、最初から配列でも構わない。
というか、こんな処理を行うなら、配列で書くように示してくれれば良いのに…。

ちなみにだが、関数`getResponse`内の
`$this->guardAgainstInvalidRequest($request);`を消してしまえば、トークンの照合を行わないのだ。
そんなことしちゃだめだぞ。絶対だからな。絶対だぞ。

#classの書き方

```php:app/SlashCommandHandlers/Hello.php
<?php
namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;
use Spatie\SlashCommand\Handlers\BaseHandler;

class Hello extends BaseHandler
{
    /**
     * If this function returns true, the handle method will get called.
     *
     * @param \Spatie\SlashCommand\Request $request
     *
     * @return bool
     */
    public function canHandle(Request $request): bool
    {
        return $request->command == 'hello';
    }

    /**
     * Handle the given request. Remember that Slack expects a response
     * within three seconds after the slash command was issued. If
     * there is more time needed, dispatch a job.
     *
     * @param \Spatie\SlashCommand\Request $request
     *
     * @return \Spatie\SlashCommand\Response
     */
    public function handle(Request $request): Response
    {
        return $this->respondToSlack("Hello! I'm Laravel Slack Slash Command.");
    }
}
```

```php:app/SlashCommandHandlers/HowAreYou.php
<?php
namespace App\SlashCommandHandlers;

use Spatie\SlashCommand\Request;
use Spatie\SlashCommand\Response;
use Spatie\SlashCommand\Handlers\BaseHandler;

class HowAreYou extends BaseHandler
{
    /**
     * If this function returns true, the handle method will get called.
     *
     * @param \Spatie\SlashCommand\Request $request
     *
     * @return bool
     */
    public function canHandle(Request $request): bool
    {
        return $request->command == 'how-are-you?';
    }

    /**
     * Handle the given request. Remember that Slack expects a response
     * within three seconds after the slash command was issued. If
     * there is more time needed, dispatch a job.
     *
     * @param \Spatie\SlashCommand\Request $request
     *
     * @return \Spatie\SlashCommand\Response
     */
    public function handle(Request $request): Response
    {
        return $this->respondToSlack("I'm good!");
    }
}
```
クラスの細かい解説は[前回の記事](https://qiita.com/shindex512/items/5933cac813931eb36b91)に詳しく書いたので省略する。
関数`canHandle`で条件分岐を行っている感じだ。

#おわりに
仕組みを理解できれば、今後簡単にコマンドを増やすことができるだろう。
ご質問があればいつでもどうぞ。