Socialiteで連携したSNSのプロフィールリンクを貼る方法

Socialiteで連携したプロフィールリンクを貼る方法

前回、Socialiteを使ったOAuth認証の方法をご紹介しました。

Socialiteの使い方を世界一丁寧に解説した

このときは、
・SNSの情報を認証以外には使用しない
という条件にしました。

しかし実際のwebアプリでは、ただログインにのみ使うのではなく、連携したSNSアカウントのリンクをユーザのプロフィール画面に表示したり、連携したSNSアカウントを投稿者として各SNSに簡単に投稿できる機能をつけたかったりしますよね。

というわけで。今回は、Socialiteの応用編として、各SNSの情報を保存&活用する方法をご紹介します!

前回の続きから始めますので、まだの方は先にこちらをどうぞ:)

Socialiteの使い方を世界一丁寧に解説した

1. 環境

Laravel Framework 5.8.7
PHP 7.3.1
mysql 8.0.14

※前回とLaravelのバージョンが違いますが、同様の手順で問題ありません。

2. DB周りの準備

コマンド
$ php artisan make:model SocialAccount --migration 

make:modelでモデルの雛形を作成するとともに、–migrationオプションで同名のマイグレーションの雛形も作成します。

database/migrations/xxxx_xx_xx_xxxxxx_create_social_accounts_table.php
     /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('social_accounts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('provider_name');
            $table->string('provider_id')->unique();
            $table->string('token')->unique();
            $table->string('secret_token')->unique();
            $table->timestamps();

            // 外部キー制約
            $table->foreign('user_id')
                ->references('id')->on('users')
                ->onDelete('cascade');
        });
    }

social_accountsテーブルのuser_idはusersテーブルのidに紐づくので、外部キー制約をつけています。

app/User.php
    public function socialAccounts()
    {
        return $this->hasMany('App\SocialAccount');
    }

usersテーブルとsocial_accountsテーブルは1対多の関係になるので、Userクラスでは複数形のメソッド名(socialAccounts())をつけ、hasManyメソッドでSocialAccountクラスを指定します。

app/SocialAccount.php
    protected $fillable = ['provider_name', 'provider_id', 'token', 'secret_token'];

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

対してSocialAccountクラスの方では、単数形のメソッド名(user())をつけ、belongsToメソッドでUserクラスを指定します。

また、新規作成時に値を指定したいprovider_name, provider_id, token, secret_tokenをfillableに代入しておきましょう。

コマンド
$ php artisan migrate

これでDB周りの準備が完了しました!

3. ログイン時の検索と新規登録

コマンド
$ mkdir app/Services

サービスクラスを置くディレクトリがまだなければ、作成します。

app/Services/SocialService.php
namespace App\Services;

use Laravel\Socialite\Contracts\User as ProviderUser;
use App\SocialAccount;
use App\User;

class SocialService
{
    /**
     *  検索か新規作成
     *
     *  @param ProviderUser $providerUser
     *  @param str $provider
     *  @return \App\User
     */
    public static function findOrCreate(ProviderUser $providerUser, $provider)
    {
        // SNS連携済みユーザがいれば返す
        $socialAccount = SocialAccount::where('provider_name', $provider)
                            ->where('provider_id', $providerUser->getId())
                            ->first();
        if ($socialAccount) {
            return $socialAccount->user;
        }

        $user = User::where('email', $providerUser->getEmail())->first();
        if (!$user) {
            // ユーザの新規作成
            $user = User::create([
                'email' => $providerUser->getEmail(),
                'name'  => $providerUser->getName()
            ]);
        }
        
        // SNSアカウントの新規作成
        $user->socialAccounts()->create([
            'provider_id'   => $providerUser->getId(),
            'provider_name' => $provider,
            'token'         => $providerUser->token,
            'secret_token'  => $providerUser->tokenSecret
        ]);

        return $user;

    }
}

findOrCreateメソッドをstaticとして宣言することで、インスタンスを生成することなくSocialService::findOrCreate()でアクセスできるようにしておきます。

