前回、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オプションで同名のマイグレーションの雛形も作成します。
/**
* 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に紐づくので、外部キー制約をつけています。
public function socialAccounts()
{
return $this->hasMany('App\SocialAccount');
}
usersテーブルとsocial_accountsテーブルは1対多の関係になるので、Userクラスでは複数形のメソッド名(socialAccounts())をつけ、hasManyメソッドでSocialAccountクラスを指定します。
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
サービスクラスを置くディレクトリがまだなければ、作成します。
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にはプロバイダに登録されているユーザのデータを利用するために必要なトークンが保存されます。
/**
* 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アカウントへのリンクを表示してみます。
/**
* プロフィールページ
*/
Route::get('/user/{userId}', 'UserController@show');
ここではサイト内のプロフィールページURLをhttps://example.com/user/{userId}とします。
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}がユーザのプロフィールページです。
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からアクセス可能です。
@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を読み込んでください。
<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への投稿機能の方も、需要があれば記事にしたいと思います。よかったらリクエスト送ってください:)
それでは、今回もご購読ありがとうございました!