PHP
vagrant
bot
ngrok
linebot

Line Botをブレークポイント貼って快適デバッグ

LineやFacebookのBotに限らずWebhookを使うものってデバッグがしづらいと思います。
変数の中身をログに出して確認するためだけにHerokuに毎度コミット&プッシュするのも面倒です。
そこでリクエストを自分のローカル環境に向けてくれるサービスngrokとリモートデバッグ用に設定したVisual Studio CodeとXdebugでブレークポイントを貼りながらLineのBotをPHPで開発してデバッグをしたいと思います。
PHPは7.1.7、CentOSはCentOS Linux release 7.3.1611 (Core)です。

はじめに注意ですが、ブレークポイントを貼っていることでリトライのリクエストが何度もやってきてしまうということがあると思います。デバッグ対象のコードよりも先にレスポンスだけ返すなどの工夫をした方がいいかもしれません。

Vagrant上にBotが動く環境を作る

centos/7のボックスにはGuest Additionが最初からインストールされていないのでvagrant-vbguestプラグインをインストールしておきます。

ホストマシン
vagrant plugin install vagrant-vbguest

Vagrantfileは以下のような感じでいきます。

Vagrantfile
Vagrant.configure("2") do |config|

  config.vm.box = "centos/7"
  config.vm.network "forwarded_port", guest: 80, host: 8080
  # Xdebugのremote_hostで192.168.33.1を指定する
  config.vm.network "private_network", ip: "192.168.33.10"
  config.vm.synced_folder ".", "/vagrant", type: "virtualbox"

  config.vm.provision "shell", inline: <<-SHELL
    # 共有フォルダ使いたいのでselinuxはpermissiveかdisabledにする
    setenforce 0
    sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config
    yum -y install epel-release
    yum -y localinstall http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
    yum -y install httpd
    yum -y --enablerepo=remi-php71 install php
    rm -rf /var/www/html
    ln -fs /vagrant/htdocs /var/www/html
    groupadd webdeveloper
    usermod -aG webdeveloper apache
    usermod -aG webdeveloper vagrant
    # error_logで/var/log/httpd/php_logに書き込むように設定
    chown root:webdeveloper /var/log/httpd
    chmod g+x /var/log/httpd
    touch /var/log/httpd/php_log
    chown apache /var/log/httpd/php_log
    sed -i "s|;error_log = syslog|&\\nerror_log = /var/log/httpd/php_log|" /etc/php.ini
    systemctl start httpd
    systemctl enable httpd
  SHELL

  config.vm.provision "shell", run: "always", inline: <<-SHELL
    # httpdが自動起動される時に共有フォルダがマウントされていないので再起動
    systemctl restart httpd
  SHELL

end

今回デバッグする対象のPHPファイルをホスト側のVagrantfileの置いてあるフォルダの下にhtdocs/index.phpとして保存しておきます。

htdocs/index.php
<?php

$jsonString = file_get_contents('php://input');
$events = json_decode($jsonString, true)['events'];

foreach ($events as $event) {
  $ch = curl_init('https://api.line.me/v2/bot/message/reply');
  curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => [
      'Content-Type: application/json',
      'Authorization: Bearer アクセストークンの文字列'
    ],
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POSTFIELDS => json_encode([
      'replyToken' => $event['replyToken'],
      'messages' => [
        [
          'type' => 'text',
          'text' => 'Hello World'
        ]
      ]
    ]),
  ]);
  $res = curl_exec($ch);
  curl_close($ch);
  error_log($res);
}

このVagrantfileではhtdocsが無いとvagrant upする時にこけるので注意です。
ゲストマシンを起動します。

ホストマシン
vagrant up

ngrokに登録してWebhookをVagrant上のサーバーへ通す

ngrokのインストール、登録や詳しい使い方は他の方の記事を見て下さい。
以下のコマンドでローカルのポート8080へ向けてくれるngrokのアドレスを作ってもらいます。

ホストマシン
ngrok http 8080

ngrokの起動画面、httpsの方のURLをWebhookに使って下さい

表示されたhttpsの方のアドレスをLineBotの設定画面のWebhook URLに設定します。
VERIFYボタンを押してSuccessと出ることを確認します。

XdebugとVisual Studio Codeでブレークポイントを貼ってBotをデバッグ

ゲストマシンに入ってXdebugをインストールします。

ゲストマシン
[vagrant@localhost ~]$ sudo yum -y --enablerepo=remi-php71 install php-pecl-xdebug

xdebug.iniの該当項目を以下のように設定します。

ゲストマシン
[vagrant@localhost ~]$ sudo vi /etc/php.d/15-xdebug.ini
/etc/php.d/15-xdebug.ini
xdebug.remote_enable = 1
xdebug.remote_port = 9000
xdebug.remote_autostart = 1
xdebug.remote_connect_back = 0;デフォルトが0なので通常設定しなくてOK
xdebug.remote_host = 192.168.33.1

xdebug.remote_connect_backはオンにしているところをよく見かけますが今回の場合ngrokを使っているのでそれはオフにしてxdebug.remote_hostでホストマシンを指定して下さい。
xdebug.remote_connect_backをオンにする場合はngrokを有料プランにするなどしてhttpだけではなく別のポート(今回の場合ngrok tcp 9000として)もローカルに向ける必要があります。

httpdを再起動します。

ゲストマシン
[vagrant@localhost ~]$ sudo systemctl restart httpd

VSCodeにPHP Debugというプラグインをインストールします。
インストールしたらVSCodeのデバッグタブ(左の虫のボタン)を開いてAdd Configurationを選んで
launch.jsonに以下の設定を追加します。

launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Remote Debug",
      "type": "php",
      "request": "launch",
      "serverSourceRoot": "/vagrant/htdocs",
      "localSourceRoot": "${workspaceRoot}/htdocs",
      "port": 9000
    }
  ]
}

vagrant upするフォルダでVSCodeを開いてlaunch.jsonを作った場合は上の設定で、htdocsを開いて作った場合は"localSourceRoot": "${workspaceRoot}"として下さい。

適当にブレークポイントを貼ってみます。

ブレークポイントを貼って変数をみる手順、VSCodeの記事などが詳しく書いてあると思います

FacebookのBotだとブレークポイントでずっと止めておくと次々とリトライが来てしまうので気をつけましょう。
先にWebhookのレスポンスだけ処理の先頭で返してset_time_limit(0)ignore_user_abort(true)などで残りの処理をするという方法があると思いますが開発時にだけにしておいたほうがいいでしょう。

少々めんどくさい部分もあって快適とは言い辛いかもしれませんがFacebookのBotや他のWebhookの開発でも同じようにできるのでおすすめです。

参考サイト

ngrokを使用してローカル環境を外部に公開する
Vagrant VirtualBoxでの共有フォルダのエラーについて
Vagrantのup時、httpdが自動起動しないとき