【PHP8.x】curl_multi_exec関数の使い方

作成日: 更新日:

curl_multi_exec関数は、複数のCURLハンドル(WebサーバーへのHTTPリクエストを管理するリソース)を並行して実行する関数です。この関数を使用することで、複数のネットワークリクエストを同時に処理し、全体の処理時間を大幅に短縮することが可能になります。例えば、複数の異なるWebサービスからデータを取得する必要がある場合に、一つずつ順番にリクエストを送信して待つよりも、すべてを並行して実行することで、システム全体の効率を向上させることができます。

この関数は、curl_multi_init関数で作成されたマルチCURLハンドル(複数の個別CURLハンドルをまとめたもの)を最初の引数として受け取ります。また、二番目の引数には、現在アクティブな(まだ処理が完了していない)CURLハンドルの数を格納するための参照変数を指定します。curl_multi_execが呼び出されるたびに、キューに追加されたリクエストのうち、処理可能なものが実行され、ネットワークからの応答が処理されます。

戻り値は、CURLM_OKCURLM_CALL_MULTI_PERFORMなどのCURLM定数で、関数の実行ステータスを示します。すべてのリクエストが完了するまで、この関数を繰り返し呼び出す必要があります。通常は、curl_multi_select関数などと組み合わせてループ内で使用され、ネットワークからのデータが利用可能になるまで効率的に待機しつつ、非同期に並行処理を進めていくことが一般的です。これにより、システムはネットワーク処理の完了を待つ間も、他のタスクを実行し続けることができます。

基本的な使い方

構文(syntax)

<?php
// CurlMultiHandle 型の変数を準備します
$multi_handle = curl_multi_init();

// 実行中のハンドルの数を格納する int 型の変数を参照渡しで準備します
$still_running = 0;

// curl_multi_exec 関数を呼び出し、戻り値 (int) を受け取ります
$result_code = curl_multi_exec($multi_handle, $still_running);

引数(parameters)

CurlMultiHandle $multi_handle, int &$still_running

  • CurlMultiHandle $multi_handle: 実行する複数のcURL転送を管理するCurlMultiHandleオブジェクト
  • int &$still_running: 実行中の転送数を格納する整数型変数への参照

戻り値(return)

int

現在処理されている、あるいは実行された転送の数を返します。

サンプルコード

PHP: curl_multi_execで並行リクエスト

<?php

/**
 * 複数のURLに並行してHTTPリクエストを送信し、その内容を取得します。
 * システムエンジニアを目指す初心者でも理解しやすいように、基本的なCURLマルチハンドルの利用方法を示します。
 *
 * @param array<string, string> $urls キーを識別子、値をURLとする連想配列。
 * @return array<string, string> キーを識別子、値をURLから取得したコンテンツ(またはエラーメッセージ)とする連想配列。
 * @throws RuntimeException CURLマルチハンドルの初期化に失敗した場合にスローされます。
 */
function fetchUrlsConcurrently(array $urls): array
{
    // 1. CURLマルチハンドルを初期化します。
    // これにより、複数のCURLリクエストを同時に管理できるようになります。
    $multi_handle = curl_multi_init();
    if ($multi_handle === false) {
        throw new RuntimeException("CURLマルチハンドルの初期化に失敗しました。");
    }

    $handles = []; // 個々のCURLハンドルを格納する配列
    $results = []; // 最終的な取得結果を格納する配列

    // 2. 各URLに対して個別のCURLハンドルを作成し、マルチハンドルに追加します。
    foreach ($urls as $key => $url) {
        $ch = curl_init();
        if ($ch === false) {
            // 個々のCURLハンドル作成に失敗した場合、エラーログを記録し、このURLはスキップします。
            error_log("CURLハンドル作成に失敗: " . $url);
            continue;
        }

        // CURLオプションを設定します。
        curl_setopt($ch, CURLOPT_URL, $url);             // リクエスト先のURLを設定
        curl_setopt($ch, CURLOPT_HEADER, 0);             // レスポンスヘッダーを結果に含めない
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  // レスポンスボディを文字列として返す

        // 個々のCURLハンドルをマルチハンドルに追加します。
        curl_multi_add_handle($multi_handle, $ch);
        $handles[$key] = $ch; // 後で結果を取得するために、識別子とともにハンドルを保存します。
    }

    // 3. すべてのリクエストが完了するまで、CURLマルチハンドルを実行します。
    $still_running = null; // まだ実行中のCURLハンドルの数を格納します。
    do {
        // curl_multi_exec() を呼び出し、リクエストの実行状況を進めます。
        // $still_running は参照渡しで、実行中のハンドル数が更新されます。
        $status = curl_multi_exec($multi_handle, $still_running);

        if ($still_running > 0) {
            // まだ実行中のリクエストがある場合、CPUの使用率を抑えるためにイベントを待機します。
            // 0.1秒 (100ミリ秒) 待機するか、ソケットの活動があるまで待機します。
            curl_multi_select($multi_handle, 0.1);
        }
    } while ($still_running > 0 && $status === CURLM_OK); // 実行中のハンドルがあり、エラーがない間ループを続けます。

    // 4. 全てのリクエストが完了したら、結果を取得し、ハンドルをクローズします。
    foreach ($handles as $key => $ch) {
        $error = curl_error($ch); // エラーメッセージを取得
        $errno = curl_errno($ch); // エラー番号を取得

        if ($errno === 0) {
            // エラーがない場合、リクエストのコンテンツを取得します。
            $results[$key] = curl_multi_getcontent($ch);
        } else {
            // エラーが発生した場合、エラーメッセージを結果に格納します。
            $results[$key] = "エラー ($errno): " . $error;
        }

        // マルチハンドルから個々のCURLハンドルを削除します。
        curl_multi_remove_handle($multi_handle, $ch);
        // 個々のCURLハンドルをクローズし、リソースを解放します。
        curl_close($ch);
    }

    // マルチCURLハンドルをクローズし、関連するリソースを解放します。
    curl_multi_close($multi_handle);

    return $results;
}

