AWSのSNSを利用してスマホにプッシュを送信する（初級編）


## 概要
AWSにはSNSというサービスがあり、高速かつ確実かつ安価にスマホにプッシュが送れますよというものです。

本来、PHPでiOSへのプッシュとAndroidのプッシュに対応しようとすると、それぞれに合わせたプログラムを書く必要がありますが、SNSを使えばそんなに頑張らなくてもいろいろな端末に対して見境なくプッシュを発射できるようになります。

##環境##

+ EC2サーバー（APIサーバー）
+ nginx 1.6.2
+ php-fpm 5.5.25
+ CakePHP 2.5.3

環境が変わってもプログラムがちょっと変わるだけでやり方は一緒です。

## 仕組み
例えば、Aという端末からBという端末に対してプッシュでメッセージを送るアプリを考えてみます。LINEみたいなやつね。

###これまでのやり方（SNSを使わない方法）###

まず、プッシュトークンを下の図のように取得して、APIサーバーを介してユーザーIDとプッシュトークンが紐付いた状態でDBに保持しておきます。

![スライド1.png](https://qiita-image-store.s3.amazonaws.com/0/72081/18853268-9efc-9a21-baa6-42bc4597d8ef.png "スライド1.png")

iOSの場合プッシュトークンはほとんど変わることはないようですが、念のため起動時毎回プッシュトークンの取得をするように書いたりします。お好みで。

Androidの場合はよくプッシュトークンが変わるので、こちらは起動時なり、前回取得から一定時間経過したら取得し直すなりするように実装しておきます。お好みで。

実際にプッシュを打つ際は、メッセージを送る相手のユーザーIDとメッセージなどをAPIサーバーへ送り、相手がAndroidならCGMサーバーを、iOSならAPNSサーバーを（以下面倒なのでプッシュサーバーと記載）叩きます。するとプッシュサーバーから対象の端末へプッシュが飛びます。

![スライド2.png](https://qiita-image-store.s3.amazonaws.com/0/72081/ecb8b900-a618-4ce4-5b75-58f2ecf511aa.png "スライド2.png")

###SNSを使う方法###

まず、プッシュトークンを下の図のように取得して、APIサーバーにユーザーIDとプッシュトークンを送ります。ここで、このままDBに保存せず、プッシュトークンをSNSに送ります。

するとEndpoint Arnが返ってきます。これとユーザーIDを紐付いた状態でDBに保存します。

![スライド3.png](https://qiita-image-store.s3.amazonaws.com/0/72081/c1d475f6-4977-e861-7101-e9f6004ce970.png "スライド3.png")

プッシュトークンはDBに保存しておいても、保存しておかなくてもOKです。プッシュトークンが変わった時だけSNSのAPIを叩きたいということならプッシュトークンも一緒にDBに保存しておきます。

そして、実際にプッシュを打つ時は、メッセージを送る相手のユーザーIDとメッセージをAPIサーバーに送るところまでは一緒ですが、APIサーバーは受け取った後にユーザーIDからEndpoint ArnをDBから取得してSNSにメッセージとEndpoint Arnを送ります。

するとどうでしょう。相手がAndroidだろうが、iOSだろうが、SNSがプッシュサーバーをうまいこと叩いてくれて、メッセージ届きます。すごい。

![スライド4.png](https://qiita-image-store.s3.amazonaws.com/0/72081/fd097f74-cbf9-aca5-da4f-3c5c8e8f910d.png "スライド4.png")

## 準備
まずは、PHPから直接プッシュをアプリに送れる状態にまでしてください。
やり方はどっかを参照すべし。

## プログラム
アプリ側は特にいじる必要はありません。

サーバー側は *AWS SDK for PHP* をまずダウンロードして下さい。このSDK使うと楽ちんです。
そして私はSNSというモデルを作ってみました。これをうまいこと使えばいけるはずです。
やり方は人それぞれだと思うので、一例だと思ってください。

```php
<?php
require_once APP . 'Vendor/aws-sdk-php/vendor/autoload.php';
use Aws\Common\Aws;
use Aws\Common\Enum\Region;

class SNS extends AppModel
{
	private $obj = null;

	private function getInstance()
	{
		if (is_null($this->obj)) {
			$this->obj = Aws::factory([
				'key'    => AWS_IAM_KEY,    // AWSのIAMに登録したキー
				'secret' => AWS_IAM_SECRET, // AWSのIAMに登録したシークレット
				'region' => Region::TOKYO   // リージョンは自分の環境に合わせるべし
			])->get('sns');
		}
		return $this->obj;
	}

	/**
	 * SNSにプッシュトーンを登録してEndpointArnを取得
	 */
	public function createPlatformEndpoint($token)
	{
		$options = [
			'Token'                  => $token,
			'PlatformApplicationArn' => (strlen($token) == 64 ? ARN_IOS: ARN_ANDROID), // トークンの文字数によってiOSかAndroidか指定してます。もっといい方法あったら教えて
		];
		try {
			$res = $this->getInstance()->createPlatformEndpoint($options);
		} catch (Exception $e) {
			$this->log($e->getMessage(), 'error');
			return false;
		}
		return $res['EndpointArn']; // ←戻り値見るとEndpointArnがわかります
	}

	/**
	 * プッシュ！
	 */
	public function publish($message, $EndpointArn)
	{
		try {
			$res = $this->getInstance()->publish([
				'Message'   => $message,
				'TargetArn' => $EndpointArn
			]);
		} catch (Exception $e) {
			$this->log($e->getMessage(), 'error');
			return false;
		}
		return $res;
	}
}
```

## まとめ
面倒だったらSNS使わないでAPIサーバーから直接プッシュサーバーを叩いてもいいよ。けど、ユーザー数が増えてくるとすごく時間かかるし、その間、サーバーのCPU使用率はギュンギュンです。

だからSNS使ったほうがいいです。SNSしゅごい(^q^)