PHPとcURLを使って外部APIを叩く方法

PHP + cURL 実装で注意すること

  1. POSTパラメータを http_build_query()に通して application/x-www-form-urlencoded 扱いにする
  2. 必要に応じて文字エンコーディングの変換を行う

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 ],
    ] );
  }
}

上記例は完全な固定値なので問題ありませんが、パラメータにユーザ入力値を含むようなケースでは必ずバリデーション処理を行い、不正リクエストを弾くようにしましょう。

意図しないパラメータを完全に除外するため、ホワイトリスト形式で弾くのが理想です。