2分で読了エンジニアリング

curl、Guzzle、WordPressを使ってPHPでURLを短縮する方法

短いcurlブロック、GuzzleまたはLaravelでの同じ呼び出し、wp_remote_postを使ったWordPress版に加え、リトライと一括短縮を紹介します。

Ana Kowalska
Marketing solutions engineering
PHPでURLを短縮する方法:宛先URLを短縮サービスのAPIへ送信するcurl POSTと、JSONレスポンスから短縮リンクをデコードする処理、その横にGuzzleとWordPressの方法を示した図

PHPでURLを短縮する処理は、1回のHTTP POSTで完了します。curl拡張機能を使って長いリンクを短縮サービスのAPIへ送り、APIキーをBearerトークンとして渡し、レスポンスからjson_decodeで短縮リンクを取り出します。Composerパッケージは不要ですが、GuzzleやLaravel HTTPクライアントを使えば同じ呼び出しを3行に短縮できます。

このテーマに関するPHPチュートリアルの多くは、博物館に置くような古いものです。Googleが2019年に新しいリンクの作成を終了した後に廃止したurlshortener/v1を接続し、非アクティブなgoo.glリンクは2025年8月25日に停止しました。または、何年も前になくなったbit.ly v3エンドポイントを使っています。以下のコードは現在利用できるAPIを対象にしています。

これはURLを短縮する一般的な手順のPHP版です。サービスをまだ選んでいない場合は、無料URL短縮サービスAPIの概要で、ここで想定しているリクエスト形式、認証モデル、制限を説明しています。フィールド名はElidoのものですが、形式は移植できます。

最速の方法:curlで1回POSTする

キーを環境変数に入れ、本文を自分でエンコードし、ペイロードを信頼する前にステータスコードを読み取ります。

<?php

$ch = curl_init('https://api.elido.app/v1/links');

curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('ELIDO_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'destination_url' => 'https://example.com/spring-sale?utm_source=newsletter',
    ]),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($body === false || $status >= 400) {
    throw new RuntimeException("shorten failed: HTTP {$status}");
}

$link = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $link['short_url']; // https://s.elido.me/ab12cd

この中の2行は、誰もが一度は遭遇するバグのために存在します。CURLOPT_RETURNTRANSFERのデフォルトはfalseなので、これを指定しないとcurlはJSONをそのまま出力し、実際の原因であるtrueを返します。これは「レスポンスが空」という質問の多くの真相です。また、CURLOPT_POSTFIELDSに配列を渡すとマルチパートフォームエンコーディングに切り替わるため、JSONリクエストを実際に作っているのはjson_encodeです。

CURLINFO_RESPONSE_CODEも同じくらい重要です。curlは、何の異常も報告せずに{"error": "unauthorized"}でいっぱいの本文を返せます。

PHPのcurl POSTが宛先URLとBearerトークンを短縮サービスのlinksエンドポイントへ送り、APIがshort_urlを含むHTTP 201を返してjson_decodeが読み取り、リトライループが429と5xxのレスポンスに対して待機する流れ

GuzzleまたはLaravelで同じ呼び出しを行う

プロジェクトですでにGuzzleを読み込んでいる場合、定型コードは短くなります。

use GuzzleHttp\Client;

$client = new Client([
    'base_uri' => 'https://api.elido.app',
    'timeout'  => 10,
    'headers'  => ['Authorization' => 'Bearer ' . getenv('ELIDO_API_KEY')],
]);

$response = $client->post('/v1/links', [
    'json'    => ['destination_url' => $destination],
    'headers' => ['Idempotency-Key' => hash('sha256', $destination)],
]);

$short = json_decode((string) $response->getBody(), true)['short_url'];

Guzzleはデフォルトで4xxではClientException、5xxではServerExceptionをスローします。これは、黙っているcurlとも、JavaScriptのfetchとも逆の動作です。これらをキャッチするか、http_errorsをfalseに設定して自分でステータスを確認してください。

Laravelでは、HTTPクライアントがGuzzleをラップし、リトライを無料で提供します。

$short = Http::withToken(config('services.elido.key'))
    ->timeout(10)
    ->retry(3, 200, throw: false)
    ->post('https://api.elido.app/v1/links', ['destination_url' => $destination])
    ->json('short_url');

このretry(3, 200)は、200ミリ秒の間隔を置いて3回試行します。一時的な障害には十分ですが、レート制限には不十分です。次のセクションで説明します。

WordPress内でリンクを短縮する

WordPressはcurlを直接使いません。代わりに、トランスポートを選んでくれるwp_remote_postがあり、curlが無効なホストでも動作します。公開フックに接続し、テンプレートやフィードから読み取れるように結果を投稿メタとして保存します。

