LaravelでOneSignalを使って特定ユーザーに通知する方法

プログラミング初心者です。

OneSignalを使って、特定ユーザーに通知する機能を実装しました。
頭を整理するために記録しておきます。

##開発環境
Laravel5.8
PHP 7.3.24
MAMP

##1　OneSignalの登録
まずOneSignalの登録や、途中までの実装は[こちら](https://qiita.com/iritec/items/47c69c61c3731f63688c)のQiita記事を参考にさせていただきました。
登録方法は[document](https://documentation.onesignal.com/docs/web-push-typical-setup)にも書いてあるので、参考にしてみてください。

##2　通知ON/OFFするやつ設置
JavaScriptを使って通知を登録するかしないか部分を実装します。
何を表示させるかは、OneSignalサイトの設定で、SUBSCRIPTION BELL(ベルマーク)やPUSH PROMPT(上からスライドで出てくる)を選べます。
私はベルマークを表示するように設定しました。

そして表示させたいBladeファイルのheadの中に入力します。

```push_blade.php
<head>
　　<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async=""></script>
    <!-- php上で別のところで定義された変数をscriptタグの中では直接使えない。だから「@php」or「<?php?>」を使ってblade上で直接定義する -->
    <?php
        $loginUser = Auth::check(); //ユーザーがログインしてるか否か してればtrue
        $appId = env('ONESINGAL_APP_ID');//envに書かないとgithubにアップされ悪用されてしまう
    ?>

  　　　<script>
        //ログイン機能を実装していれば、ログイン前にベルマーク表示する必要がないため、ユーザーがログインしてればベルマークが登場する
        if( {{$loginUser==null ? "false":"true"}} ) {

            //ベルマーク表示関係
            window.OneSignal = window.OneSignal || [];
            OneSignal.push(function() { //if文の中まで
                    OneSignal.init({
                        //上のPHP内で定義したものを使うには'{{}}'でOK
                        appId: '{{ $appId }}', 
                    });
            });
        }   
                        
    </script>
</head>
```
- ざっくりとした説明をコードのコメント内に記載しています。
- ベルマーク表示関係についてはOneSignal側でこれをコードに貼ってくださいって指示されます。

##3　pushes_tableを作成
create_pushes_tableというmigrationsファイルを作成します。
そしてログインしているuserのIDと通知をON/OFFした時に発行されるPlayerIDをDBに保存したいため、```user_id```と```player_id```というカラムを作成します。

```create_pushes_table.php
public function up()
    {
        Schema::create('pushes', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('player_id');
            $table->timestamps();
        });
    }
```
##4　Model作成
Push.phpという名前でModelを作成しました。

```Push.php
class Push extends Model
{
    protected $guarded = array('id');

    public static $rules = array(
        'user_id' => 'required',
        'player_id' => 'required',
    );
}
```

##5　通知ON/OFF時に、PlayerIDが発行され、保存/削除できるようにControllerに渡すように実装
ベルマーク表示関係の中に以下のコードを追加します。

```push_blade.php
<head>
　　<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async=""></script>
    <!-- php上で別のところで定義された変数をscriptタグの中では直接使えない。だから「@php」or「<?php?>」を使ってblade上で直接定義する -->
    <?php
        $loginUser = Auth::check(); //ユーザーがログインしてるか否か してればtrue
        $appId = env('ONESINGAL_APP_ID');//envに書かないとgithubにアップされ悪用されてしまう
    ?>

  　　　<script>
        //ログイン機能を実装していれば、ログイン前にベルマーク表示する必要がないため、ユーザーがログインしてればベルマークが登場する
        if( {{$loginUser==null ? "false":"true"}} ) {

            //ベルマーク表示関係
            window.OneSignal = window.OneSignal || [];
            OneSignal.push(function() { //if文の中まで
                    OneSignal.init({
                        //上のPHP内で定義したものを使うには'{{}}'でOK
                        appId: '{{ $appId }}', 
                    });

             //通知をON/OFFしてもonesignalのplayerid発行してuserIdに入れる
             //それを ajax非同期通信 使って指定URLに送信
             OneSignal.on('subscriptionChange', function (isSubscribed) {
                    //通知ONになった時
                    if (isSubscribed == true) {
                        OneSignal.getUserId(function(userId) {
                            
                            $.ajax({
                                headers: {
                                    // csrf対策
                                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                                },
                                
                                
                                url: '/push/subscribe', // アクセスするURL
                                type: 'POST', // POSTかGETか
                                data: { 
                                    'player_id' : userId //controllerに送るデータ
                                },
                                
                                success: function() {
                                    //通信が成功した場合の処理をここに書く
                                    //通信がうまくいったか調べるために、logを表示するようにする
                                    console.log('success');
                            },
                            
                            error: function() {
                                //通信が失敗した場合の処理をここに書く
                               //通信が失敗したか調べるために、logを表示するようにする
                                console.log('error');
                            }
                            
                            
                        });
                    });
        
                  //通知OFFになった時
                } else if (isSubscribed == false) {
                    OneSignal.getUserId(function(userId) {
                        console.log("OneSignal User ID:", userId);
                        // (Output) OneSignal User ID: 270a35cd-4dda-4b3f-b04e-41d7463a2316    
                        
                        $.ajax({
                                headers: {
                                        // csrf対策
                                        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                                    },
                        
                        
                                    url: '/push/delete', // アクセスするURL
                                    type: 'GET', // POSTかGETか
                                    data: { 
                                            'player_id' : userId //controllerに送るデータ
                                        },
                            
                                        success: function() {
                                                //通信が成功した場合の処理をここに書く
                                                //通信がうまくいったか調べるために、logを表示するようにする
                                                console.log('success_delete');
                                            },
                                
                                            error: function() {
                                                    //通信が失敗した場合の処理をここに書く
                                                    //通信が失敗したか調べるために、logを表示するようにする
                                                    console.log('error_delete');
                                                }
                                            });
                                        });
                                    
                                    }
                                });

            });
        }   

    </script>
</head>
```
- ざっくりとした説明をコードのコメント内に記載しています。
- ベルマークの通知をON/OFFにしたときに、```OneSignal.getUserId()```でPlayerIDが発行され、```userId```に代入されます。
- pushes_tableに保存/削除の実装はControllerで書きますが、画面上で通知ON/OFFする際リロードせずにDBにデータを渡したい時、Javascript側でAjax通信(非同期通信)を使います。
- Ajax通信の参考サイト[こちら](https://nana-engineer.com/2021/02/13/laravel-db-ajax/)
- envメソッドを書いただけでは、反映されない事もあるので、[こちら](https://qiita.com/meiyin/items/ae29e4f8ca6764454e2a)を参考にしてみてください。

##6　Controllerにtableに保存/削除するように実装
- ざっくりとした説明をコードのコメント内に記載しています。

```PushController.php
    //通知ON player_id保存
    public function PushID(Request $request)
    {
        //ajaxがcontrollerまで来てるか確認するために、log表示する
        logger('ajax success');

        $user = Auth::user();
        $push = new Push;
        $push->user_id = $user->id;
        //$form=$request->all() $push->player_id = $form['player_id']を省略すると
        $push->player_id = $request->all()['player_id'];
        

        //Push tableのuser_idにログインしてるユーザーのidがなかったらsave
        //↑これがないとONするたびに、新しいレコードが生成され同じuser_id/player_idが保存されてしまう
        if (Push::where('user_id',$push->user_id)->doesntExist()) {
            $push->save();
            
        }
    }


    //通知OFF player id削除
    public function DeletePushID(Request $request)
    {
        //ajaxがcontrollerまで来てるか確認するために、log表示する
        logger('ajax_delete success');

        //Auth::id()と一致するuser_idの一番最初のレコードを取得
        $push = Push::where('user_id',Auth::id())->first();
      
        //もし$pushがNULLだった時errorが出てしまうので、NULLじゃなかった時削除するようにします。
        if ($push != NULL) {
            $push->delete();
        }
    }
```
- ここまで実装してみたら、通知をON/OFFしたら保存/削除されるか試してみてください。

##7 artisanコマンドを作成し、コマンドファイルの中に特定のユーザーに通知するように実装
- 今回は自分で作成したコマンドで実行すると、特定ユーザーに通知されるという実装をしました。
- もしかしたらアプリによっては違う実装の仕方もあるかもしれないので、このやり方は絶対ではありません。
- artisanコマンドを作成は[こちら](https://tektektech.com/laravel-artisan-make-command/)を参考にしました。
- ここではPushというコマンドを作成したとして、Push.phpファイルに以下のコードを入力します。

```Push.php
//これはコマンドの名前を登録します。特定ユーザーに通知するように引数を指定してます
//引数を指定する時は　{}　で囲む
protected $signature = 'Push {user_id}';

//これはコマンドの説明を入力します
protected $description = 'Pushを出力';
```
- 引数の指定は[こちら](https://qiita.com/shosho/items/af15ef1d94a0a7f34e8e)を参考にしました。
- そして下にスクロールして、handleアクションに以下のコードを入力します。

```Push.php
 //処理内容を記述します
    public function handle()
    {
        //引数で落ちてくる user_id を取得するには以下を記載
        $user_id = $this->argument('user_id');
        //user_idという引数に例えば1が入力される、そしたらPush Tableのuser_idが1のレコードが取得される
        $push = Push::where('user_id',$user_id)->first();
        
        //通知内容を自分で設定できる
        $fields = array(
            'app_id' => env('ONESINGAL_APP_ID'),//環境変数にしないとgithubに公開される
            'include_player_ids' => [$push->player_id],//特定ユーザーの保存したplayer_idを入れる
            'url' => "通知されるurl",
            'headings' => array('en' => '好きなタイトル'),
            'contents' => array('en' => '好きな本文')
        );

        //この下からonesignalと繋がっている
        $fields = json_encode($fields);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, "https://onesignal.com/api/v1/notifications");
        curl_setopt($ch, CURLOPT_HTTPHEADER,
                    array('Content-Type: application/json; charset=utf-8', 'Authorization: Basic '.env('ONESINGAL_REST_API_KEY')));//環境変数
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        $response = curl_exec($ch);
        logger($response);
        curl_close($ch); 
    }
```
- ここまでできたら```$php artisan Push 1```で実行してみて、通知がくるか試してみてください。
引数の部分は登録してるユーザーのIDを入力してください。

- ざっくりとした説明をコードのコメント内に記載しています。
- 「//この下からonesignalと繋がっている」の下は、記事を参考にさせていただいたため、正直どういうコードの意味なのかまだ自分でも分かっておりません。documentに載ってるらしいですが、見つけられておらずすみません。

##最後に
ここまで読んでいただいた方ありがとうございます。そしてお疲れ様です。
長い記事になってしまい、分かりづらい部分や、もしかしたら抜けてしまっている部分があるかもしれません。
おかしくない？っていうところがございましたら、コメントくださいませ。
そしてこのやり方じゃ全然通知できないってこともありえます。あくまで私がやってみた実装ですので参考程度に捉えてください。