provider_nameには、”twitter”や”facebook”などのプロバイダ名、
provider_idにはプロバイダからアカウント毎に発行されているID、
tokenとsecret_tokenにはプロバイダに登録されているユーザのデータを利用するために必要なトークンが保存されます。

app/Http/Controllers/Auth/LoginController.php
    /**
     * OAuth認証の結果受け取り
     *
     * @param str $provider
     * @return \Illuminate\Http\Response
     */
    public function handleProviderCallback($provider)
    {
        try {
            $providerUser = Socialite::with($provider)->user();
        } catch(\Exception $e) {
            return redirect('/login')->with('oauth_error', '予期せぬエラーが発生しました');
        }

        if ($email = $providerUser->getEmail()) {
            Auth::login(SocialService::findOrCreate($providerUser, $provider));
            return redirect($this->redirectTo);
        } else {
            return redirect('/login')->with('oauth_error', 'メールアドレスが取得できませんでした');
        }

    }

コントローラでは、前回User::findOrCreate()としていたところを今作ったSocialService::findOrCreate()に変えます。

これでsocialAccountにデータがない場合は、新規登録してからログインできるようになりました。

4. SNSアカウントへのリンクを表示

次は保存したトークンを使って、プロフィール画面に連携したSNSアカウントへのリンクを表示してみます。

vim routes/web.php
/**
 * プロフィールページ
 */
Route::get('/user/{userId}', 'UserController@show');

ここではサイト内のプロフィールページURLをhttps://example.com/user/{userId}とします。

app/Services/SocialService.php
use Socialite;

class SocialService
{
    /**
     * リンクを取得
     */
    public static function findLink($provider, $token, $secret)
    {
        $user = Socialite::driver($provider)->userFromTokenAndSecret($token, $secret);
        if (!$user) return '';

        switch ($provider) {
            case 'twitter':
                return 'https://twitter.com/' . $user->nickname;
            default:
                return '';
        }

    }
}

}

Socialite::driver($provider)->userFromTokenAndSecret($token, $secret)でプロバイダのユーザ情報を取得することができます。

ここでは例としてTwitterのリンクのみに対応したコードにしてあります。

Twitterの場合、https://twitter.com/{nickname}がユーザのプロフィールページです。

app/Http/Controllers/UserController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\User;
use App\Services\SocialService;

class UserController extends Controller
{

    /**
     * プロフィールページの表示
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function show($userId)
    {
        $userId = intval($userId);

        $user = User::findOrFail($userId);

        // social_account情報
        $socialAccounts = [];
        foreach ($user->socialAccounts as $account) {
            $socialAccounts[$account->provider_name]['link'] = SocialService::findLink($account->provider_name, $account->token, $account->secret_token);
        }

        return view('user/show', [
            'user'              => $user,
            'socialAccounts'    => $socialAccounts
        ]);
    }
}

Userモデルにリレーションを設定してあるので、socialAccounts情報は$user->socialAccountsからアクセス可能です。

resources/views/user/show.blade.php
                    @foreach ($socialAccounts as $provider => $account)
                        @if (isset($account['link']) && $account['link'])
                            <a href="{{ $account['link'] }}" target="_blank" rel="noopener noreferrer">
                            @if ($provider === 'twitter')
                               <i class="fab fa-twitter-square fa-2x"></i> 
                            @else
                                {{ $provider }}
                            @endif
                            </a>
                        @endif
                    @endforeach

余談ですが、TwitterのアイコンにはFont Awesomeを使用しています。
同じように使いたい方は以下のようにして必要なcssを読み込んでください。

resources/views/layout/app.blade.php
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" inte      grity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">

これで、/user/{userId}にそのユーザのTwitterのリンクが表示されるようになりました♪

Facebook等もほぼ同様にリンクを貼ることができますが、各SNSごとにプロフィールページURLの法則が違いますので
SocialService::findLinkメソッド内で適宜URLを生成してください。

冒頭で例に挙げた各SNSへの投稿機能の方も、需要があれば記事にしたいと思います。よかったらリクエスト送ってください:)

それでは、今回もご購読ありがとうございました!