FuelPHPにてcurlでAPIにXML形式などで投げる方法

使い方を勘違いしてドハマりしたので、備忘録として残します。


今回動きを確認した環境



まずはXML形式ではなく、通常のパラメータで渡すやり方から。


curlを用いてPOSTでパラメータを普通にAPI等に渡す方法



(1) 通常のPHPの関数のみで実現するパターン
参考にしたサイト: http://php.net/manual/ja/book.curl.php#116122

<?php
$ch   = curl_init('http://localhost/request/api');
$params = array('userid' => 12,
                'data'   => 'sample');
curl_setopt($ch, CURLOPT_POSTFIELDS,     http_build_query($params));
curl_setopt($ch, CURLOPT_POST,           true);
curl_setopt($ch, CURLOPT_TIMEOUT,        30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);    // responseのstring化
$response = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
    $array_body = get_object_vars(simplexml_load_string($response));
    var_dump($array_body);
}
curl_close($ch);



(2) FuelPHPの関数を利用して実現するパターン
参考にしたサイト: http://qiita.com/uchiko/items/3cec115db0b99a25d089

<?php
$curl   = Request::forge('http://localhost/request/api', 'curl');
$params = array('userid' => 12,
                'data'   => 'sample');
$curl->set_params($params);
$curl->set_method('post');
$curl->set_option(CURLOPT_TIMEOUT, 30);
$response = $curl->execute()->response();
if ($response->status == 200) {
    $array_body = Format::forge($response->body(), 'xml')->to_array();
    var_dump($array_body);
}



ここまではまあ普通の方法なので、頑張ってぐぐればいけるはず。
で、本題は次の、パラメータをXML形式とかにして渡すやり方。


curlを用いてPOSTでパラメータをXML形式でAPI等に渡す方法



(3) 通常のPHPの関数のみで実現するパターン
参考にしたサイト: http://hydrocul.github.io/wiki/blog/2013/1111-php-post-curl.html

<?php
$ch = curl_init('http://localhost/request/api');
$xml  = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL
      . '<base_node><userid>12</userid><data>sample</data></base_node>';
curl_setopt($ch, CURLOPT_POSTFIELDS,     $xml);
curl_setopt($ch, CURLOPT_POST,           true);
curl_setopt($ch, CURLOPT_TIMEOUT,        30);
curl_setopt($ch, CURLOPT_HTTPHEADER,     array('Content-Type: text/xml'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);    // responseのstring化
$response = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
    $array_body = get_object_vars(simplexml_load_string($response));
    var_dump($array_body);
}
curl_close($ch);



(4) FuelPHPの関数を利用して実現するパターン
参考にしたサイト: http://fuelphp.jp/docs/1.7/classes/request/curl.html

<?php
$curl   = Request::forge('http://localhost/request/api', 'curl');
$params = array('userid' => 12,
                'data'   => 'sample');
$params = array('base_node' => $params);    // XMLの大外の要素を用意
$curl->set_params($params);
$curl->set_method('post');
$curl->set_option(CURLOPT_TIMEOUT, 30);
$curl->set_header('Content-Type', 'text/xml');   // 第一引数の文字の大小はこれを厳守
$response = $curl->execute()->response();
if ($response->status == 200) {
    $array_body = Format::forge($response->body(), 'xml')->to_array();
    var_dump($array_body);
}



こんな感じでした。
FuelPHPのcoreクラスの関数を使う場合の注意点は以下の2点です。

  • POSTで投げる場合、set_paramsにXMLの内容を配列で渡す
  • set_headerにContent-Typeとして'text/xml'や'application/xml'を指定



あとこの記事ではXML形式で送信することをメインに書いていますが、コンテンツタイプの指定によってはxmlの他にjsoncsvやserialize(?)での送信にも対応しているようです。(動かして確認まではしてないが、$auto_detect_formatsにそれらのフォーマットの用意もあるので、詳しくはRequest_Driverクラス参照)


余談1:パラメータをXMLで送ろうとしてドハマりした際の手順(失敗例)

(5) FuelPHPの関数を利用したがXMLの中身が消えているNGパターン

<?php
$curl   = Request::forge('http://localhost/request/api', 'curl');
$params = array('userid' => 12,
                'data'   => 'sample');
$xml = Format::forge($params)->to_xml(null, null, 'base_node');
// $curl->set_params($params);
$curl->set_method('post');
$curl->set_options(array(CURLOPT_TIMEOUT    => 30,
                         CURLOPT_POSTFIELDS => $xml));
$curl->set_header('Content-Type', 'text/xml');
$response = $curl->execute()->response();
if ($response->status == 200) {
    $array_body = Format::forge($response->body(), 'xml')->to_array();
    var_dump($array_body);
}



