【PHP8.x】curl_multi_add_handle()関数の使い方
curl_multi_add_handle関数の使い方について、初心者にもわかりやすく解説します。
基本的な使い方
curl_multi_add_handle関数は、PHPで複数のCURL通信を効率的に並行処理するために使用される関数です。この関数は、事前にcurl_multi_init()で初期化された「マルチCURLハンドル」に、curl_init()で作成された「単一のCURLハンドル」を追加する役割を担います。
Webアプリケーション開発において、複数の外部APIに同時にリクエストを送信したり、多数のWebサイトから情報を並行して取得したりする場面があります。このような場合、通常のリクエストを一つずつ順に実行すると、前のリクエストの完了を待つ必要があり、処理時間が長くなってしまいます。curl_multi_add_handle関数を用いることで、複数の単一CURLハンドルを一つのマルチCURLハンドルにまとめて登録し、curl_multi_exec()関数で一括して実行することが可能になります。これにより、ネットワークの待ち時間を有効活用し、アプリケーション全体のパフォーマンスを向上させることができます。
具体的には、第一引数にcurl_multi_init()が返したマルチCURLリソースを、第二引数にはcurl_init()が返した単一CURLリソースを指定します。関数が成功すると0が返され、失敗した場合はメモリ不足を示すCURLM_OUT_OF_MEMORYのようなCURLMエラーコードが返されます。追加された単一CURLハンドルは、その後curl_multi_exec()やcurl_multi_select()といった関数と組み合わせて非同期通信に利用されます。
構文(syntax)
1<?php 2$multi_handle = curl_multi_init(); 3$ch = curl_init(); 4curl_setopt($ch, CURLOPT_URL, "https://example.com"); 5curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 6 7curl_multi_add_handle($multi_handle, $ch); 8 9curl_multi_remove_handle($multi_handle, $ch); 10curl_close($ch); 11curl_multi_close($multi_handle); 12?>
引数(parameters)
CurlMultiHandle $multi_handle, CurlHandle $handle
- CurlMultiHandle $multi_handle: 複数のCurlセッションを管理するためのリソース
- CurlHandle $handle: 追加する単一のCurlセッションリソース
戻り値(return)
int
curl_multi_add_handle関数は、指定されたcURLハンドルをマルチハンドルに追加できたかどうかを示す整数値を返します。成功した場合はCURLE_OK(通常は0)を、失敗した場合はエラーコードを返します。
サンプルコード
PHP cURLマルチで複数URLを並行取得する
1<?php 2 3/** 4 * 複数のURLに並行してHTTP GETリクエストを送信し、その内容を取得します。 5 * この関数は、cURLのマルチハンドラ機能(curl_multi_add_handle, curl_multi_selectなど)を利用して 6 * 効率的に複数のリクエストを処理する方法を示します。 7 * 8 * @param array<string> $urls フェッチするURLの配列 9 * @return array<string> 各URLのフェッチ結果。成功時はコンテンツ、失敗時はエラーメッセージ。 10 */ 11function fetch_multiple_urls_concurrently(array $urls): array 12{ 13 // 複数のcURLリクエストを並行して管理するためのマルチハンドラを初期化 14 $multi_handle = curl_multi_init(); 15 if ($multi_handle === false) { 16 return ['Error: Failed to initialize cURL multi handle.']; 17 } 18 19 $curl_handles = []; // 個々のcURLハンドラを格納する配列 20 $results = []; // フェッチ結果を格納する配列 21 22 // 各URLに対して個別のcURLハンドラを作成し、マルチハンドラに追加 23 foreach ($urls as $key => $url) { 24 $ch = curl_init(); // 個々のcURLリクエストを管理するハンドラを作成 25 if ($ch === false) { 26 $results[$key] = 'Error: Failed to initialize cURL handle for ' . $url; 27 continue; 28 } 29 30 // cURLオプションを設定 31 curl_setopt($ch, CURLOPT_URL, $url); // リクエスト対象のURL 32 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 実行結果を文字列で返す 33 curl_setopt($ch, CURLOPT_HEADER, false); // レスポンスヘッダを含めない 34 35 // curl_multi_add_handle を使用して、個々のハンドラをマルチハンドラに追加 36 // これにより、このリクエストが並行処理の対象となる 37 $add_result = curl_multi_add_handle($multi_handle, $ch); 38 if ($add_result !== CURLM_OK) { 39 $results[$key] = 'Error: Failed to add cURL handle to multi handle for ' . $url . ' (Code: ' . $add_result . ')'; 40 curl_close($ch); 41 continue; 42 } 43 $curl_handles[$key] = $ch; // 後で結果を取得するために保存 44 } 45 46 // 全てのcURLリクエストが完了するまで実行と監視を繰り返す 47 $running_handles = null; // まだ処理中のハンドラの数 48 do { 49 // cURLマルチハンドラのアクティビティを実行し、$running_handlesに処理中のハンドラ数をセット 50 $status = curl_multi_exec($multi_handle, $running_handles); 51 52 // curl_multi_execがCURLM_CALL_MULTI_PERFORMを返した場合、すぐに再実行が必要 53 if ($status === CURLM_CALL_MULTI_PERFORM) { 54 continue; 55 } 56 57 // まだアクティブなハンドラがあり、エラーがない場合、cURLアクティビティを待機 58 if ($running_handles > 0 && $status === CURLM_OK) { 59 // curl_multi_select を使用して、ファイルディスクリプタのアクティビティを待機 (最長1秒) 60 // これによりCPUを不必要に消費することなく、イベント駆動型の処理が可能になる 61 $select_result = curl_multi_select($multi_handle, 1.0); 62 if ($select_result === -1) { 63 // selectエラーまたはタイムアウトの場合、短いスリープを入れてCPU負荷を軽減 64 usleep(1000); // 1ミリ秒 65 } 66 } 67 68 } while ($running_handles > 0 && $status === CURLM_OK); // 処理中のハンドラがあるか、エラーが発生するまでループ 69 70 // 各cURLハンドラから結果を取得し、後処理 71 foreach ($curl_handles as $key => $ch) { 72 $error = curl_error($ch); 73 if (empty($error)) { 74 // エラーがなければ、フェッチしたコンテンツを取得 75 $results[$key] = curl_multi_getcontent($ch); 76 } else { 77 // エラーがあれば、エラーメッセージを保存 78 $results[$key] = 'Error: ' . $error; 79 } 80 81 // マルチハンドラから個々のハンドラを削除し、クローズ 82 curl_multi_remove_handle($multi_handle, $ch); 83 curl_close($ch); 84 } 85 86 // cURLマルチハンドラをクローズ 87 curl_multi_close($multi_handle); 88 89 return $results; 90} 91 92// --- コードの実行例 --- 93// 複数のURLを指定(成功例と失敗例を含む) 94$urls_to_fetch = [ 95 'https://www.example.com/', 96 'https://www.google.com/', 97 'https://www.php.net/', 98 'http://nonexistent-domain-12345.com/', // 存在しないドメインでエラーを発生させる 99]; 100 101// 関数を呼び出して、並行してURLをフェッチ 102$fetched_contents = fetch_multiple_urls_concurrently($urls_to_fetch); 103 104// 結果を表示 105foreach ($fetched_contents as $url_key => $content) { 106 echo "--- URL: " . $urls_to_fetch[$url_key] . " ---\n"; 107 if (str_starts_with((string)$content, 'Error:')) { 108 echo "Status: FAILED - " . $content . "\n"; 109 } else { 110 echo "Status: SUCCESS - Content length: " . strlen((string)$content) . " bytes\n"; 111 // 初心者向けなので、実際のコンテンツは一部のみ表示または省略 112 // echo "Content preview: " . substr((string)$content, 0, 100) . "...\n"; 113 } 114 echo "\n"; 115}
curl_multi_add_handleは、PHPのcURL拡張機能の一部で、複数のHTTPリクエストを並行して効率的に処理するために使用される関数です。この関数は、個々のcURLリクエストを管理するハンドラ(CurlHandle)を、複数のリクエストを一括で管理するマルチハンドラ(CurlMultiHandle)に追加する役割を持ちます。
第一引数$multi_handleには、curl_multi_init()で作成されたマルチハンドラを指定します。第二引数$handleには、curl_init()で作成され、リクエスト設定(例:URL、戻り値形式など)が施された個別のcURLハンドラを渡します。これにより、$handleで定義されたリクエストが$multi_handleによる並行処理の対象となります。
この関数の戻り値はint型で、操作の成功または失敗を示すCURLMコードを返します。通常、CURLM_OKが返されれば、ハンドラの追加が成功したことを意味します。
サンプルコードでは、複数のURLに対してそれぞれ個別のcURLハンドラを作成し、ループ内でcurl_multi_add_handleを使ってこれらを一つのマルチハンドラに次々と追加しています。全てのハンドラが追加された後、curl_multi_exec()とcurl_multi_select()(イベント駆動型でCPU負荷を抑えつつ処理を待機する関数)を組み合わせることで、リクエストの完了を効率的に待機し、複数のURLからのデータをほぼ同時に取得しています。このように、curl_multi_add_handleは複数のHTTPリクエストを同時に実行する並行処理の基盤を築く重要な関数です。
各cURLハンドラやマルチハンドラは、使用後に必ずcurl_closeやcurl_multi_closeで閉じる必要があります。これによりメモリリークを防ぎ、システムリソースを適切に解放します。特にエラーが発生した場合でも、確実にクローズ処理を実行するよう注意してください。また、curl_multi_add_handleの戻り値やcurl_error関数を使い、各処理段階でエラーがないか細かく確認する習慣をつけましょう。並行処理では、curl_multi_execとcurl_multi_selectを組み合わせることで、CPUを不必要に消費せず効率的に処理を待機させることが重要です。PHP 8からはCurlHandleなどの厳密な型が導入され、コードの堅牢性が向上しています。
PHP: curl_multi_add_handleで複数リクエストを追加する
1<?php 2 3/** 4 * 複数のCURLリクエストを非同期で実行するサンプル関数です。 5 * `curl_multi_init` でマルチCURLハンドルを初期化し、 6 * `curl_multi_add_handle` を使って複数のCURLリクエストを登録する方法を示します。 7 */ 8function executeParallelCurlRequests(): void 9{ 10 // 1. マルチCURLハンドルの初期化 11 // 複数のCURLリクエストを並行して管理するためのコンテナを作成します。 12 $multiHandle = curl_multi_init(); 13 if ($multiHandle === false) { 14 echo "エラー: マルチCURLハンドルを初期化できませんでした。\n"; 15 return; 16 } 17 18 // 2. 個別のCURLハンドルの準備 19 // 実行したい各リクエスト(例: 異なるAPIエンドポイント)に対して 20 // 個別のCURLハンドルを設定します。 21 $ch1 = curl_init(); 22 if ($ch1 === false) { 23 echo "エラー: CURLハンドル1を初期化できませんでした。\n"; 24 curl_multi_close($multiHandle); 25 return; 26 } 27 curl_setopt($ch1, CURLOPT_URL, 'https://jsonplaceholder.typicode.com/posts/1'); 28 curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); // 結果を文字列で返す設定 29 30 $ch2 = curl_init(); 31 if ($ch2 === false) { 32 echo "エラー: CURLハンドル2を初期化できませんでした。\n"; 33 curl_close($ch1); // 既に初期化されたハンドルを閉じる 34 curl_multi_close($multiHandle); 35 return; 36 } 37 curl_setopt($ch2, CURLOPT_URL, 'https://jsonplaceholder.typicode.com/todos/1'); 38 curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); // 結果を文字列で返す設定 39 40 // 3. 個別のCURLハンドルをマルチCURLハンドルに追加 41 // `curl_multi_add_handle` は個別のCURLハンドルをマルチCURLハンドルに登録します。 42 // 戻り値は整数で、`CURLM_OK` (0) は成功を意味します。 43 if (curl_multi_add_handle($multiHandle, $ch1) !== CURLM_OK) { 44 echo "エラー: CURLハンドル1の追加に失敗しました。\n"; 45 curl_close($ch1); 46 curl_close($ch2); 47 curl_multi_close($multiHandle); 48 return; 49 } 50 if (curl_multi_add_handle($multiHandle, $ch2) !== CURLM_OK) { 51 echo "エラー: CURLハンドル2の追加に失敗しました。\n"; 52 curl_multi_remove_handle($multiHandle, $ch1); // 既に登録されたハンドルを削除 53 curl_close($ch1); 54 curl_close($ch2); 55 curl_multi_close($multiHandle); 56 return; 57 } 58 59 // 4. リクエストの実行と完了待ち 60 // 全てのリクエストが完了するまでループします。 61 $running = null; // 現在実行中のハンドルの数を保持する変数 62 do { 63 // CURLリクエストを実行し、実行中のハンドル数を更新します。 64 curl_multi_exec($multiHandle, $running); 65 66 // 実行中のリクエストがある場合、イベント発生を待機します。 67 if ($running > 0) { 68 // イベントが発生するか、最大0.5秒待機します。 69 curl_multi_select($multiHandle, 0.5); 70 } 71 } while ($running > 0); // 実行中のハンドルがなくなるまでループを続けます 72 73 // 5. 結果の取得と表示 74 // 各CURLハンドルから取得したコンテンツを取り出します。 75 $response1 = curl_multi_getcontent($ch1); 76 $response2 = curl_multi_getcontent($ch2); 77 78 echo "--- 'posts/1' からのレスポンス ---\n"; 79 // レスポンスが長い場合があるので、一部のみ表示します。 80 echo $response1 ? substr($response1, 0, 100) . "...\n\n" : "レスポンスがありませんでした。\n\n"; 81 82 echo "--- 'todos/1' からのレスポンス ---\n"; 83 echo $response2 ? substr($response2, 0, 100) . "...\n\n" : "レスポンスがありませんでした。\n\n"; 84 85 // 6. ハンドルのクリーンアップ 86 // 登録された個別のCURLハンドルをマルチCURLハンドルから削除し、 87 // それぞれのハンドルを閉じます。最後にマルチCURLハンドル自体を閉じます。 88 curl_multi_remove_handle($multiHandle, $ch1); 89 curl_multi_remove_handle($multiHandle, $ch2); 90 curl_close($ch1); 91 curl_close($ch2); 92 curl_multi_close($multiHandle); 93} 94 95// 関数を実行します。 96executeParallelCurlRequests(); 97 98?>
PHPのcurl_multi_add_handle関数は、複数のCURLリクエストを並行して実行するために利用されます。この関数は、curl_multi_initで作成したマルチCURLハンドルに、個別のCURLリクエストを設定した通常のCURLハンドルを追加する役割を持ちます。
サンプルコードでは、まずcurl_multi_init関数でマルチCURLハンドル $multiHandle を初期化し、複数のリクエストをまとめて管理する準備をします。次に、curl_init関数を使って、異なるAPIエンドポイント(posts/1とtodos/1)に対する二つの個別のCURLハンドル $ch1 と $ch2 をそれぞれ準備しています。
そして、curl_multi_add_handle($multi_handle, $handle)関数が登場します。この関数は、一つ目の引数に複数のリクエストを管理するマルチCURLハンドルを、二つ目の引数に追加したい個別のCURLハンドルを指定します。戻り値は整数で、CURLM_OK (0) が返されればハンドルの追加が成功したことを意味します。これにより、二つの異なるリクエストがマルチCURLハンドルに登録され、並行して実行できる状態になります。
登録後は、curl_multi_execなどの関数を用いて全てのリクエストが完了するまで処理を進め、最終的に各リクエストの結果を取得し表示しています。この方法により、Webアプリケーションなどで複数の外部APIへ同時にアクセスするような処理を効率的に実装できます。最後に、使用した全てのハンドルを適切にクリーンアップしています。
このサンプルコードは、複数のCURLリクエストを並行実行する基本です。最も重要な注意点は、curl_multi_initやcurl_initで取得したハンドルを、処理の成否にかかわらずcurl_closeやcurl_multi_closeで必ず解放することです。解放を怠るとメモリリークに繋がります。curl_multi_add_handleの戻り値がCURLM_OK以外の場合はエラー処理として、リソースを適切にクリーンアップし処理を中断してください。PHP 8の型ヒントはコードの安全性と可読性を高めます。非同期処理はcurl_multi_execとcurl_multi_selectを組み合わせて管理します。