add_action('publish_post', function (int $post_id): void {
    if (get_post_meta($post_id, '_elido_short_url', true)) {
        return; // already shortened
    }

    $response = wp_remote_post('https://api.elido.app/v1/links', [
        'timeout' => 10,
        'headers' => [
            'Authorization'   => 'Bearer ' . ELIDO_API_KEY,
            'Content-Type'    => 'application/json',
            'Idempotency-Key' => 'post-' . $post_id,
        ],
        'body' => wp_json_encode(['destination_url' => get_permalink($post_id)]),
    ]);

    if (is_wp_error($response)) {
        error_log('shorten failed: ' . $response->get_error_message());
        return;
    }

    $link = json_decode(wp_remote_retrieve_body($response), true);
    update_post_meta($post_id, '_elido_short_url', $link['short_url']);
}, 10, 1);

ELIDO_API_KEYwp-config.phpに置きます。公開リポジトリに入ってしまうプラグインファイルや、すべての管理者が読めるオプション行には置かないでください。冪等性キーにも注目してください。post-123は安定しているため、再公開時にフックが2回実行されても、2回目の呼び出しは別のリンクを新規作成せず、すでに存在するリンクを返します。

フックを自分で保守したくない場合は、WordPress短縮サービスガイドで、プラグインを使う方法やノーコードの方法と比較しています。

これらのスニペットをそのまま実行したいですか?無料プランでキーを作成し、ELIDO_API_KEYとしてエクスポートすれば、上のcurlブロックは変更なしで動作します。

リトライ、レート制限、冪等性

無人で動くPHP処理、つまりcronジョブ、キューワーカー、一括インポーターは、429や一時的な5xxに耐える必要があります。また、POSTがAPIへ到達したものの、戻りの途中でレスポンスが失われる厄介なケースにも耐えなければなりません。コードはタイムアウトを検知してリトライしますが、冪等性キーがそれを止めなければ、同じ宛先に2つ目の短縮リンクを作成します。

function shorten(string $destination, int $retries = 3): string
{
    $key = hash('sha256', $destination); // stable across retries and re-runs

    for ($attempt = 0; $attempt < $retries; $attempt++) {
        [$body, $status, $retryAfter] = post_link($destination, $key);

        if ($status === 429) {
            sleep(max(1, (int) $retryAfter));
            continue;
        }
        if ($status >= 500) {
            sleep(2 ** $attempt); // exponential backoff
            continue;
        }
        if ($status >= 400) {
            throw new RuntimeException("shorten failed: HTTP {$status} {$body}");
        }

        return json_decode($body, true, 512, JSON_THROW_ON_ERROR)['short_url'];
    }

    throw new RuntimeException("shorten failed after {$retries} attempts");
}

401や403は自然に修復されることがないため、3回待つのではなく最初の試行で例外をスローします。429ではRetry-Afterヘッダーが指定する時間だけ待ちます。倍増するバックオフを使うのは5xxだけです。レート制限と冪等性の詳しい解説では、同じバッチを再実行する可能性がある場合、ランダムなUUIDより宛先のハッシュをキーにする方が通常よい理由を含め、詳しく説明しています。

PHP特有の落とし穴が1つあります。Webリクエスト内のsleep()は、php-fpmワーカーをその間ずっと保持します。リトライはCLIスクリプト、キュージョブ、またはWP-Cronで行い、ページをレンダリングするリクエスト内では行わないでください。

直列待ちをせずに一括短縮する

500個のURLを短縮するforeachループは、500回の往復を連続して行います。1回120ミリ秒なら、純粋な待ち時間だけで1分です。PHPには非同期ランタイムはありませんが、同時HTTP処理はあります。GuzzleのPoolは、一定数のリクエストを処理中に保ちます。

use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

$requests = static function (array $urls) {
    foreach ($urls as $url) {
        yield new Request('POST', '/v1/links', [
            'Content-Type'    => 'application/json',
            'Idempotency-Key' => hash('sha256', $url),
        ], json_encode(['destination_url' => $url]));
    }
};

$short = [];
$pool  = new Pool($client, $requests($urls), [
    'concurrency' => 8,
    'fulfilled'   => function ($response, $i) use (&$short, $urls) {
        $short[$urls[$i]] = json_decode((string) $response->getBody(), true)['short_url'];
    },
    'rejected'    => function ($reason, $i) use (&$short, $urls) {
        $short[$urls[$i]] = 'ERROR: ' . $reason->getMessage();
    },
]);

$pool->promise()->wait();

元のURLをキーにして結果を保存すると、一部の失敗が見える状態になり、再実行できます。Composerなしでは、curl_multi_initがより多くの管理処理を必要とするものの同じ役割を果たします。ハンドルを追加し、curl_multi_execのループを回し、それぞれのレスポンスを収集します。同時実行数は8前後から始め、レスポンスヘッダーを見て減らすべきか判断してください。

