2013年8月19日月曜日

GCM(Google Cloud Message) を PHP + PEAR Request2 + CodeIgniter で JSON で

タイトルの通り、CodeIgniter に PEAR の Request2 をインストールし、JSON形式でプッシュ送信を行う備忘録。

[前提条件]
  • CodeIgniter が使える
  • PEAR がインストールされている
  • Google より Project ID 取得済
  • Google より Server API Key 取得済
  • 通知したい端末の RegistrationID が取得済
[やる事]
  1. PEAR Request2 のインストール
  2. CodeIgniter へ PEAR Request2 のインストール
  3. プッシュ通知の実装(サンプル後述)
1.  PEAR Request2 のインストール

$ su
# pear install http_request2

PEAR Request でハマった。 これは非推奨となっているが、CodeIgniterでは使えない。
Request2 を使う。

2. CodeIgniter へ PEAR Request2 のインストール

2.1. PEAR用ディレクトリをCodeIgniterへ作成

色々サイトを見たが、 system/application/pear ディレクトリを作る、と良く見かける。これでまず少しハマる。
どうやら実際は "CIが配置された場所"/application/pear ディレクトリを作る。"system"の部分は"system"ディレクトリではない。アプリケーションのトップディレクトリ、というか、プロジェクトディレクトリの事を表している。
project dir/
    application/
        cache/
        config/
        controllers/
        core/
        helpers/
        hooks/
        language/
        libraries/
        logs/
        models/
        pear/
        third_party/
        views/

理解すると、他の場所にも移せると思うがとりあえず時間無いので確実に動く方法という事で。

$ cd <インストールされているディレクトリ>/application
$ mkdir pear

$ sudo chown apache pear

パーミッション変更は適宜。Mac OS X ユーザなら "apache"ではなく"_www" になると思う。
(apache を起動しているユーザが参照できれば良い)

2.2. PEARの一部を CodeIgniter へコピー

調べてみると、依存関係がどうのこうのとあったが、面倒なので、PEAR.php と HTTPディレクトリ、Net ディレクトリをコピーする。

pear を探す。
$ which pear
/usr/bin/pear
$
$ more /usr/bin/pear
#!/bin/sh
exec /usr/bin/php -C -d include_path=/usr/share/pear \
    -d output_buffering=1 /usr/share/pear/pearcmd.php "$@"
$

ちなみにこれは "CentOS release 6.4 (Final)" で実行した結果 /usr/share/pear にPEARがあった。Mac OS X(Mountain Lion) だと /usr/lib/php/pear
$ cd /usr/share/pear
$ cp -p PEAR.php /xxxxxxxx/application/pear
$ cp -pr HTTP /xxxxxxxx/application/pear
$ cp -pr Net /xxxxxxxx/application/pear

2.3. CodeIgniter 各種設定

/application/config/config.php
$config['enable_hooks'] = TRUE;

/application/config/hooks.php
$hook['pre_controller'][] = array(
        'class' => 'Pear_hook',
        'function' => 'index',
        'filename' => 'pear_hook.php',
        'filepath' => 'hooks'
);

include path の調整を行う為、ここで定数を用意しておく。
PEAR のファイルを CodeIgniter へコピーしたが、PEARにも同じファイルがあり、通常なら、元のPEARのへの include path が始めに参照されてしまう。
もし、CodeIgniter に配置した PEAR のファイルに手を入れるような事があった場合、混乱を防ぐ為、include path の順序を CodeIgniter 側の PEAR が先にくるように修正していく。

/web/index.php
define('TOPPATH', realpath(BASEPATH . '..') . '/');

/application/hooks/pear_hook.php
if (!defined('BASEPATH')) exit('No direct script access allowed');

class Pear_hook{
    function index(){
        // OS independent
        ini_set('include_path',TOPPATH.'application/pear/'.PATH_SEPARATOR.ini_get('include_path'));
        
        // on Apache
        // ini_set('include_path',ini_get('include_path').':'.BASEPATH.'application/pear/');
        // on Windows
        // ini_set('include_path',ini_get('include_path').';'.BASEPATH.'application/pear/');
    }
}



これで準備は整った。後は実装。
その前に、サーバから 443 ポートで外へ接続できるか確認しておく事。


$ curl https://www.google.co.jp

これで画面にHTMLが表示されれば、443ポートで外に出られるのが確認できる。

3. プッシュ通知の実装


class SampleController extends CI_Controller {
    /** Android 通知用プロジェクトID */
    const NOTIFY_ANDROID_PROJECT_ID = '6330938XXXXXX';
    /** Android プッシュ通知用サーバーAPIキー */
    const NOTIFY_ANDROID_SERVER_API_KEY = 'AIzaSyAc_IazhhxVfdCgaRsBesgXXXXXXXXXXXX';
    const NOTIFY_ANDROID_GCM_URL = 'https://android.googleapis.com/gcm/send';

    function _notifyAndroid($registrationId, $message) {
        if( !$registrationId ) {
            return FALSE;
        }

        $this->load->library('pearloader');
        $https_request = $this->pearloader->load('HTTP', 'Request2');
        $https_request->setUrl(SampleController::NOTIFY_ANDROID_GCM_URL);
        $https_request->setMethod(HTTP_Request2::METHOD_POST);
        $https_request->setConfig('ssl_verify_peer', false);
        $https_request->setHeader('Content-Type', 'application/json');
        $https_request->setHeader('Authorization', 'key=' . SampleController::NOTIFY_ANDROID_SERVER_API_KEY);

        // 端末レジストレーションID
        if(!is_array($registrationId)) {
            $registrationId = array($registrationId);
        }
        $data['registration_ids'] = $registrationId;
        // メッセージグループ
        //$message['collapse_key'] = '';
        // デバイスがアイドルではない場合すぐには送らない設定
        //$message['delay_while_idle'] = 'true';
        // デバイスがオフラインの時、メッセージを保持しておく時間(秒)
        $data['time_to_live'] = 600;
        // メッセージ内容
        $data['data'] = array('message' => $message);
        $https_request->setBody(json_encode($data));

        $response = $https_request->send();

        $resDecoded = json_decode($response->getBody());
        if( !$resDecoded->success ) {
            //Logger::error(__FILE__, __LINE__, 'GCM response' . $response->getBody());
            return FALSE;
        }
        return $resDecoded->success;
    }
}


見ての通り、"_"から始まるメソッド名なので、外から叩けない。
パラメータ"$registrationId" は、1つの文字列でも、配列に入った複数でも問題ない。
Registration ID は端末毎に取得し、サーバ側に何らかの状態で保持しておく必要がある。

一つ注意が必要な場所があった。
端末一つでのテストしかしていないので、40行目の success は複数端末を指定した場合、何になるか分からない。
ちなみに、端末一台にプッシュ通知を行った結果 Google サーバから返るJSONのサンプルは以下。
※ "success" : 1 ... のところが、複数端末へ実行した時何が返るか試してません。なのでサンプルコードの if文の所の調整が必要になるかもしれない。

{"multicast_id":772188850101XXXXXXXX,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1376887251914339%352f836XXXXXXXXXX"}]}