はじめに
Webアプリケーションを開発していると、URLのクエリパラメータやフォーム送信データの中に、%E3%81%82 や +(プラス記号)のようなエンコードされた文字列が登場することがあります。これはURLエンコード(正確には application/x-www-form-urlencoded 形式のエンコード)と呼ばれるもので、URLの中で安全に使えない文字(日本語や記号、スペースなど)を別の表現に変換したものです。
このエンコードされた文字列を元に戻す(デコードする)ために使うのが urldecode() 関数です。$_GET や $_POST を使う場合はPHPが自動的にデコードしてくれるため意識することは少ないですが、外部APIからのレスポンスや、手動でURLを組み立てて受け渡しする場面などでは、この関数を明示的に使う機会が出てきます。本記事では urldecode() の基本と、混同されがちな rawurldecode() との違い、実践的な活用例までを詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | urldecode() |
| 所属拡張 | コア関数(標準で常に利用可能) |
| シグネチャ | urldecode(string $string): string |
| 引数 | $string — デコード対象のURLエンコードされた文字列 |
| 戻り値 | デコードされた文字列 |
| 対応バージョン | PHP 4以降 |
| 対になる関数 | urlencode() |
| 類似関数 | rawurldecode()(+ をスペースに変換しない点が異なる) |
| OOP版の有無 | なし。手続き型関数のみ |
デコードの流れ(イメージ図)
エンコードされた文字列
"%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF+PHP"
│
▼
urldecode()
│
┌──────────┴──────────┐
│ 1. "%XX" を対応するバイトに変換 │
│ 2. "+" を半角スペースに変換 │
└──────────┬──────────┘
▼
"こんにちは PHP"
ポイントは、urldecode() が + を半角スペースとして解釈する という点です。これはRFC 1866で定義された application/x-www-form-urlencoded の仕様に基づく挙動であり、通常のパーセントエンコーディング(RFC 3986)とは異なります。この違いを理解していないと、URLパス中の + を誤ってスペースに変換してしまうバグを生むことがあります。
実践サンプル7選
例1:基本的なデコード
<?php
class BasicUrlDecoder
{
public function decode(string $encoded): string
{
// %XX形式と+をそれぞれ元の文字に変換する
return urldecode($encoded);
}
}
$decoder = new BasicUrlDecoder();
echo $decoder->decode('%E3%81%82%E3%81%84%E3%81%86') . PHP_EOL; // "あいう"
echo $decoder->decode('hello+world') . PHP_EOL; // "hello world"
例2:クエリ文字列を手動でパースするクラス
<?php
class QueryStringParser
{
/**
* "key1=value1&key2=value2" 形式の文字列を
* 連想配列にデコードして返す
*/
public function parse(string $queryString): array
{
$result = [];
$pairs = explode('&', $queryString);
foreach ($pairs as $pair) {
if ($pair === '') {
continue;
}
[$key, $value] = array_pad(explode('=', $pair, 2), 2, '');
// キーと値それぞれをurldecode()でデコード
$result[urldecode($key)] = urldecode($value);
}
return $result;
}
}
$parser = new QueryStringParser();
print_r($parser->parse('name=%E5%A4%AA%E9%83%8E&hobby=tennis+%26+golf'));
// 出力例: ['name' => '太郎', 'hobby' => 'tennis & golf']
例3:外部APIレスポンスに含まれるエンコード済みURLを復元する
<?php
class ApiResponseNormalizer
{
/**
* APIレスポンス中のエンコードされたリダイレクトURLを
* 人間が読める形に戻す
*/
public function normalizeRedirectUrl(string $rawUrl): string
{
// 例: "https://example.com/search?q=%E6%9D%B1%E4%BA%AC"
return urldecode($rawUrl);
}
}
$normalizer = new ApiResponseNormalizer();
echo $normalizer->normalizeRedirectUrl('https://example.com/search?q=%E6%9D%B1%E4%BA%AC') . PHP_EOL;
// "https://example.com/search?q=東京"
例4:urldecodeとrawurldecodeの挙動比較
<?php
class DecodeComparator
{
/**
* urldecode()とrawurldecode()の違いを
* "+"の扱いに注目して比較する
*/
public function compare(string $encoded): array
{
return [
'urldecode' => urldecode($encoded),
'rawurldecode' => rawurldecode($encoded),
];
}
}
$comparator = new DecodeComparator();
print_r($comparator->compare('a+b%2Bc'));
// urldecode: "a b+c" ← "+" はスペースに、"%2B" は "+" に変換される
// rawurldecode: "a+b+c" ← "+" はそのまま、"%2B" のみ "+" に変換される
例5:Cookie値に含まれるエンコードデータの復元
<?php
class CookiePayloadReader
{
/**
* URLエンコードされた状態で保存されているCookie値を
* デコードしてJSONとしてパースする
*/
public function readJsonPayload(string $rawCookieValue): ?array
{
$decoded = urldecode($rawCookieValue);
$data = json_decode($decoded, true);
return json_last_error() === JSON_ERROR_NONE ? $data : null;
}
}
$reader = new CookiePayloadReader();
$encoded = urlencode(json_encode(['theme' => 'dark', 'lang' => 'ja']));
print_r($reader->readJsonPayload($encoded));
// 出力例: ['theme' => 'dark', 'lang' => 'ja']
例6:二重エンコードされた文字列を検出して安全にデコードする
<?php
class DoubleEncodeSafeDecoder
{
/**
* 二重にurlencodeされている可能性がある文字列を
* 安全に1回だけデコードする(多重デコードによる事故を防ぐ)
*/
public function decodeOnce(string $value): string
{
$decoded = urldecode($value);
// 再エンコードした結果が元の値と一致する場合のみ、
// 単純な1回のエンコードだったと判断する
if (urlencode($decoded) === $value) {
return $decoded;
}
// 一致しない場合は元の複雑な構造を保持するため、そのまま返す
return $value;
}
}
$decoder = new DoubleEncodeSafeDecoder();
echo $decoder->decodeOnce('%25E3%2581%2582') . PHP_EOL; // 二重エンコードの例
echo $decoder->decodeOnce('%E3%81%82') . PHP_EOL; // 通常のエンコードの例
例7:ログファイルからURLエンコードされたパラメータを抽出・復元する
<?php
class AccessLogUrlExtractor
{
/**
* アクセスログの1行から "GET /path?key=value" 形式を抜き出し、
* クエリパラメータをデコードして返す
*/
public function extractDecodedQuery(string $logLine): array
{
if (!preg_match('/\?([^\s"]+)/', $logLine, $matches)) {
return [];
}
$result = [];
foreach (explode('&', $matches[1]) as $pair) {
[$key, $value] = array_pad(explode('=', $pair, 2), 2, '');
$result[urldecode($key)] = urldecode($value);
}
return $result;
}
}
$extractor = new AccessLogUrlExtractor();
$log = '192.168.1.1 - - [30/Jul/2026] "GET /search?q=%E6%97%A5%E6%9C%AC%E8%AA%9E&page=2 HTTP/1.1" 200';
print_r($extractor->extractDecodedQuery($log));
// 出力例: ['q' => '日本語', 'page' => '2']
関連関数との比較
| 関数 | 役割 | urldecodeとの違い |
|---|---|---|
urldecode() | URLエンコードされた文字列をデコード | 本記事の対象。+ を半角スペースに変換する |
urlencode() | 文字列をURLエンコードする | urldecode() の対になるエンコード関数(スペースを+にする) |
rawurldecode() | RFC 3986準拠のデコード | + をスペースに変換せず、そのまま+として扱う |
rawurlencode() | RFC 3986準拠のエンコード | スペースを%20にエンコードする(+にはしない) |
http_build_query() | 配列からクエリ文字列を生成 | エンコード処理を内包しており、デコードとは逆方向の処理 |
よくある落とし穴(注意点)
+の扱いを誤解するurldecode()は+を半角スペースに変換します。URLのパス部分に含まれる本来の+記号(数式や電話番号の国番号表記など)をデコードする際は、意図せずスペースに変わってしまう可能性があるため、rawurldecode()を使うべきかどうか検討が必要です。- $_GETや$_POSTは既にデコード済み PHPは
$_GETや$_POSTに格納する時点で自動的にURLデコードを行っています。これらの値に対して重ねてurldecode()を呼び出すと、意図しない二重デコードが発生することがあります。 - 不正な
%エンコーディングへの耐性%の後ろに正しい16進数が続かない不正な文字列を渡した場合、PHPのバージョンによって挙動が異なることがあるため、外部から受け取った信頼できない文字列をデコードする際はバリデーションを併用することが望ましいです。 - マルチバイト文字のエンコーディングに注意 デコード後の文字列がUTF-8であることを前提とした処理をする場合、実際のエンコーディングが異なると文字化けの原因になります。必要に応じて
mb_check_encoding()などで確認しましょう。 - セキュリティ上の考慮 デコードした文字列をそのままHTMLに出力すると、XSS(クロスサイトスクリプティング)の原因になります。
urldecode()の結果を画面に表示する際は、必ずhtmlspecialchars()などでエスケープしてください。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | URLエンコードされた文字列(%XX形式や+)を元の文字列にデコードする |
| 主な用途 | クエリ文字列の手動パース、外部APIレスポンスの復元、Cookie値の読み取りなど |
| 特徴的な挙動 | + を半角スペースに変換する(rawurldecode()との最大の違い) |
| 対になる関数 | urlencode()(エンコード)、rawurldecode()(RFC 3986準拠のデコード) |
| 注意点 | 二重デコード、+の誤変換、デコード後のXSS対策を忘れないこと |
urldecode() は一見単純な関数に見えますが、+ の扱いや二重デコードのリスクなど、実務で注意すべきポイントがいくつも存在します。用途に応じて rawurldecode() との使い分けを意識しながら、安全にURLデータを扱っていきましょう。
