2015/01/08追記 ココカラ-->
本エントリ内で紹介している src/Module/OAuth/Twitter{Module|Provider}.php
は外部モジュールとして切り出し、 ray/oauth-module として公開しています。
<--ココマデ
つい昨日(2014/12/22)、BEAR.Sunday v1.0.0-alpha がリリースされました!
最初に
今ちょうどプライベートでBEAR.Sundayを使ったサービス開発を進めていて、TwitterへのOAuthという大きめの実装に着手しようとしていたので、この機会にAdventCalendarのエントリとして実装例を紹介します。
BEAR.Sunday と PHPoAuthLib
-
- リソース指向のPHPフレームワーク
- DIとAOPをサポート
- BEAR.Package、BEAR.Resource、BEAR.Sundayの3つのパッケージから構成される
- 2014/12/22に
v1.0.0-alpha
がリリースされましたが、自分のプロジェクトで使用しているバージョンがv0.10.5
のため、本エントリではv0.10系を使用する前提で書いています
-
- PHP製のOAuthライブラリ
- Twitter以外にも多くのサービスをサポートしている
- いくつか存在するPHPのOAuthライブラリの中で1番オブジェクト指向でしっかり設計されている(と思う(小並感))
本エントリで詳しく説明しないこと
-
BEAR.Sunday(v0.10系)のプロジェクト作成方法
$ composer create-project bear/skeleton:~0.10 My.Project $ cd My.Project $ composer install
- BEAR.SundayのDI(Dependency Injection)の仕組み
- OAuthの仕組み
- Twitterアプリの作成方法(=APIキーの取得方法)
Twitterアプリの設定
Twitterアプリの管理ページで Website と Callback URL を設定します。
プロジェクト構成
編集/追加した部分以外は、基本的にComposerコマンドで作成したそのままのプロジェクト構成です。
PHPoAuthLibのインストール
src/composer.json (編集)
"require": {
"php": "5.5.*",
"bear/package": "0.13.0",
+ "lusitanian/oauth": "~0.3",
"ext-apcu": "*",
"ext-mbstring": "*"
},
TwitterのAPIキーを定数として定義し、DIにより使用する
var/conf/constants.php (編集)
Twitter APIのConsumer Key
,Consumer Secret
とCallback URL
を定数として定義し、以降のロジックからDIの仕組みにより使用します。
'prod' => [
// DB
'master_db' => $masterDb,
'slave_db' => $slaveDb,
+ // Twitter API
+ 'twitter_api' => [
+ 'consumer_key' => '{YOUR_CONSUMER_KEY}',
+ 'consumer_secret' => '{YOUR_CONSUMER_SECRET}',
+ // OAuth時のコールバックURLパス
+ 'oauth_callback_path' => '/oauth/twitter/callback',
+ ],
],
PHPoAuthLibをDIする
src/Module/OAuth/TwitterProvider.php (追加)
PHPoAuthLibをBEAR.SundayのDIの仕組みから使うための初期化処理を記述します。
<?php
namespace My\Project\Module\OAuth;
use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Http\Uri\UriFactory;
use OAuth\Common\Storage\Session;
use OAuth\OAuth1\Service\Twitter;
use OAuth\ServiceFactory;
use Ray\Di\Di\Inject;
use Ray\Di\Di\Named;
use Ray\Di\ProviderInterface;
class TwitterProvider implements ProviderInterface
{
/**
* @var string
*/
private $consumerKey;
/**
* @var string
*/
private $consumerSecret;
/**
* @var string
*/
private $oauthCallbackPath;
/**
* @param array $apiConfig
*
* @Inject
* @Named("twitter_api")
*/
public function __construct($apiConfig)
{
$this->consumerKey = $apiConfig['consumer_key'];
$this->consumerSecret = $apiConfig['consumer_secret'];
$this->oauthCallbackPath = $apiConfig['oauth_callback_path'];
}
/**
* @return \OAuth\OAuth1\Service\Twitter
*/
public function get()
{
// コールバックURLの生成
$uri = (new UriFactory())->createFromSuperGlobalArray($_SERVER);
$uri->setPath($this->oauthCallbackPath);
$uri->setQuery('');
$callbackUrl = $uri->getAbsoluteUri();
$credentials = new Credentials(
$this->consumerKey,
$this->consumerSecret,
$callbackUrl
);
// Twitter用のOAuthサービスインスタンスを生成
return (new ServiceFactory())->createService('twitter', $credentials, new Session());
}
}
src/Module/OAuth/TwitterModule.php (追加)
上で作成したTwitterProvider
(のgetメソッドの戻り値)を\OAuth\OAuth1\Service\Twitter
に注入(束縛)するための設定を記述します。
<?php
namespace My\Project\Module\OAuth;
use OAuth\OAuth1\Service\Twitter;
use Ray\Di\AbstractModule;
use Ray\Di\Scope;
class TwitterModule extends AbstractModule
{
protected function configure()
{
$this->bind(Twitter::class)->toProvider(TwitterProvider::class)->in(Scope::SINGLETON);
}
}
src/Module/App/Dependency.php (編集)
protected function configure()
{
+ $this->install(new TwitterModule());
}
src/Module/AppModule.php (編集)
依存をアプリにインストールします。
protected function configure()
{
$this->install(new StandardPackageModule('Kawanamiyuu\Pentan', $this->context, dirname(dirname(__DIR__))));
// override module
// $this->install(new SmartyModule($this));
// $this->install(new AuraViewModule($this));
// install application dependency
- // $this->install(new App\Dependency);
+ $this->install(new App\Dependency);
// install application aspect
$this->install(new App\Aspect($this));
}
}
src/Inject/TwitterOAuthServiceInject.php (追加)
プログラムからはこのtrait
をuseすることでOAuthロジックを使用します。setTwitterOAuthService
で依存が注入されます。
<?php
namespace My\Project\Inject;
use OAuth\OAuth1\Service\Twitter;
use Ray\Di\Di\Inject;
trait TwitterOAuthServiceInject
{
/**
* @var Twitter
*/
protected $twitterOAuthService;
/**
* @param Twitter $twitter
*
* @Inject
*/
public function setTwitterOAuthService(Twitter $twitter)
{
$this->twitterOAuthService = $twitter;
}
}
Twitterの認証画面へのリダイレクト
src/Resource/Page/Oauth/Twitter/Redirect.php (追加)
Twitterの認証画面 にリダイレクトする ためのPageリソースです。
次のURLパスに対応します→ /oauth/twitter/redirect
<?php
namespace My\Project\Resource\Page\Oauth\Twitter;
use BEAR\Resource\ResourceObject;
use My\Project\Inject\TwitterOAuthServiceInject;
class Redirect extends ResourceObject
{
use TwitterOAuthServiceInject;
public function onGet()
{
// リクエストトークンを取得
$requestToken = $this->twitterOAuthService->requestRequestToken()->getRequestToken();
$url = $this->twitterOAuthService->getAuthorizationUri([
'oauth_token' => $requestToken,
'force_login' => 'true',
]);
// Twitterの認証画面へリダイレクト
header('Location:' . $url);
exit;
}
}
Twitterの認証画面からのコールバック
src/Resource/Page/Oauth/Twitter/Callback.php (追加)
Twitterの認証画面 からリダイレクトされる Pageリソースです。
次のURLパスに対応します→ /oauth/twitter/callback
<?php
namespace My\Project\Resource\Page\Oauth\Twitter;
use BEAR\Resource\ResourceObject;
use My\Project\Inject\TwitterOAuthServiceInject;
class Callback extends ResourceObject
{
use TwitterOAuthServiceInject;
/**
* @param string $oauth_token
* @param string $oauth_verifier
* @param string $denied
*/
public function onGet($oauth_token = null, $oauth_verifier = null, $denied = null)
{
// Twitterの認証画面で[キャンセル]ボタンを押された場合
if ($denied) {
$this['is_authorized'] = false;
return $this;
}
// アクセストークンを取得
$accessToken = $this->twitterOAuthService->requestAccessToken(
$oauth_token,
$oauth_verifier
);
// 認証成功時
$this['is_authorized'] = true;
$this['token'] = $accessToken->getAccessToken();
$this['token_secret'] = $accessToken->getAccessTokenSecret();
$this['user_id'] = $accessToken->getExtraParams()['user_id'];
$this['screen_name'] = $accessToken->getExtraParams()['screen_name'];
return $this;
}
}
src/Resource/Page/Oauth/Twitter/Callback.twig (追加)
※注意※
ここではサンプルコードとしての便宜上、取得したトークンを画面に表示します。プロダクションコードではDBに保存後リダイレクトするなどして適切にハンドリングします。
<html lang="ja">
<head>
<meta charset="utf-8" />
<style type="text/css">
dt { font-weight: bold; }
dt:after { content: ':'; }
</style>
</head>
<body>
{% if is_authorized %}
<dl>
<dt>Token</dt><dd>{{ token }}</dd>
<dt>Token Secret</dt><dd>{{ token_secret }}</dd>
</dl>
<dl>
<dt>user_id</dt><dd>{{ user_id }}</dd>
<dt>screen_name</dt><dd>{{ screen_name }}</dd>
</dl>
{% else %}
<p>
<a href="/oauth/twitter/redirect">リトライ</a>
</p>
{% endif %}
</body>
</html>
ローカルでの動作確認
1. ビルドインサーバを起動する
$ cd /path/to/My.Project
# DI、AOPのためのコンパイルキャシュを削除
$ php bin/clear.php
$ php -S localhost:8080 -t var/www
2. http://localhost:8080/oauth/twitter/redirect
にアクセスする
3. Twitterの認証画面にリダイレクトされる
4. [連携アプリを認証]ボタンを押す
5. http://localhost:8080/oauth/twitter/callback
にリダイレクトされる
アクセストークンが表示されればOK
最後に
今回作成したコードの大半がDIの仕組みに関する部分であったように、BEAR.Sundayを使った開発ではDIが実装の肝になってきます。
自分も今回初めて本格的に外部ライブラリをDIして使用するコードを書いてみてその仕組みの理解が進みました。
今後もBEAR.Sundayを使った開発を通してその設計思想を学んでいきたいと思います。
とりあえず次は今のプロジェクトのコードベースをv1.0.0-alpha
に移行しないと(*´ڡ`●)