PHP + cURL 実装で注意すること
- POSTパラメータを http_build_query()に通して application/x-www-form-urlencoded 扱いにする
- 必要に応じて文字エンコーディングの変換を行う
cURL でPOSTするパラメータを http_build_query()
に通さない場合、multipart/form-data
で送信され文字化けの可能性が高くなります。また、Webアプリケーション側とAPI側で扱う文字エンコーディングが異なる場合、変換処理が必要になります。
実装例
API から返却されるデータの形式はQueryString( 例: name=taro&age=15&country=jpn ) 、Webアプリケーション側の文字エンコーディングはUTF-8、API側の文字エンコーディングはShift-JISと仮定した場合の例です。
<?php
public function request() {
$ch = curl_init();
// 必要に応じてPOSTパラメータを設定
$data = [
'hoge' => 1,
'fuga' => 2,
];
mb_convert_variables( 'SJIS-win', 'UTF-8', $data );
$options = [
CURLOPT_URL => 'https://example.com/api/path',
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query( $data ),
];
curl_setopt_array( $ch, $options );
$r = curl_exec( $ch );
$r = mb_convert_encoding( $r, 'UTF-8', 'SJIS-win' );
// QueryStringを配列に変換
$response = [];
parse_str( $r, $response );
// parse_str() を使うと「+」が半角スペースに変わってしまうため、
// 返却値に「+」が含まれるデータの場合は元に戻します。
$response[ 'key' ] = str_replace( '', '+', $response['key'] );
curl_close( $ch );
return $response;
}
API から返却されるデータ形式がjsonであれば、parse_str( $r, $response )
を$response = json_decode( curl_exec( $ch ), true );
に変更すればOKです。
CURLOPT_xxx
設定値の意味は公式リファレンスを参照してください。
CakePHPでコンポーネント化して使う例
CakePHP で使う場合はコンポーネント化したほうがよいでしょう。例えば、「API を使ってクーポン情報を取得したい」というケースでは、以下のようなコンポーネントを作成します。
<?php
class HogeAPIComponent extends Component {
// configs
private $__base_url = null;
private $__id = null;
private $__pass = null;
// params
private $__path = null;
private $__data = null;
private $__timeout = null;
private $__options = null;
/**
* 初期化の際に API に関する設定情報を読み込む
* (core.php などで定義しておく)
*
* @param [Controller] $controller
* @return [void]
*/
public function initialize( Controller $controller ) {
$this->Controller = $controller;
$this->__base_url = Configure::read( 'HogeAPI.base_url' );
$this->__id = Configure::read( 'HogeAPI.id' );
$this->__pass = Configure::read( 'HogeAPI.pass' );
if ( empty( $this->__base_url ) ||
empty( $this->__id ) ||
empty( $this->__pass ) || ) {
throw new InternalErrorException( 'HogeAPIComponent failed to read configure.' );
}
}
/**
* クーポン情報を取得する
* @param [array] $params
* @return [array] $response
*/
public function searchCoupon( $params ) : array {
$params['path'] = 'coupon/search/';
$params['data']['api_id'] = $this->__id;
$params['data']['api_pass'] = $this->__pass;
$response = $this->request( $params );
if ( 'APIのエラーコードをもとに判定' ) {
// 弾くなりログを吐くなり何かしらの処理
}
return $response;
}
/**
* API リクエスト送信
* @param [array] $params
* @return [array] $response
*/
public function request( array $params = [] ) : array {
$this->__clear();
$this->__bind( $params );
$ch = curl_init();
curl_setopt_array( $ch, $this->__options );
$r = curl_exec( $ch );
$r = mb_convert_encoding( $r, 'UTF-8', 'SJIS-win' );
// QueryStringを配列に変換
// parse_str() は 「+」 を半角スペースに変換するため注意
$response = [];
parse_str( $r, $response );
curl_close( $ch );
return $response;
}
/**
* 設定中のパラメータを初期化
* @return [void]
*/
private function __clear() {
$this->__path = null;
$this->__data = null;
$this->__timeout = null;
$this->__options = null;
}
/**
* パラメータバインド
* @param [array] $params
* @return [void]
*/
private function __bind( array $params ) {
$this->path = $params['path'] ?? null;
$this->data = $params['data'] ?? null;
$this->timeout = $params['timeout'] ?? 60;
$this->__makeOptions();
}
/**
* cURL オプション設定
* 送信データは文字化け回避のため、http_build_query() を通して
* Content-Type: application/x-www-form-urlencoded で送る。
*
* @return [void]
*/
private function __makeOptions() {
mb_convert_variables( 'SJIS-win', 'UTF-8', $this->__data );
$this->__options = [
CURLOPT_URL => $this->__base_url . $this->__path,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => $this->__timeout,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query( $this->__data ),
];
}
}
コンフィグは以下のようになっていると仮定します。
<?php
Configure::write( 'HogeAPI.base_url', 'https://example.com/hogeapi/' );
Configure::write( 'HogeAPI.id', 'APIアクセス用ID' );
Configure::write( 'HogeAPI.pass', 'APIアクセス用パスワード' );
最後に、このコンポーネントを使用してクーポンNo.100の情報を取得する例です。
<?php
class HogeController extends AppController {
public $components = [
'HogeAPI',
];
public function search() {
$coupon = $this->HogeAPI->searchCoupon( [
'data' => [ 'coupon_id' => 100 ],
] );
}
}
上記例は完全な固定値なので問題ありませんが、パラメータにユーザ入力値を含むようなケースでは必ずバリデーション処理を行い、不正リクエストを弾くようにしましょう。
意図しないパラメータを完全に除外するため、ホワイトリスト形式で弾くのが理想です。