// --- 使用例 ---
$urls_to_fetch = [
    'google' => 'https://www.google.com/',
    'github' => 'https://github.com/',
    'example_com' => 'https://example.com/',
    'invalid_url' => 'http://this-url-does-not-exist-12345.com/', // エラーテスト用
];

echo "複数のURLからコンテンツを並行して取得中...\n";

try {
    $fetched_contents = fetchUrlsConcurrently($urls_to_fetch);

    // 取得したコンテンツまたはエラーメッセージを表示します。
    foreach ($fetched_contents as $name => $content) {
        echo "\n--- " . $name . " ---\n";
        if (str_starts_with($content, "エラー")) {
            echo $content . "\n"; // エラーメッセージを表示
        } else {
            // 取得したコンテンツの最初の200文字を表示します。
            echo substr($content, 0, 200) . "...\n";
        }
    }
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}

?>

PHPのcurl_multi_exec関数は、複数のHTTPリクエストを並行して実行し、その進行を管理する際に使用されます。これにより、複数のウェブサイトからコンテンツを効率的に同時に取得するなどの処理が可能になります。

この関数の最初の引数$multi_handleは、curl_multi_initで初期化されたCurlMultiHandle型のリソースで、実行する複数のCURLリクエストをまとめた集合体です。二番目の引数&$still_runningは参照渡しで、関数が呼び出されるたびに、まだ実行中(処理が完了していない)のCURLハンドルの数がこの変数に整数値として更新されます。戻り値は整数型で、通常はCURLM_OKなどのマルチCURLハンドルの現在の実行ステータスを示します。

サンプルコードでは、fetchUrlsConcurrently関数内でcurl_multi_initで初期化したマルチハンドルに、curl_multi_add_handleを使って個々のCURLリクエストを追加しています。その後、do-whileループの中でcurl_multi_exec($multi_handle, $still_running)を繰り返し呼び出し、追加された複数のリクエストをバックグラウンドで並行処理します。$still_runningが0になるまでループを続けることで、すべてのリクエストの完了を待ちます。ループ内では、curl_multi_selectを挟むことで、CPU使用率を抑えつつ効率的にイベントを待機します。すべてのリクエストが完了したら、curl_multi_getcontentでそれぞれの結果を取得し、curl_multi_remove_handlecurl_closeで関連するリソースを解放しています。このように、curl_multi_execはCURLによる並行処理を実現するための中心的な役割を担います。

curl_multi_initcurl_initで初期化したリソースは、必ずcurl_multi_closecurl_closeで解放する必要があります。解放を忘れると、メモリリークやリソース枯渇の原因となりますので、注意が必要です。curl_multi_execの第二引数$still_runningは参照渡しであり、実行中のリクエスト数がこの変数に更新されます。この値が0になるまでループを継続することで、全てのリクエストが処理されます。curl_multi_selectは、CPU負荷を抑えながら効率的に並行処理を行うために非常に重要です。これを省略すると、CPUを過剰に使用したり、無限ループに陥る可能性があります。また、個々のリクエストのエラーはcurl_errnocurl_errorで確認し、適切にエラーハンドリングを行うことで、堅牢なアプリケーションを構築できます。

【PHP8.x】curl_multi_exec関数の使い方 | いっしー@Webエンジニア