前回の記事の続きです。
この記事のゴール
前回は「Laravel\Socialite\Two\InvalidStateException」の原因究明と対策の提示をしました。
今回は、ログイン後のページでGitHubのユーザー情報を取得して表示するところまでの実装を進めていきます。
トークンを使ってユーザー情報を取得する
公式サイトに説明がありますが、
![](https://macocci7.net/blog/wp-content/uploads/cocoon-resources/blog-card-cache/ac4098820121124c276f1d0008e904d4.jpg)
Socialite::driver('github')->userFromToken($token);
でGitHubのユーザー情報を取得することができます。
ここでいうトークンは、ログイン時にGitHubで発行されたトークンで、
usersテーブルに登録されている「github_token」です。
ログイン後に、
auth()->user()->github_token
で取得することができます。
$githubToken = auth()->user()->github_token;
$githubUser = Socialite::driver('github')->userFromToken($githubToken);
var_dump($githubUser);
を実行して出力される内容は次の通りです。
object(Laravel\Socialite\Two\User)[1456]
public 'id' => int 19181121
public 'nickname' => string 'macocci7' (length=8)
public 'name' => string 'macocci7' (length=8)
public 'email' => string 'macocci7@yahoo.co.jp' (length=20)
public 'avatar' => string 'https://avatars.githubusercontent.com/u/19181121?v=4' (length=52)
public 'user' =>
array (size=32)
'login' => string 'macocci7' (length=8)
'id' => int 19181121
'node_id' => string 'MDQ6VXNlcjE5MTgxMTIx' (length=20)
'avatar_url' => string 'https://avatars.githubusercontent.com/u/19181121?v=4' (length=52)
'gravatar_id' => string '' (length=0)
'url' => string 'https://api.github.com/users/macocci7' (length=37)
'html_url' => string 'https://github.com/macocci7' (length=27)
'followers_url' => string 'https://api.github.com/users/macocci7/followers' (length=47)
'following_url' => string 'https://api.github.com/users/macocci7/following{/other_user}' (length=60)
'gists_url' => string 'https://api.github.com/users/macocci7/gists{/gist_id}' (length=53)
'starred_url' => string 'https://api.github.com/users/macocci7/starred{/owner}{/repo}' (length=60)
'subscriptions_url' => string 'https://api.github.com/users/macocci7/subscriptions' (length=51)
'organizations_url' => string 'https://api.github.com/users/macocci7/orgs' (length=42)
'repos_url' => string 'https://api.github.com/users/macocci7/repos' (length=43)
'events_url' => string 'https://api.github.com/users/macocci7/events{/privacy}' (length=54)
'received_events_url' => string 'https://api.github.com/users/macocci7/received_events' (length=53)
'type' => string 'User' (length=4)
'site_admin' => boolean false
'name' => string 'macocci7' (length=8)
'company' => null
'blog' => string '' (length=0)
'location' => string 'Japan' (length=5)
'email' => string 'macocci7@yahoo.co.jp' (length=20)
'hireable' => null
'bio' => string 'full stack web engineer.' (length=24)
'twitter_username' => null
'public_repos' => int 14
'public_gists' => int 4
'followers' => int 36
'following' => int 88
'created_at' => string '2016-05-04T04:59:39Z' (length=20)
'updated_at' => string '2024-01-24T12:27:14Z' (length=20)
public 'attributes' =>
array (size=6)
'id' => int 19181121
'nodeId' => string 'MDQ6VXNlcjE5MTgxMTIx' (length=20)
'nickname' => string 'macocci7' (length=8)
'name' => string 'macocci7' (length=8)
'email' => string 'macocci7@yahoo.co.jp' (length=20)
'avatar' => string 'https://avatars.githubusercontent.com/u/19181121?v=4' (length=52)
public 'token' => string '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' (length=40)
public 'refreshToken' => null
public 'expiresIn' => null
public 'approvedScopes' => null
GitHubプロフィールを表示してみる
ログイン後のダッシュボードにGitHubプロフィールを表示してみます。
「resources/views/components/」フォルダ内に「github」フォルダを作成し、
その中に新しいファイル「profile.blade.php」を作成します。
<!-- GitHub Profile //-->
<div class="mt-4 mb-4 border border-gray-800 rounded-lg bg-gray-100">
<p class="ml-4 mt-2 font-semibold text-lg text-gray-500">GitHub Profile:</p>
<div class="flex ml-2 py-4">
<a href="{{ $githubUser->user['html_url'] }}" target="_blank">
<img
src="{{ $githubUser->avatar }}"
width="120"
height="120"
alt="Avatar: {{ $githubUser->name }}"
title="{{ $githubUser->name }}"
class="ml-4 rounded-full"
/>
</a>
<ul class="ml-4 text-gray-500">
<li>Name: <a href="{{ $githubUser->user['html_url'] }}" target="_blank">{{ $githubUser->name }}</a></li>
<li>Email: {{ $githubUser->email }}</li>
<li>
<span>followers: {{ $githubUser->user['followers'] }}</span>
<span class="ml-4">following: {{ $githubUser->user['following'] }}</span>
</li>
<li>bio: {{ $githubUser->user['bio'] }}</li>
<li>location: {{ $githubUser->user['location'] }}</li>
</ul>
</div>
</div>
<!-- // GitHub Profile -->
ダッシュボードのビューファイルに読み込ませます。
「resources/views/dashboard.blade.php」を編集します。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<x-github.profile
:github-user="$github_user"
/>
</div>
</div>
</div>
</div>
</x-app-layout>
「routes/web.php」を編集・保存します。
「/dashboard」の箇所に追記します。
Route::get('/dashboard', function () {
$githubToken = auth()->user()->github_token;
$githubUser = Socialite::driver('github')->userFromToken($githubToken);
return view('dashboard', [
'user' => auth()->user(),
'github_user' => $githubUser,
]);
})->middleware(['auth', 'verified'])->name('dashboard');
WEBブラウザでGitHubソーシャルログインし
http://localhost:8000/dashboard
ダッシュボードを開きます。
![](https://macocci7.net/blog/wp-content/uploads/2024/02/laravel_breeze_socialite3_01.png)
このように表示されました。
GitHubAPIで他の情報も取得してみる
ここから先はSocialiteは全く関係ありませんが、
折角なので、公開リポジトリ、フォロワーリスト、フォローリストくらいは追加で表示してみたいと思います。
GitHub APIを叩いて情報を取得していきます。
![](https://macocci7.net/blog/wp-content/uploads/cocoon-resources/blog-card-cache/513a2fe403a179cd13970f0e76bdc69e.png)
公開リポジトリの一覧は、
https://api.github.com/users/[ログインID]/repos
フォロワーリストは、
https://api.github.com/users/[ログインID]/followers
フォローリストは、
https://api.github.com/users/[ログインID]/following
で取得することができます。
それぞれ、URLパラメータ
?per_page=[1ページ当たりの件数]&page=[ページ番号]
を追加することでページングできます。
例えば、
で出力される内容は
[
{
"id": 698479567,
"node_id": "R_kgDOKaHzzw",
"name": "PHP-PhotoGps",
"full_name": "macocci7/PHP-PhotoGps",
"private": false,
"owner": {
"login": "macocci7",
・・・(中略)・・・
},
"html_url": "https://github.com/macocci7/PHP-PhotoGps",
"description": "Gets GPS data from a photo.",
"fork": false,
"url": "https://api.github.com/repos/macocci7/PHP-PhotoGps",
・・・(中略)・・・
"created_at": "2023-09-30T03:45:44Z",
"updated_at": "2023-11-05T21:40:46Z",
・・・(中略)・・・
"stargazers_count": 0,
"watchers_count": 0,
"language": "PHP",
・・・(中略)・・・
"forks": 0,
"open_issues": 1,
"watchers": 0,
"default_branch": "main"
}
]
このような感じです。かなり長いので省略しています。
フォロワーリストの例は、
[
{
"login": "cryptobear0108",
"id": 79739656,
"node_id": "MDQ6VXNlcjc5NzM5NjU2",
"avatar_url": "https://avatars.githubusercontent.com/u/79739656?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/cryptobear0108",
"html_url": "https://github.com/cryptobear0108",
"followers_url": "https://api.github.com/users/cryptobear0108/followers",
"following_url": "https://api.github.com/users/cryptobear0108/following{/other_user}",
"gists_url": "https://api.github.com/users/cryptobear0108/gists{/gist_id}",
"starred_url": "https://api.github.com/users/cryptobear0108/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/cryptobear0108/subscriptions",
"organizations_url": "https://api.github.com/users/cryptobear0108/orgs",
"repos_url": "https://api.github.com/users/cryptobear0108/repos",
"events_url": "https://api.github.com/users/cryptobear0108/events{/privacy}",
"received_events_url": "https://api.github.com/users/cryptobear0108/received_events",
"type": "User",
"site_admin": false
},
{
"login": "najlae01",
"id": 88176530,
"node_id": "MDQ6VXNlcjg4MTc2NTMw",
"avatar_url": "https://avatars.githubusercontent.com/u/88176530?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/najlae01",
"html_url": "https://github.com/najlae01",
"followers_url": "https://api.github.com/users/najlae01/followers",
"following_url": "https://api.github.com/users/najlae01/following{/other_user}",
"gists_url": "https://api.github.com/users/najlae01/gists{/gist_id}",
"starred_url": "https://api.github.com/users/najlae01/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/najlae01/subscriptions",
"organizations_url": "https://api.github.com/users/najlae01/orgs",
"repos_url": "https://api.github.com/users/najlae01/repos",
"events_url": "https://api.github.com/users/najlae01/events{/privacy}",
"received_events_url": "https://api.github.com/users/najlae01/received_events",
"type": "User",
"site_admin": false
}
]
このような感じです。
フォローリストの例は、
[
{
"login": "soxofaan",
"id": 44946,
"node_id": "MDQ6VXNlcjQ0OTQ2",
"avatar_url": "https://avatars.githubusercontent.com/u/44946?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/soxofaan",
"html_url": "https://github.com/soxofaan",
"followers_url": "https://api.github.com/users/soxofaan/followers",
"following_url": "https://api.github.com/users/soxofaan/following{/other_user}",
"gists_url": "https://api.github.com/users/soxofaan/gists{/gist_id}",
"starred_url": "https://api.github.com/users/soxofaan/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/soxofaan/subscriptions",
"organizations_url": "https://api.github.com/users/soxofaan/orgs",
"repos_url": "https://api.github.com/users/soxofaan/repos",
"events_url": "https://api.github.com/users/soxofaan/events{/privacy}",
"received_events_url": "https://api.github.com/users/soxofaan/received_events",
"type": "User",
"site_admin": false
},
{
"login": "facebook",
"id": 69631,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjY5NjMx",
"avatar_url": "https://avatars.githubusercontent.com/u/69631?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/facebook",
"html_url": "https://github.com/facebook",
"followers_url": "https://api.github.com/users/facebook/followers",
"following_url": "https://api.github.com/users/facebook/following{/other_user}",
"gists_url": "https://api.github.com/users/facebook/gists{/gist_id}",
"starred_url": "https://api.github.com/users/facebook/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/facebook/subscriptions",
"organizations_url": "https://api.github.com/users/facebook/orgs",
"repos_url": "https://api.github.com/users/facebook/repos",
"events_url": "https://api.github.com/users/facebook/events{/privacy}",
"received_events_url": "https://api.github.com/users/facebook/received_events",
"type": "Organization",
"site_admin": false
}
]
このような感じです。
スタックの選択
スタックの選択は何でも良いのですが、
筆者の趣味としてVue.jsを使います。
今回は実験的な内容なのでCDN版を使います。
ページネーションはVuetify3のv-paginationを使います。
※最初はvuejs-paginate-nextで実装を進めていたのですが、動作が不安定な上に、イベント発火していない筈がAPIをフルスピードで数万回連打してしまい、気付いた時にはAPIのアクセスレート制限超過でブロックされる事態になっていました。。状況が改善されなかった為、Vuetify3に変更しました。
ダッシュボードビューファイル編集
「resources/views/dashboard.blade.php」を編集・保存します。
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify@3.5.4/dist/vuetify.min.css"></link>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css">
<script src="https://cdn.jsdelivr.net/npm/vuetify@3.5.4/dist/vuetify.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/scripts/verify.min.js"></script>
<x-github.profile
:github-user="$github_user"
/>
<x-github.attributes.container
:github-user="$github_user"
/>
</div>
</div>
</div>
</div>
</x-app-layout>
変更点は、
- Vue.jsのCDN版読込追加
- VuetifyのCDN版読込追加
- MDIのCDN版読込追加
- GitHub属性表示用テンプレート読込追加
です。
GitHub属性表示用テンプレート作成
「resources/views/components/github/」フォルダ内に「attributes」フォルダを作成し、その中に「container.blade.php」を作成します。
<!-- github-attributes // -->
<div id="github-attributes">
<x-github.attributes.tabs />
<x-github.attributes.stats
:githubUser="$githubUser"
/>
<x-github.attributes.repos
:githubUser="$githubUser"
/>
<x-github.attributes.followers
:githubUser="$githubUser"
/>
<x-github.attributes.follows
:githubUser="$githubUser"
/>
</div>
<!-- // github-attributes -->
<x-github.attributes.script
:github-user="$githubUser"
/>
<x-github.attributes.style />
同じフォルダ内に「tabs.blade.php」を作成します。
<!-- GitHub Tabs // -->
<div class="github-tabs">
<ul class="flex justify-between py-0 my-2 border-b-2">
<li
v-for="(tab,tabKey) in tabs"
:class="{'tab': true, 'tab-active': tabKey == currentTab}"
@click="activateTab(tabKey)"
>
@{{ tab.name }}
</li>
</ul>
</div>
<!-- // GitHub Tabs -->
同じフォルダ内に「stats.blade.php」を作成します。
<!-- GitHub Stats //-->
<div :class="{hide: tabs[currentTab].name !== 'GitHub Stats'}">
<img align="center" src="https://github-readme-stats.vercel.app/api?username={{ $githubUser->name }}&show_icons=true&theme=shadow_green&rank_icon=percentile&include_all_commits=true&theme=transparent&hide_border=true" alt="{{ $githubUser->name }}'s github stats" />
<img align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username={{ $githubUser->name }}&layout=compact&theme=buefy&hide_border=true" />
</div>
<!-- // GitHub Stats -->
同じフォルダ内に「repos.blade.php」を作成します。
<!-- Public Repos //-->
<div :class="{hide: tabs[currentTab].name !== 'Public Repos'}">
<h3 class="font-semibold text-lg">Public Repos ({{ $githubUser->user['public_repos']}}):</h3>
<ul class="repos-list">
<li
v-for="repo in repos.data"
class="mt-2 p-4 bg-gray-100 border border-gray-900"
>
<a
:href="repo['html_url']"
target="_blank"
>
<p class="font-semibold text-gray-700">@{{ repo['full_name'] }}</p>
<p class="ml-4 text-gray-600">
@{{ repo['description'] }}
</p>
<p class="text-gray-500">
<span class="ml-4">★ @{{ repo['stargazers_count'] }}</span>
<span class="ml-4">watch @{{ repo['watchers_count'] }}</span>
<span class="ml-4">fork @{{ repo['forks_count'] }}</span>
<span class="ml-4">issues @{{ repo['open_issues_count'] }}</span>
</p>
</a>
</li>
</ul>
<!-- Vuetify Pagination // -->
<div class="pagination flex justify-start">
<v-pagination
v-model="repos.currentPage"
:length="repos.pageCount"
:total-visible="6"
:size="32"
:color="'blue-darken-4 bg-blue-lighten-5'"
:active-color="'white bg-blue-darken-3'"
:rounded="'sm'"
@click = "fetchRepos"
>
</v-pagination>
</div>
<!-- // Vuetify Pagination -->
</div>
<!-- // Public Repos -->
同じフォルダ内に「follwers.blade.php」を作成します。
<!-- Followers // -->
<div :class="{hide: tabs[currentTab].name !== 'Followers'}">
<p class="font-semibold text-gray-800 text-lg">Followers ({{ $githubUser->user['followers']}})</p>
<ul class="followers-list">
<li
v-for="(follower, followerIndex) in followers.data"
class="mt-2 p-4 bg-gray-100 border border-gray-900"
>
<a
:href="follower.html_url"
target="_blank"
class="flex"
>
<img
:src="follower.avatar_url"
width="40"
height="40"
:alt="follower.login"
:title="follower.login"
class="rounded-full"
/>
<span class="ml-4">@{{ follower.login }}</span>
</a>
</li>
</ul>
<!-- Vuetify Pagination // -->
<div class="pagination flex justify-start">
<v-pagination
v-model="followers.currentPage"
:length="followers.pageCount"
:total-visible="6"
:size="32"
:color="'blue-darken-4 bg-blue-lighten-5'"
:active-color="'white bg-blue-darken-3'"
:rounded="'sm'"
:previous-aria-label="'<'"
:next-aria-label="'>'"
@click = "fetchFollowers"
>
</v-pagination>
</div>
<!-- // Vuetify Pagination -->
</div>
<!-- // Followers -->
同じフォルダ内に「follows.blade.php」を作成します。
<!-- Follows // -->
<div :class="{hide: tabs[currentTab].name !== 'Follows'}">
<p class="font-semibold text-gray-800 text-lg">Follows ({{ $githubUser->user['following']}})</p>
<ul class="follows-list">
<li
v-for="(follow, followsIndex) in follows.data"
class="flex mt-2 p-4 bg-gray-100 border border-gray-900"
>
<a
:href="follow.html_url"
target="_blank"
class="flex"
>
<img
:src="follow.avatar_url"
width="40"
height="40"
:alt="follow.login"
:title="follow.login"
class="rounded-full"
/>
<span class="ml-4">@{{ follow.login }}</span>
</a>
</li>
</ul>
<!-- Vuetify Pagination // -->
<div class="pagination flex justify-start">
<v-pagination
v-model="follows.currentPage"
:length="follows.pageCount"
:total-visible="6"
:size="32"
:color="'blue-darken-4 bg-blue-lighten-5'"
:active-color="'white bg-blue-darken-3'"
:rounded="'sm'"
@click = "fetchFollows"
>
</v-pagination>
</div>
<!-- // Vuetify Pagination -->
</div>
<!-- // Follows -->
同じフォルダ内に「script.blade.php」を作成します。
<script>
Vue.createApp({
data () {
return {
currentTab: 0,
tabs: {
0: { name: 'GitHub Stats' },
1: { name: 'Public Repos' },
2: { name: 'Followers' },
3: { name: 'Follows' },
},
repos: {
url: '{{ $githubUser->user["repos_url"] }}',
perPage: 10,
pageCount: Math.ceil({{ $githubUser->user['public_repos'] }} / 10 ),
currentPage: 1,
data: [],
},
followers: {
url: '{{ $githubUser->user["followers_url"] }}',
perPage: 10,
pageCount: Math.ceil({{ $githubUser->user['followers'] }} / 10 ),
currentPage: 1,
data: [],
},
follows: {
url: '{{ str_replace('{/other_user}', '', $githubUser->user["following_url"]) }}',
perPage: 10,
pageCount: Math.ceil({{ $githubUser->user['following'] }} / 10 ),
currentPage: 1,
data: [],
},
}
},
methods: {
async fetchRepos () {
const url = this.repos.url
+ '?per_page=' + this.repos.perPage
+ '&page=' + this.repos.currentPage;
const res = await fetch(url);
this.repos.data = await res.json();
},
async fetchFollowers () {
const url = this.followers.url
+ '?per_page=' + this.followers.perPage
+ '&page=' + this.followers.currentPage;
const res = await fetch(url);
this.followers.data = await res.json();
},
async fetchFollows () {
const url = this.follows.url
+ '?per_page=' + this.follows.perPage
+ '&page=' + this.follows.currentPage;
const res = await fetch(url);
this.follows.data = await res.json();
},
activateTab (tabKey) {
this.currentTab = tabKey;
if (this.tabs[tabKey].name == 'Public Repos') {
this.fetchRepos();
} else if (this.tabs[tabKey].name == 'Followers') {
this.fetchFollowers();
} else if (this.tabs[tabKey].name == 'Follows') {
this.fetchFollows();
}
},
},
})
.use(Vuetify.createVuetify())
.mount('#github-attributes');
</script>
同じフォルダ内に「style.blade.php」を作成します。
<style>
.pagination {
flex;
}
.pagination li {
justify-content: center;
margin-right: 6px;
}
.pagination li.active {
background-color: #3333ff;
color: #ffffff;
font-weight: bold;
}
.pagination li:hover:not(.disabled):not(.active) {
background-color: #ffee33;
}
.hide {
display: none;
}
.tab {
color: #999999;
}
.tab-active {
border-bottom: 2px solid #999999;
color: #333333;
font-weight: bold;
}
.repos-list, .followers-list, .follows-list {
display: flex;
flex-wrap: wrap;
}
.repos-list li {
width: 320px;
margin: 6px;
}
.followers-list li, .follows-list li {
width: 240px;
margin: 6px;
}
</style>
ブラウザ確認
WEBブラウザでダッシュボードページを再読み込みします。
![](http://macocci7.net/blog/wp-content/uploads/2024/02/laravel_breeze_socialite3_02.png)
GitHubプロフィールの下に、4つのタブと、
デフォルトで開いている「GitHub Stats」が表示されました。
「Public Repos」タブをクリックしてみます。
![](http://macocci7.net/blog/wp-content/uploads/2024/02/laravel_breeze_socialite3_03-1024x666.png)
リポジトリが表示されました。
「Followers」タブをクリックしてみます。
![](https://macocci7.net/blog/wp-content/uploads/2024/02/laravel_breeze_socialite3_04-1024x666.png)
フォロワーリストが表示されました。
ページネーションも機能しています。
「Follows」タブをクリックしてみます。
![](https://macocci7.net/blog/wp-content/uploads/2024/02/laravel_breeze_socialite3_05-1024x666.png)
フォローリストが表示されました。
ページネーションも機能しています。
今回は以上です。お疲れさまでした。
コメント