同じ短縮サービスのエンドポイントへ至る3つのPHPの方法:共有ホスティングでは素のcurl、アプリケーションではGuzzleまたはLaravel、WordPress内ではwp_remote_postを使い、いずれも環境変数からAPIキーを読み取る

どの方法を選ぶべきか

ホストに合わせてツールを選びます。Composerがない共有ホスティングなら、素のcurlブロックです。SymfonyまたはLaravelアプリなら、クライアントレベルで一度リトライを設定したGuzzleまたはHTTPクライアントです。WordPressなら、公開フック上のwp_remote_postです。何千ものリンクを夜間にインポートするなら、安定した冪等性キーを使うプールにすれば、再実行しても追加コストはかかりません。

どの方法を選んでも、4つの点は変わりません。キーは環境変数から取得し、curlには明示的なタイムアウトを設定し、本文より前にステータスコードを読み取り、2回実行される可能性がある処理には冪等性キーを付けます。最初のスクリプトを超えて運用するチームは、通常APIとSDKプラットフォームが開発者に公開しているその他の機能を求めます。クリック、タグ、有効期限、ポーリングの代わりとなるリンクイベント用webhookなどです。

基礎シリーズを読む

この記事はエンジニアリングクラスターに属します。まず無料URL短縮サービスAPIガイドでエンドポイントの形式と認証を確認し、次にレート制限と冪等性の記事で負荷の下でも適切に動作させる方法を学んでください。正式なリファレンスはAPIドキュメントです。goo.glリンクを引き継いだ場合は、Google URL Shortenerの代替サービスで現在の移行先を説明しています。

ブログの関連記事

よくある質問

PHPでURLを短縮するにはどうすればよいですか?

長いURLを短縮サービスのAPIへPOSTし、APIキーをBearerヘッダーで、宛先をJSON本文で送信してから、レスポンスをjson_decodeし、short_urlフィールドを読み取ります。curl_setopt_arrayを使えばおよそ15行、プロジェクトにGuzzleまたはLaravel HTTPクライアントがすでにある場合は3行ほどです。

外部ライブラリを使わずにPHPでURLを短縮するにはどうすればよいですか?

PHPに付属するcurl拡張機能を使います。curl_init、curl_setopt_array、curl_exec、json_decodeだけでComposer依存なしに一連の処理を完了できます。Composerを実行できない共有ホスティングでは重要です。CURLOPT_RETURNTRANSFERをtrueに設定しないと、curl_execはレスポンスを返さずに出力してしまいます。

Google URL Shortener APIは今でもPHPで動作しますか?

いいえ。Googleは2019年にURL Shortener APIでの新しいリンクの受け付けを停止してサービスを終了し、非アクティブなgoo.glリンクも2025年8月25日に解決を停止しました。urlshortener/v1を使うPHPチュートリアルはすべて古いコードであり、同時期のbit.ly v3の例もなくなっています。

PHPを使ってWordPress内でURLを短縮できますか?

はい。公開時に実行されるフックからwp_remote_postを呼び出し、返された短縮リンクをupdate_post_metaで保存すれば、テーマの他の部分から読み取れます。APIキーはプラグインファイルやデータベースではなくwp-config.phpに置き、レスポンスについてis_wp_errorを確認してください。WordPressは例外をスローせず、WP_Errorオブジェクトを返すためです。

PHPで多数のURLを一度に短縮するにはどうすればよいですか?

各レスポンスを待つforeachループではなく、上限を設けたプールでリクエストを同時に送信します。GuzzleのPoolで同時実行数を約8にするのが最短の方法で、curl_multiならComposerなしでも同じことができます。各URLから生成したIdempotency-Keyにより、再実行で重複リンクが作られるのを防げます。

PHPのcurlから短縮サービスへ送ったリクエストが401または空のレスポンスになるのはなぜですか?

401はAuthorizationヘッダーがないか形式不正であることを意味します。CURLOPT_HTTPHEADER配列内の単一の文字列として、正確に'Authorization: Bearer YOUR_KEY'と記述する必要があります。空の戻り値は通常、CURLOPT_RETURNTRANSFERを設定していないことを意味します。その場合、本文は直接出力され、curl_execは代わりにtrueを返します。

Elidoを試す

URLを貼り付けて短縮リンクを取得

登録不要。リンクは30日間有効。永久に保存するには登録してください。

Free、登録不要 · 1日あたり2件

Elidoを試す

EUホスティングのURL短縮サービス。カスタムドメイン、詳細な分析、オープンAPI付き。無料プラン - クレジットカード不要。

タグ
how to shorten a url in php
php url shortener
shorten url php curl
url shortener api php
wordpress shorten url php
bulk shorten urls php

続きを読む