google-api-php-client でどんな通信が行われているか見るために Guzzle の Middleware を使う
PHPでGoogle Play Developer APIを使ってAndroidのレシートを検証したいときは、こんな感じでやればOKです。 ohshige.hatenablog.com
APIを実データで叩くだけなら問題ないのですが、モック化したいということがありました。
モック化のやり方は良いとして、そもそもgoogle-api-php-client内部でどういうデータのやり取りをしているのか、よく理解できていません。
そこで、そのデータのやり取りを覗いてみることにしました。
google-api-php-clientではAPI通信の処理でGuzzleクライアントが使われています。
しかも、そのGuzzleクライアントは自由にカスタマイズできます。
まず、google-api-php-clientで使われるGuzzleクライアントを明示的に指定する方法は以下の通りです。
$http = new \GuzzleHttp\Client();
$client = new \Google_Client();
$client->setHttpClient($http);
これだけで明示的に指定できるので、指定するGuzzleクライアント経由でリクエストとレスポンスを確認できそうです。
Guzzleクライアント経由でリクエストとレスポンスを見るために、Middlewareを使います。 docs.guzzlephp.org
以下のように、リクエストとレスポンスをver_dumpするようなコールバック関数をGuzzleクライアントに指定して、それをGoogle_Clientに設定すれば、どんな通信がなされているか簡単にわかります。
$stack = new \GuzzleHttp\HandlerStack(); $stack->setHandler(\GuzzleHttp\choose_handler()); $stack->push(function (callable $handler) { return function ( \Psr\Http\Message\RequestInterface $request, array $options ) use ($handler) { var_dump("<============================== request start ==============================>"); var_dump($request); $promise = $handler($request, $options); return $promise->then( function (\Psr\Http\Message\ResponseInterface $response) { var_dump($response->getBody()->getContents()); var_dump("<============================== request end ==============================>"); return $response; } ); }; }); $http = new \GuzzleHttp\Client(['handler' => $stack]); $client = new \Google_Client(); $client->setHttpClient($http);
上記の設定後、Google Play Developer APIを呼ぶための準備をして、あるサブスクリプションのレシート検証をしてみると以下のような出力があります。
$client->setAuthConfig([認証用JSONを設置したパス]); $client->setRedirectUri('http://localhost'); $client->setAccessType('offline'); $client->setApprovalPrompt('force'); $client->addScope(\Google_Service_AndroidPublisher::ANDROIDPUBLISHER); $client->refreshToken([リフレッシュトークン]); $publisher = new \Google_Service_AndroidPublisher($client); $publisher->purchases_subscriptions->get([packageName], [productId], [purchaseToken]);
string(77) "<============================== request start ==============================>" object(GuzzleHttp\Psr7\Request)#23 (7) { ["method":"GuzzleHttp\Psr7\Request":private]=> string(4) "POST" ["requestTarget":"GuzzleHttp\Psr7\Request":private]=> NULL ["uri":"GuzzleHttp\Psr7\Request":private]=> object(GuzzleHttp\Psr7\Uri)#18 (7) { ["scheme":"GuzzleHttp\Psr7\Uri":private]=> string(5) "https" ["userInfo":"GuzzleHttp\Psr7\Uri":private]=> string(0) "" ["host":"GuzzleHttp\Psr7\Uri":private]=> string(21) "oauth2.googleapis.com" ["port":"GuzzleHttp\Psr7\Uri":private]=> NULL ["path":"GuzzleHttp\Psr7\Uri":private]=> string(6) "/token" ["query":"GuzzleHttp\Psr7\Uri":private]=> string(0) "" ["fragment":"GuzzleHttp\Psr7\Uri":private]=> string(0) "" } ["headers":"GuzzleHttp\Psr7\Request":private]=> array(4) { ["User-Agent"]=> array(1) { [0]=> string(69) "GuzzleHttp/6.3.3" } ["Host"]=> array(1) { [0]=> string(21) "oauth2.googleapis.com" } ["Cache-Control"]=> array(1) { [0]=> string(8) "no-store" } ["Content-Type"]=> array(1) { [0]=> string(33) "application/x-www-form-urlencoded" } } ["headerNames":"GuzzleHttp\Psr7\Request":private]=> array(4) { ["user-agent"]=> string(10) "User-Agent" ["host"]=> string(4) "Host" ["cache-control"]=> string(13) "Cache-Control" ["content-type"]=> string(12) "Content-Type" } ["protocol":"GuzzleHttp\Psr7\Request":private]=> string(3) "1.1" ["stream":"GuzzleHttp\Psr7\Request":private]=> object(GuzzleHttp\Psr7\Stream)#21 (7) { ["stream":"GuzzleHttp\Psr7\Stream":private]=> resource(93) of type (stream) ["size":"GuzzleHttp\Psr7\Stream":private]=> NULL ["seekable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["readable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["writable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["uri":"GuzzleHttp\Psr7\Stream":private]=> string(10) "php://temp" ["customMetadata":"GuzzleHttp\Psr7\Stream":private]=> array(0) { } } } string(267) "{ "access_token": "__access_token__", "expires_in": 3600, "scope": "https://www.googleapis.com/auth/androidpublisher", "token_type": "Bearer" }" string(75) "<============================== request end ==============================>" string(77) "<============================== request start ==============================>" object(GuzzleHttp\Psr7\Request)#49 (7) { ["method":"GuzzleHttp\Psr7\Request":private]=> string(3) "GET" ["requestTarget":"GuzzleHttp\Psr7\Request":private]=> NULL ["uri":"GuzzleHttp\Psr7\Request":private]=> object(GuzzleHttp\Psr7\Uri)#41 (7) { ["scheme":"GuzzleHttp\Psr7\Uri":private]=> string(5) "https" ["userInfo":"GuzzleHttp\Psr7\Uri":private]=> string(0) "" ["host":"GuzzleHttp\Psr7\Uri":private]=> string(18) "www.googleapis.com" ["port":"GuzzleHttp\Psr7\Uri":private]=> NULL ["path":"GuzzleHttp\Psr7\Uri":private]=> string(278) "/androidpublisher/v3/applications/[packageName]/purchases/subscriptions/[productId]/tokens/[purchaseToken]" ["query":"GuzzleHttp\Psr7\Uri":private]=> string(0) "" ["fragment":"GuzzleHttp\Psr7\Uri":private]=> string(0) "" } ["headers":"GuzzleHttp\Psr7\Request":private]=> array(3) { ["Host"]=> array(1) { [0]=> string(18) "www.googleapis.com" } ["content-type"]=> array(1) { [0]=> string(16) "application/json" } ["User-Agent"]=> array(1) { [0]=> string(27) "google-api-php-client/2.2.3" } } ["headerNames":"GuzzleHttp\Psr7\Request":private]=> array(3) { ["host"]=> string(4) "Host" ["content-type"]=> string(12) "content-type" ["user-agent"]=> string(10) "User-Agent" } ["protocol":"GuzzleHttp\Psr7\Request":private]=> string(3) "1.1" ["stream":"GuzzleHttp\Psr7\Request":private]=> object(GuzzleHttp\Psr7\Stream)#50 (7) { ["stream":"GuzzleHttp\Psr7\Stream":private]=> resource(136) of type (stream) ["size":"GuzzleHttp\Psr7\Stream":private]=> NULL ["seekable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["readable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["writable":"GuzzleHttp\Psr7\Stream":private]=> bool(true) ["uri":"GuzzleHttp\Psr7\Stream":private]=> string(10) "php://temp" ["customMetadata":"GuzzleHttp\Psr7\Stream":private]=> array(0) { } } } string(374) "{ "kind": "androidpublisher#subscriptionPurchase", "startTimeMillis": "1111111111111", "expiryTimeMillis": "9999999999999", "autoRenewing": false, "priceCurrencyCode": "JPY", "priceAmountMicros": "0000000000000", "countryCode": "JP", "developerPayload": "", "cancelReason": 1, "orderId": "XXXXXXXXXXXXXXXXXXXXXXXXXX", "purchaseType": 0, "acknowledgementState": 1 } " string(75) "<============================== request end ==============================>"
これを見ると2回の通信が行われていることがわかり、1回目はアクセストークンの取得、2回目は実際のpurchaseTokenの検証を、それぞれ実行しています。
リクエストもレスポンスもよくわかり、モック化の参考になります。
リフレッシュトークンの設定を外せば、2回目のAPIだけが呼ばれることになります。
モック化にあたっては、その2回目のAPIだけを考慮すれば良さそうです。
GuzzleのMiddlewareは便利で、単に通信を覗き見るだけでなく、ヘッダーを常に付与したり、レスポンスを変換したり、色々なことが可能になります。