11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CURLOPT_*のデフォルトオプション結合の罠

Last updated at Posted at 2016-08-11

問題

cURL関数を用いる際,CURLOPT_RETURNTRANSFERのように使用頻度の高いものは,デフォルトオプションとして省略できるようにラッパー関数を作りたいところ.

これだけでHTMLを取得できるように curl_init_with() 関数を作りたい
$html = curl_exec(curl_init_with('https://github.com/mpyw'));

ところが,データをファイルとして保存したい場合にはCURLOPT_FILEオプションを使う場合もあるだろう.

これだけでHTMLを保存できるように curl_init_with() 関数を作りたい
curl_exec(curl_init_with('https://github.com/mpyw', [CURLOPT_FILE => fopen('data.html', 'wb')]));

これらの要件をどちらも満たすように関数を作るにはどうすればいいだろうか.ただし言うまでもないが,オプションがデフォルトのものと衝突したときは当然,後から設定した方を優先したい.

以下のうち,正しく動作するものはどれ? (複数回答可)

A
function curl_init_with($url, array $options = [])
{
    $options += [
        CURLOPT_RETURNTRANSFER => true,
    ];
    $ch = curl_init($url);
    curl_setopt_array($ch, $options);
    return $ch;
}
B
function curl_init_with($url, array $options = [])
{
    $options = [
        CURLOPT_RETURNTRANSFER => true,
    ] + $options;
    $ch = curl_init($url);
    curl_setopt_array($ch, $options);
    return $ch;
}
C
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;
}
D
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;
}
E
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;
}
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;
}

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  
 
 
 
 
 
 

答え

正しく動作するのは F のみです!

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;
}

以下では,何故間違っているのかの解説を行います.

C
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;
}
D
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_*の定数は数値です

B
function curl_init_with($url, array $options = [])
{
    $options = [
        CURLOPT_RETURNTRANSFER => true,
    ] + $options;
    $ch = curl_init($url);
    curl_setopt_array($ch, $options);
    return $ch;
}
E
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 はこれを考慮した際に,デフォルト値のほうが優先されるようになってしまっています.

A
function curl_init_with($url, array $options = [])
{
    $options += [
        CURLOPT_RETURNTRANSFER => true,
    ];
    $ch = curl_init($url);
    curl_setopt_array($ch, $options);
    return $ch;
}

さて,一見こいつは正解に見えるんですが,実際に動かしてみると期待したように動きません. そこで,$optionsの値に着目してみましょう. F との違いはこのようになるはずです.

A の $options
[
    CURLOPT_FILE => fopen('data.html', 'wb'),
    CURLOPT_RETURNTRANSFER => true,
]
F の $options
[
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FILE => fopen('data.html', 'wb'),
]

はい,実は順番って大事なんです.CURLOPT_RETURNTRANSFERCURLOPT_FILEは互いに衝突し合うオプションです.後から出現したほうが前を上書きしてしまうんです.

  • CURLOPT_RETURNTRANSFERtrue にしたとき
    → 成功時には結果文字列,失敗時には false を返す.
  • CURLOPT_RETURNTRANSFERfalse にしたとき
    → 成功時にtrue,失敗時には false を返す.結果は標準出力する.
  • CURLOPT_FILE を設定したとき
    → 成功時にtrue,失敗時には false を返す.結果はファイルに書き込む.

しかし, A でも呼び出し方次第で正常に動作させることはできます.以下のように書けば問題はありません.

A も正しく動かすための記述
curl_exec(curl_init_with('https://github.com/mpyw', [
    CURLOPT_RETURNTRANSFER => false, // true でも false でもどっちでもいいw
    CURLOPT_FILE => fopen('data.html', 'w'),
]));

でも全然直感的じゃないからできれば避けたいところ…

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?