[PHP]Amazon SNS を使い、iOS・AndroidへPUSH通知 - デバイストークン登録・更新編 -

# **サーバ側の処理**

### **AWS SDK for PHPをインストール**
いくつか方法がありますが、今回はpharでインストールします。
以下のページの"download the packaged phar"からダウンロードしてください。
https://docs.aws.amazon.com/aws-sdk-php/v3/guide/getting-started/installation.html

### **AWS SDK読み込み**
```
require APP. 'Vendor/aws.phar';
use Aws\Sns\SnsClient;
```
サーバーに設置して読み込むだけです。
パスは環境によって適宜変更してください。

### **SNSクライアントを生成**
```
$sns = SnsClient::factory(array(
    'version' => 'latest',
    'credentials' => array(
        'key' => AWS_KEY,
        'secret'  => AWS_SECRET,
    ),
    'region' => 'ap-northeast-1'
    )
);
```
SNS APIにリクエストするときに必要となります。
key と secret にはAWSから取得した、アクセスキーIDとシークレットアクセスキーを設定します。

### **デバイストークン登録とプラットフォームエンドポイント取得**
```
$options = array(
    'PlatformApplicationArn' => APP_ARN,
    'Token' => $push_token,
    'CustomUserData' => $user_id, 
    'Attributes' => ['Enabled' => 'true'],
);

// デバイストークンをAmazon SNSに登録する
$result = $sns->createPlatformEndpoint($options);

if (isset($result['EndpointArn'])) {
    $endpoint = $result['EndpointArn'];
}
```
・PlatformApplicationArn
　iOSならiOSのアプリケーションARN、AndroidならAndroidのアプリケーションARNを設定します。
・Token
　アプリから送られてきたデバイストークンです。
・CustomUserData
　ユーザーの識別子です。ユーザーIDを入れておくといいと思います。

登録が完了するとプラットフォームエンドポイントが生成されるので、DBに保存しておきます。


###### **注意点**
既にSNSに登録されているデバイストークンで "createPlatformEndpoint" を実行した場合、
CustomUserData が同じであれば正常に登録されたときと同じようにエンドポイントが返却されます。
この場合はエンドポイントが無効になっていても、自動的に有効にはならないので、別途有効にする処理が必要となります。

CustomUserData が異なる場合はエラーとなり、エラーメッセージに "already exists" が入ります。
この場合はエラーメッセージの中からエンドポイントを抜き出して、DB更新やエンドポイントの有効化を行います。

### **ALLトピックに追加**
```
$sns->subscribe(array(
  'Endpoint' => $endpoint,
  'Protocol' => 'Application',
  'TopicArn' => $topic_arn,
));
```
・TopicArn
　前回作成した全配信用のトピックARNを設定します。
　iOSならiOS用のトピックARN、AndroidならAndroid用のトピックARNです。

### **デバイストークン更新**
アプリ側でデバイストークンが更新されている場合があったり、エンドポイントが無効になっている場合があるので、アプリ起動時に毎回実行するといいと思います。

```
$sns->setEndpointAttributes(array(
    'EndpointArn' => $endpoint,
    'Attributes' => array(
        'Enabled' => 'true',
        'Token' => $push_token,
    )
));
```
・EndpointArn
　上記の登録APIから取得したエンドポイントを設定します。
・Attributes
　エンドポイントが無効になっている場合もあるので、"'Enabled' => 'true',"を設定します。
　デバイストークンもここで設定します。

### **登録・更新共通ロジック**
登録も更新も同時にできるfunctionを作成しました。
新規の場合はデバイストークンのみ、更新の場合はエンドポイントも付与します。

```
// デバイストークンをSNSに登録する
private function add_sns_endpoint($device_type, $user_id, $push_token, $endpoint = NULL) {
    $app_arn = '';
    $topic_arn = '';
    
    if ($device_type == '1') {
        $app_arn = IOS_APP_ARN;
        $topic_arn = IOS_ALL_TOPIC_ARN;
    } else {
        $app_arn = ANDROID_APP_ARN;
        $topic_arn = ANDROID_ALL_TOPIC_ARN;
    }
    
    // SNSクライアント生成
    $sns = SnsClient::factory(array(
        'version' => 'latest',
        'credentials' => array(
            'key' => AWS_KEY,
            'secret'  => AWS_SECRET,
        ),
        'region' => 'ap-northeast-1'
        )
    );
    
    if ($endpoint) {
        // DBにendpointが登録されている場合は Enabled と Token を更新
        try{
            $sns->setEndpointAttributes(array(
                'EndpointArn' => $endpoint,
                'Attributes' => array(
                    'Enabled' => 'true',
                    'Token' => $push_token,
                )
            ));
            return $endpoint;
        }
        catch (Exception $e) {
            // 何か問題があれば新規登録する
        }
    }
    
    $options = array(
        'PlatformApplicationArn' => $app_arn,
        'Token' => $push_token,
        'CustomUserData' => $user_id, //User Data
        'Attributes' => ['Enabled' => 'true'],
    );
    
    $endpoint = '';
    
    try {
        // デバイストークンをAmazon SNSに登録する
        $result = $sns->createPlatformEndpoint($options);
        
        if (isset($result['EndpointArn'])) {
            $endpoint = $result['EndpointArn'];
            
            // OS別のALLトピックに登録
            $sns->subscribe(array(
              'Endpoint' => $endpoint,
              'Protocol' => 'Application',
              'TopicArn' => $topic_arn,
            ));
        } else {
            return false;
        }
    } catch (Exception $e) {
        // 同じデバイストークンが既に登録済みで、CustomUserDataが異なる場合はエラーが発生する
        $message = $e->getMessage();
        
        preg_match("/.+Endpoint (.+) already.+/",$message,$matches);
        
        if (count($matches) > 1) {
            // エラーメッセージからエンドポイントを取得
            $endpoint = $matches[1];
            
            try{
                // エンドポイントを Enabled に変更する
                $sns->setEndpointAttributes(array(
                    'EndpointArn' =>$endpoint,
                    'Attributes' => array(
                        'Enabled' => 'true',
                        'Token' => $push_token,
                    )
                ));
            }
            catch (Exception $e) {
                return false;
            }
        } else {
            return false;
        }
    }
    
    return $endpoint;
}
```