このパターンが何がいけなかったかというと、以下の点です。

  • POSTメソッドで送信しようとしている
  • set_paramsではなく、curlのoptionとしてCURLOPT_POSTFIELDSにxml文字列を渡していた
  • set_paramsは未設定の状態

この条件が重なると、送信直前に空のパラメータでCURLOPT_POSTFIELDSの要素が上書きされます。
そのため、送信されるXMLは想定していた

<?xml version="1.0" encoding="utf-8"?>
<base_node><userid>12</userid><data>sample</data></base_node>

ではなく、2行目が空になった状態の

<?xml version="1.0" encoding="utf-8"?>
<xml/>

になります。


なぜ2行目の情報が消えるかは、FuelPHPのcoreクラスの使い方が間違っていたからです。
FuelPHPのcoreフォルダ内にあるcoreクラスのうち、Request_Curlクラスのexecuteメソッドを見ると、送信methodの指定がある場合は

<?php
if ( ! empty($this->method))
{
	$this->options[CURLOPT_CUSTOMREQUEST] = $this->method;
	$this->{'method_'.strtolower($this->method)}();
}

により、Request_Curlクラスの method_xxxメソッドが呼び出されます。
POST指定時はmethod_postメソッドが呼ばれるため、以下の内容になります。

<?php
protected function method_post()
{
	$params = is_array($this->params) ? $this->encode($this->params) : $this->params;

	$this->set_option(CURLOPT_POST, true);
	$this->set_option(CURLOPT_POSTFIELDS, $params);
}

最初の行で、headerのContent-Typeに応じた形式にパラメータをエンコードしていますが、set_params未実施の場合はパラメータは空の配列のため、$params内には2行のXMLの文字列で用意され、そのうち2行目の内容は既に以下のようになっています。

<xml/>

method_postメソッドの最後の行ではset_optionにてCURLOPT_POSTFIELDSにparamsの内容をセット(上書き)しています。
set_optionsにてCURLOPT_POSTFIELDSに入れていたパラメータは、ここで消えていることになります。
さらに処理を追いかけると、method_postを実施後かつcurl_execを実行する前に

curl_setopt_array($connection, $this->options);

にてoptionsの内容がcurlハンドラに実際にセットされますが、既に空の配列を元にした内容でCURLOPT_POSTFIELDSの内容は上書き済みのため、最初にセットしたxmlの情報が消えていました、というオチです。


この辺りはFuelPHP側がこのように使うことを想定した作りになっているため、想定通りに関数を正しく利用すれば問題にはなりません。
ただ、公式ドキュメントにもあまりこういう詳細な使い方は載っていなかったのと、ぐぐってもその辺りの情報に巡り合えなかったので、記事として残しておきます。(このパターンに嵌る人もあまりいないとは思いますが…)
まあ良く分からなかったらFuelPHPのcoreクラスのソースを読めば何とかなるでしょう。たぶん。

(6) 送信内容の確認用ソース
上記のサンプルソースのリクエスト先URLの「http://localhost/request/api」は、Windows環境のLocalに入れたXAMPPとFuelPHPのcontrollerの組み合わせで、apacheのドキュメントルートにFuelPHPのソースのpublicを指定し、XMLで送られた内容とパラメータで送られた内容をデバッグでログ出力するのと、レスポンスに適当なXMLを返しているだけの以下のようなザックリとした内容を用意して確認してました。

<?php
class Controller_Request extends Controller {
    function action_api() {
        Log::debug(file_get_contents('php://input'));
        Log::debug(print_r($_POST, true));

        $response = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL
                  . '<base_node>'
                  .   '<foo>bar</foo>'
                  .   '<hoge>piyo</hoge>'
                  . '</base_node>';
        echo $response;
    }
}



余談2:はてなブログシンタックスハイライトについて


なんかPHPを指定しても、はてなブログ上でハイライトされなくて悲しすぎた。
この記事のPHPソースはjavascriptを指定して無理やりハイライトしてます。
調べたら他の言語は対応してるけどPHPはダメみたい。
(参考: http://mpon.hatenablog.com/entry/2014/12/10/000712 )
公式ヘルプ見るとphp載ってるんだけど、
(参考: http://help.hatenablog.com/entry/markup/syntaxhighlight )
使い方が間違ってるとか? でも他の言語指定する場合は動くので理屈が通らん。
はてなの中の人はPHPだけ超絶嫌いなんかな…。



2/12 追記:
コメントにて情報いただきました、ありがとうございます。
PHPの場合は、

<?php

のようにPHP開始タグがコードブロックに含まれていればハイライトが反映されるそうです。
本記事の上の方もその形式でコード部分を修正しました。