Laravel5.5でFacebook認証結果をDBに保存する的なログイン機能を実装したい

#参考資料
- [【Laravel】Facebookログインを実装する方法【コピペOK】](https://manablog.org/laravel_facebook_login/)

今回はこれをなぞります。
以前は理解できずに挫折しましたが、今なら理解できるはず……！
古い情報なのでバージョンは5.5ではないっぽいです。

##前段
- [Laravel5.5でDBのCRUDを実装してみる - Qiita](https://qiita.com/ragingalpaca/items/2e27e25888241cd56e23)
- [Laravel5.5（Homestead）をXSERVERにGitでデプロイする努力の過程 - Qiita](https://qiita.com/ragingalpaca/items/d6bab7b8d989de46f53e)

これで作成中のアプリに機能を追加していきます。

#FacebookSDKのインストール（※）
**結論からいうとこの手順は必要ありません。**

```shell:仮想環境
composer require facebook/php-sdk
```

インストールはできたんですが、なんか怒られました。

```
Package facebook/php-sdk is abandoned, you should avoid using it. Use facebook/graph-sdk instead.
```

**php-sdkというのはもう使われてないのでgraph-sdkを使ってねとのこと。**
グラフ……？って気分になりますが本当なんでしょうか。
とりあえずそっちもインストールしておくことにします。

#Facebookアプリの作成
これは済んでいるのでスルー。
ドメインとコールバックURLの指定をしっかりしましょう。
~~FB側の認証ってどうすればいいんだろう、ひたすらにめんどい~~

#configファイルの作成
`app/config/facebook.php`とありますが、Laravel5.5だと`config/facebook.php`ですね。

#DBの作成
これもフォルダ配置が微妙に違います。
usersはすでに作成しているので、適当に項目を変更しました。
photoを追加しただけかな。

```php:datebase/migrations/create_users_table.php
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('email')->unique();
            $table->string('photo');
            $table->string('name');
            $table->string('password');
            $table->string('lang')->default('en');
            $table->string('timezone')->default('UTC');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
```

profilesは作成します。
あ、`migrate:make`じゃなくて`make:migration`みたいです。
なんか前にもやった気がするこれ。
（ていうかLaravelのバージョンがそもそも違うのを参考にしているという……）

```shell
$ php artisan make:migration create_profiles_table
Created Migration: 2017_12_31_063451_create_profiles_table
```

```php:datebase/migrations/create_profiles_table.php（新規）
    public function up()
    {
        Schema::create('profiles', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned();
            $table->string('username');
            $table->biginteger('uid')->unsigned();
            $table->string('access_token');
            $table->string('access_token_secret');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('profiles');
    }
```

DBの作成。

```shell
$ php artisan migrate:reset
$ php artisan migrate
```

既存テーブルの構造を修正したので一度resetしています。


#モデルの作成
モデルの場所は、デフォルトのままapp直下にしています。
微妙に宣言とか足りてなかったので参考資料に手を入れてます。

```php:app/Profile.php（新規作成）
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Profile Extends Model
{

    public function user()
    {
        return $this->belongsTo('App\User');
    }
}
```

```php:app/User.php
    public function profiles()
    {
        return $this->hasMany('App\Profile');
    }
```

#ルーティングの設定
`app.routes.php`とありますが……これは`routes/web.php`でいいのかな。
これに追記しました。

#つないでみるが……ダメ……ッ！

`指定されたアプリidは無効なアプリidのようです。`とか言われてしまいました。
なんなん。
php-sdkがダメなんですかね。
そういえば以前利用した、socialiteとかいうやつならログインできています。
これ流用できたりするのかな？

- [仮想環境（homestead）なlaravelでfacebookログイン - Qiita](https://qiita.com/ragingalpaca/items/120694ffe094cdc915ed)

今回のは`routes/web.php`に処理が記載されています。

Socialiteは`app/Http/Controllers/Auth/SocialController.php`というところで処理を記述していました。

ちょっと処理を見比べてみます。

##ログイン部分
だいぶ処理の趣が異なってました。
が、やってることは変わらなくて、Socialiteを使えば１行でできるというくらいの差しかありませんでした。

よってスルー。


##コールバック部分
どちらも戻り値的なところなのかfacebookのAPIなのかよくわかりませんが何らかの値を取得していました。

利用するパラメータは以下の通り。

- 今回のやつ
    - user
        - uid
        - name(first_name + last_name)
        - email
        - username
    - profile
        - uid
        - username
        - access_token
        - access_token_secret
- Socialiteのやつ
    - token
    - refreshToken
    - expiresIn
    - id
    - nickname
    - name
    - email
    - avatar
    - user
        - name
        - email
        - gender
        - verified
        - link
        - id
    - avatar_original
    - profileUrl

（ここでQiitaメンテが発生して泣いてました。Kobitoはこういうときに必要なのかもしれません）

###修正案
方針としてはSocialController.phpのコールバック処理を書き換えることにします。

これに伴い、参照関係をいろいろと追加。
完成したのがこちら。

**ここでこんなに処理書いちゃっていいのだろうかという一抹の不安がありますが無視します。**

```php:App/Http/Controllers/Auth/SocialController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Socialite;
use DB;
use Auth;
use Redirect;
use App\User;
use App\Profile;

class SocialController extends Controller
{
    public function viewLogin()
    {
        return view('auth.login');
    }

    public function redirectToFacebookProvider()
    {
        return Socialite::driver('facebook')->redirect();
    }

    public function handleFacebookProviderCallback()
    {
        try{
            DB::transaction(function () {

                $me = Socialite::driver('facebook')->user();

                if($me){
                    $uid = $me->getId();

                    $profile = Profile::whereUid($uid)->first();


                    if (empty($profile)) {

                        $user = new User;
                        $user->name = $me->getName();
                        $user->email = $me->getEmail();
                        $user->photo = $me->getAvatar();
                        $user->password = "hogehoge";

                        $user->save();

                        $profile = new Profile();
                        $profile->uid = $uid;
                        $profile->username = $me->getName();
                        $profile->access_token = "fuga";
                        $profile->access_token_secret = "hoge";
                        $profile = $user->profiles()->save($profile);
                    }

                    $profile->access_token = "hoge";
                    $profile->access_token_secret = "fuga";
                    //$profile->access_token = $facebook->getAccessToken();
                    $profile->save();

                    $user = $profile->user;

                    Auth::login($user);

//                    dd($user);
                    dd($profile);
                }
            });

            return Redirect::to('/')->with('message', 'Facebookログインしました');

        }catch(Exception $e){
            return redirect("/");
        }
    }
}
```

**アクセストークンとかなんか無視しちゃいけなさげなパラメータ**がちらほらと見えますが、とりあえず無視します。

できました（真顔）


###試行錯誤部分
####トランザクション
**デバッグの最中に、usersだけに値が入ってprofilesに入っていない状態が発生しました。**
profilesで存在確認を行うので、このままだとINSERTしようとしてしまい、キー重複でエラーになります。

正しく動けば発生しない状態ではあるものの、後のことも考えるとトランザクションの方法は調べておいて損はなさそうです。
というわけで、これを見ながら`DB::transaction(function (){〜〜〜});`を追加してます（反映済み）。

- [データベース：利用開始 5.5 Laravel](https://readouble.com/laravel/5.5/ja/database.html)

最初はsave()の前後だけでいいかと思ってたんですが、中が無名関数になるため変数的にいろいろ面倒になり、try全体を含めました。

####リターンでリダイレクト返すの違う……？
なんかcallbackからページ遷移しませんでした。

と思ったら、リターンをトランザクションの中に入れてたのが動かない原因のようでした。
そりゃ動きませんて。

#ついでにログインボタンを出したい
作ってるサービスの都合上、FBログインを必須にしたいので、本来のログインページを潰します。

というわけでルーティングをいじって、`my.blade.php`あたりでメニューのログインを変更したり、ユーザー登録のメニューを消したりしました。
その部分がもともと`href="{{ route('login') }}"`となっていたので、`href="{{ url('auth/login/facebook') }}"`に修正。

ログイン……させるときって、どんなボタンでしたっけ……？
そもそもポップアップで動くボタンがFB側からは配布されてますけど、これを使ったほうがいいんでしょうか。

あ、VALUのイメージでいいのか。
とりあえず今回はそんな感じで。