問題
cURL関数を用いる際,CURLOPT_RETURNTRANSFER
のように使用頻度の高いものは,デフォルトオプションとして省略できるようにラッパー関数を作りたいところ.
$html = curl_exec(curl_init_with('https://github.com/mpyw'));
ところが,データをファイルとして保存したい場合にはCURLOPT_FILE
オプションを使う場合もあるだろう.
curl_exec(curl_init_with('https://github.com/mpyw', [CURLOPT_FILE => fopen('data.html', 'wb')]));
これらの要件をどちらも満たすように関数を作るにはどうすればいいだろうか.ただし言うまでもないが,オプションがデフォルトのものと衝突したときは当然,後から設定した方を優先したい.
以下のうち,正しく動作するものはどれ? (複数回答可)
function curl_init_with($url, array $options = [])
{
$options += [
CURLOPT_RETURNTRANSFER => true,
];
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function curl_init_with($url, array $options = [])
{
$options = [
CURLOPT_RETURNTRANSFER => true,
] + $options;
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function curl_init_with($url, array $options = [])
{
$options = array_merge($options, [
CURLOPT_RETURNTRANSFER => true,
]);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function curl_init_with($url, array $options = [])
{
$options = array_merge([
CURLOPT_RETURNTRANSFER => true,
], $options);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function curl_init_with($url, array $options = [])
{
$options = array_replace($options, [
CURLOPT_RETURNTRANSFER => true,
]);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function curl_init_with($url, array $options = [])
{
$options = array_replace([
CURLOPT_RETURNTRANSFER => true,
], $options);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
答え
正しく動作するのは F のみです!
function curl_init_with($url, array $options = [])
{
$options = array_replace([
CURLOPT_RETURNTRANSFER => true,
], $options);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
以下では,何故間違っているのかの解説を行います.
function curl_init_with($url, array $options = [])
{
$options = array_merge($options, [
CURLOPT_RETURNTRANSFER => true,
]);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function curl_init_with($url, array $options = [])
{
$options = array_merge([
CURLOPT_RETURNTRANSFER => true,
], $options);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
まず真っ先に array_merge
を使っている C と D は選択肢から外れます.この関数は数値キーに対してはキーを振り直すという動作を取りますが,CURLOPT_*
の定数は数値です.
function curl_init_with($url, array $options = [])
{
$options = [
CURLOPT_RETURNTRANSFER => true,
] + $options;
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
function curl_init_with($url, array $options = [])
{
$options = array_replace($options, [
CURLOPT_RETURNTRANSFER => true,
]);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
続いて, B と E も選択肢から外れます.キーが衝突したとき,+
演算子は最初に出現したものを優先して残し,逆に array_replace
関数は後から出現したもので上書きします. B と E はこれを考慮した際に,デフォルト値のほうが優先されるようになってしまっています.
function curl_init_with($url, array $options = [])
{
$options += [
CURLOPT_RETURNTRANSFER => true,
];
$ch = curl_init($url);
curl_setopt_array($ch, $options);
return $ch;
}
さて,一見こいつは正解に見えるんですが,実際に動かしてみると期待したように動きません. そこで,$options
の値に着目してみましょう. F との違いはこのようになるはずです.
[
CURLOPT_FILE => fopen('data.html', 'wb'),
CURLOPT_RETURNTRANSFER => true,
]
[
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FILE => fopen('data.html', 'wb'),
]
はい,実は順番って大事なんです.CURLOPT_RETURNTRANSFER
とCURLOPT_FILE
は互いに衝突し合うオプションです.後から出現したほうが前を上書きしてしまうんです.
-
CURLOPT_RETURNTRANSFER
をtrue
にしたとき
→ 成功時には結果文字列,失敗時にはfalse
を返す. -
CURLOPT_RETURNTRANSFER
をfalse
にしたとき
→ 成功時にtrue
,失敗時にはfalse
を返す.結果は標準出力する. -
CURLOPT_FILE
を設定したとき
→ 成功時にtrue
,失敗時にはfalse
を返す.結果はファイルに書き込む.
しかし, A でも呼び出し方次第で正常に動作させることはできます.以下のように書けば問題はありません.
curl_exec(curl_init_with('https://github.com/mpyw', [
CURLOPT_RETURNTRANSFER => false, // true でも false でもどっちでもいいw
CURLOPT_FILE => fopen('data.html', 'w'),
]));
でも全然直感的じゃないからできれば避けたいところ…