GoogleAppEngineとFuelPHPでCronを設定し無料でTwitterBotを作ってみよう

# 概要

[GoogleAppEngineでFuelPHP（と、ついでにGoogleCloudSQL）をカジュアルに使ってみよう](http://qiita.com/HikaruYokogoshi/items/29d583f242c6f0407ab3)
を読んでいただいた方向けです。先に↑の記事を読んでいただくことを推奨します！
（GoogleAppEngine（以下GAE）のcron設定方法だけ知りたい方は読まなくても大丈夫です

GAEでのCron設定方法とそれを使って適当なことをつぶやくTwitterBotを作成してみます。
今回はGoogleCloudSQLは使わないので完全に無料でTwitterBotを作れます！お財布に優しいですね！

# 解説手順

- dispatch.yamlとcron用ルーティングファイルの作成
- バックエンドインスタンスの説明
- Twitterライブラリの導入
- 実装＆テスト方法
- デプロイ

# dispatch.yamlとcron用ルーティングファイルの作成

設定に当たっては
[GAE、Modulesの設定 ②](http://blog1.erp2py.com/2015/06/gaemodules_24.html)
を参考にさせていただきました。ありがとうございます。

dispatch.yamlを設置するとURLで起動サーバを分岐させることが出来ます。
「起動サーバを分岐させる」という言葉に疑問が出てくるかもしれません。
が、今はあまり気にしないでください。この後も説明がちょっとわかりにくいかもしれないのですが、完成してしまえばなんとなくしくみを理解できるはずです。
dispatch.yamlを設置せずcronを置くことも一応可能ですが、cronから起動したスクリプトが60秒で強制終了してしまいます。
この作業を行うとcronの制限時間を無制限に伸ばせます。そのためにやっている作業なんだ…と思っておいてください。

今回設置するdispatch.yamlを見てみましょう。
この設定は
https://[プロジェクトID].appspot.com/_ar/*
https://[プロジェクトID].appspot.com/cron/*
というURLそれぞれに「back-job」というmoduleを割り当てている事になります。
moduleに関しては後ほど解説します。

```yaml:/path/to/fuelphp/dispatch.yaml
dispatch:
  # /_ah/startという特殊URLにアクセスするため必須
  - url: "*/_ah/*"
    module: back-job
  # こちらは任意
  - url: "*/cron/*"
    module: back-job
  # それ以外のURLはapp.yamlに振り分け
  - url: "*/*"
    module: default
```

back-job moduleにもルーティングを適切に設定しましょう。
また、cronアクセス時はcronURLにアクセスをかけてもいいか
https://[プロジェクトID].appspot.com/_ar/start
というURLにGAE側から確認しに来るので、これには無条件で200を返却するようにしています。

```yaml:/path/to/fuelphp/back-job.yaml
runtime: php55
api_version: 1
# app.yampにはmoduleが書いていないが、moduleを省略した場合、自動的に「default」が割り当てられる
module: back-job 

# インスタンスのスペック。一番安いB1を選択している
# 詳しくは https://cloud.google.com/appengine/docs/about-the-standard-environment#instance_classes
instance_class: B1
basic_scaling:
  max_instances: 1 # 最大1台までしか起動しない
  idle_timeout: 1m # 1分間アイドル状態が続いたらインスタンスをシャットダウン

handlers:
# 無条件に200を返却するようにすることでcronアクセスを許可する
- url: /_ah/start
  script: public/200.php

- url: /.*
  script: public/index.php
  login: admin

# app.yamlと同じプログラムが入るため同じようにFUEL_ENVを用意する必要がある
env_variables:
# FUEL_ENV: 'production'
 FUEL_ENV: 'development'
```

```php:/path/to/fuelphp/public/200.php
<?php
header("HTTP/1.1 200 OK");
```

# バックエンドインスタンスの説明

一旦これらをapp.yamlと共存させるとどういったことが起こるかを解説します。

![GAE_cron.png](https://qiita-image-store.s3.amazonaws.com/0/50841/1bf4bd47-0212-b850-acc0-c66b7a94edf9.png)

まず、dispatch.yamlがURLを判断し、module:defaultサーバに振り分けるか、module:back-jobサーバに振り分けるかを決定します。
/ah もしくは /cron からURLが始まっていたらmodule:back-jobサーバに、それ以外はmodule:defaultサーバに振り分けます。
app.yaml側はユーザのアクセスが多くなればなるほど適切にスケーリングしていく「Automatic scaling」を使用している状態であるのに対しback-job.yamlは「Basic scaling」を使用しています。
サーバは2つに別れていますが、あくまでアップロードするプログラムは同じにし、用途に応じて使用するmoduleを変えてやるようにします。
Automatic scalingとBasic scalingの違いは下記のとおりです。

#### Automatic scaling

- ユーザアクセス状況に合わせて自動的にスケーリング
- アクセスが無いことを判断したら自動的にインスタンスをシャットダウンする
- 基本最大60秒でレスポンスを返却しなければならない

#### Basic scaling

- インスタンスの最大数はmax_instancesで明示的に指定
- シャットダウン時間もidle_timeoutで明示的に指定
- スクリプトの実行時間は無制限

通常
Automatic scalingがユーザアクセス
Basic scalingがcron実行時のアクセス
という風に得意分野があるのがわかると思います。

さらに、Automatic scalingとBasic scalingは無料枠区分が分かれており、分散して使うとよりお得です。
https://cloud.google.com/appengine/docs/php/an-overview-of-app-engine
詳しくはこちらも御覧ください


# Twitterライブラリの導入

さて、Twitterにつぶやくためのライブラリを導入しましょう。
おそらくPHPで（たぶん）メジャーなライブラリであろう[TwitterOAuth](https://github.com/abraham/twitteroauth)はローカルファイルへの参照を行っている部分がありましたので使用できませんでした。。

代わりに[codebird-php](https://github.com/jublonet/codebird-php)を使います。

```bash:shell
$ cd /path/to/fuelphp
$ php composer.phar require jublonet/codebird-php
```

ライブラリが導入できました。
これからcronで使用するスクリプトを書いていくのですが、いわゆるtasksが使用できません。
GAEのcronはあくまでGETリクエストをサーバに投げてくれるだけなので、若干気持ち悪いですが、コントローラにcronを書く必要があります。

また、この先でTwitterAPIキーを使用します。
https://syncer.jp/twitter-api-matome
上記などが詳しく解説してくださっているので、取得しておいてください。

# 実装＆テスト方法

下記ファイルを用意します。

```yaml:/path/to/fuelphp/cron.yaml
cron:
  # 説明文
- description: "tweet" 
  # 実行するURL
  url: /cron/tweet           
  # 実行間隔 毎時間ごとに実行を意味する。
  schedule: every 1 hours    
  # scheduleの基準となるタイムゾーン
  timezone: Asia/Tokyo
  # 実行するmodule
  target: back-job
```

cronとして登録するURL一覧を記載します。
scheduleのフォーマットに関しては
https://cloud.google.com/appengine/docs/php/config/cronref
こちらも参照してください。

```ini:/path/to/fuelphp/php.ini
extension="curl.so"
```

GAEではいくつか使用できるエクステンションがあります。今回はcurlを使用するので、それを許可しておきます。
https://cloud.google.com/appengine/docs/php/config/php_ini
こちらも参照してください。

```php:/path/to/fuelphp/fuel/app/config/routes.php
return array(
    /* 省略 */
    'cron/tweet' => 'cron/tweet', // 追加
);
```

ルーティングの追加を行います

```php:/path/to/fuelphp/fuel/app/classes/controller/cron.php
class Controller_Cron extends Controller
{
    const TWITTER_CONSUMER_KEY = 'Consumer Key (API Key)';
    const TWITTER_CONSUMER_SECRET = 'Consumer Secret (API Secret)';
    const TWITTER_ACCESS_TOKEN = 'Access Token Key';
    const TWITTER_ACCESS_TOKEN_SECRET = 'Access Token Secret';

    public function before()
    {
        parent::before();
        if (\Fuel::$env == \Fuel::PRODUCTION) {
            if (Input::ip() !== '0.1.0.1') {
                throw new HttpNotFoundException;
            }
        }
    }

    public function action_tweet()
    {
        \Codebird\Codebird::setConsumerKey(
            self::TWITTER_CONSUMER_KEY,
            self::TWITTER_CONSUMER_SECRET
        );
        $cb = \Codebird\Codebird::getInstance();
        $cb->setToken(self::TWITTER_ACCESS_TOKEN, self::TWITTER_ACCESS_TOKEN_SECRET);
        $params = [
            'status' => '彼女が欲しいです' . date('Y-m-d H:i:s')
        ];
        $cb->statuses_update($params);
        return Response::forge(null, 200);
    }
}
```

実際にツイートを行うコントローラファイルを用意します。
先程取得した各種TwitterAPIキーを入れてください。
また、beforeメソッドで本番環境時はIPアドレス0.1.0.1以外を弾いています。
これはGAEから来るcronアクセスは必ずリモートアクセスIPが0.1.0.1になるので、それを利用して不正アクセスを検知しています。

ではテストを行ってみましょう

```bash:shell
$ cd /path/to/fuelphp/
$ dev_appserver.py ./app.yaml ./dispatch.yaml ./back_job.yaml
```

app.yaml以外にモジュール系やルーティング系のyamlも増えた場合それも明示的に指定しなければならないようになっています。

起動後に
http://localhost:8000/cron
にアクセスすると

![Cron.png](https://qiita-image-store.s3.amazonaws.com/0/50841/13eca156-17dc-1960-e0a2-064f60e8e9ff.png)

このような画面が表示されればcronが正常に検知されていることがわかります。
指定時間を待たずとも「Run Now」を押せば即時で実行させることが出来ます。
ボタンを押して「彼女が欲しいです [今日の日付時刻]」とツイートされればテストは成功です！

# デプロイ

ここまで出来たら本番デプロイを行いましょう。
まずFUEL_ENVをどちらもproductionにしておきます。

```yaml:/path/to/fuelphp/app.yaml
env_variables:
 FUEL_ENV: 'production'
# FUEL_ENV: 'development'
```

```yaml:/path/to/fuelphp/back_job.yaml
env_variables:
 FUEL_ENV: 'production'
# FUEL_ENV: 'development'
```

```bash:shell
$ cd /path/to/fuelphp
$ gcloud app deploy --project [プロジェクトID] app.yaml dispatch.yaml cron.yaml back_job.yaml
```

デプロイ時もデバッグ時と同じくapp.yaml以外のファイルが増えた場合明示的に指定する必要があります。デプロイ時はcron.yamlも指定しましょう。
これでデプロイは完了です。後はcron.yamlに設定された時間を待てば確認することができるのですが、待たずに今すぐ実行することも出来ます。

https://console.cloud.google.com/appengine/taskqueues
から、「cronジョブ」を選択すると登録しているtweetジョブが出てくるので、「今すぐ実行」を押せばすぐツイートが行われるはずです！

# 最後に

いかがでしたでしょうか？
TwitterBotを量産するもよし、他のアプリを作ってみるもよし！無料なのでどんどん使ってみてください！

（アドベントカレンダー14日投稿間に合わなくてすいませんでした）