PHP http_build_query()函数

PHP http_build_query() 函数

http_build_query() 函数用于生成 URL 编码的查询字符串。它将数组或对象转换为符合 URL 查询字符串格式的字符串,非常适合用于构建 GET 请求参数或 POST 数据。

提示: 这个函数是 PHP 中处理 URL 查询参数的核心函数之一,广泛应用于构建 API 请求、表单提交、重定向 URL 等场景。
特点: 自动处理数组和嵌套数据结构,支持多种编码类型,可自定义参数分隔符。

语法

http_build_query (
    mixed $data ,
    string $numeric_prefix = "" ,
    string $arg_separator = null ,
    int $encoding_type = PHP_QUERY_RFC1738
) : string

参数说明:

参数 说明 必需 默认值
$data 要处理的数据,可以是数组或对象
$numeric_prefix 当数组的键名为数字时,使用此前缀作为键名前缀 ""(空字符串)
$arg_separator 参数分隔符,通常为 "&" ini_get("arg_separator.output")
$encoding_type 编码类型,可选 PHP_QUERY_RFC1738 或 PHP_QUERY_RFC3986 PHP_QUERY_RFC1738

返回值

函数返回 URL 编码后的查询字符串。

返回值示例:
name=John+Doe&age=30&city=New+York

编码类型说明:

  • PHP_QUERY_RFC1738 - 使用 RFC 1738 标准编码(空格编码为 +)
  • PHP_QUERY_RFC3986 - 使用 RFC 3986 标准编码(空格编码为 %20)

示例 1:基本用法 - 生成简单查询字符串

下面的示例演示如何使用 http_build_query() 函数将关联数组转换为查询字符串。

<?php
// 基础关联数组
$data = [
    'name' => 'John Doe',
    'age' => 30,
    'city' => 'New York',
    'country' => 'USA'
];

// 生成查询字符串
$queryString = http_build_query($data);

echo "<h4>生成的查询字符串:</h4>";
echo "<div class='alert alert-info'>" . htmlspecialchars($queryString) . "</div>";

// 构建完整的URL
$url = 'https://api.example.com/user?' . $queryString;
echo "<h4>完整URL:</h4>";
echo "<div class='alert alert-success'><a href='" . htmlspecialchars($url) . "' target='_blank'>" . htmlspecialchars($url) . "</a></div>";

// 解码查看(用于验证)
echo "<h4>解码后的参数:</h4>";
parse_str($queryString, $decoded);
echo "<pre>" . print_r($decoded, true) . "</pre>";
?>

示例 2:处理数字键名和嵌套数组

下面的示例演示如何处理数字键名和嵌套数组结构。

<?php
// 包含数字键名和嵌套数组的数据
$data = [
    'user' => 'john_doe',
    'items' => ['apple', 'banana', 'orange'], // 数字索引数组
    'filters' => [
        'category' => 'electronics',
        'price' => ['min' => 100, 'max' => 500], // 嵌套关联数组
        'brands' => ['Samsung', 'Apple', 'Sony'] // 嵌套索引数组
    ],
    0 => 'first_item', // 纯数字键名
    1 => 'second_item'
];

echo "<h4>原始数据:</h4>";
echo "<pre>" . print_r($data, true) . "</pre>";

echo "<hr><h4>1. 默认处理(数字键名直接使用):</h4>";
$query1 = http_build_query($data);
echo "<div class='alert alert-light'>" . htmlspecialchars($query1) . "</div>";

echo "<h4>2. 使用数字前缀:</h4>";
$query2 = http_build_query($data, 'param_');
echo "<div class='alert alert-light'>" . htmlspecialchars($query2) . "</div>";

echo "<h4>3. 自定义分隔符:</h4>";
$query3 = http_build_query($data, '', '&amp;');
echo "<div class='alert alert-light'>" . htmlspecialchars($query3) . "</div>";

echo "<h4>4. RFC3986编码:</h4>";
$query4 = http_build_query($data, '', '&', PHP_QUERY_RFC3986);
echo "<div class='alert alert-light'>" . htmlspecialchars($query4) . "</div>";

// 解码其中一个示例查看结构
echo "<h4>解码后的结构(示例1):</h4>";
parse_str($query1, $decoded);
echo "<pre>" . print_r($decoded, true) . "</pre>";
?>

示例 3:构建复杂 API 请求参数

下面的示例演示如何使用 http_build_query() 构建复杂的 API 请求参数。

<?php
/**
 * 构建API请求URL
 */
function buildApiUrl($baseUrl, $endpoint, $params = [], $apiKey = null) {
    // 添加API密钥(如果提供)
    if ($apiKey !== null) {
        $params['api_key'] = $apiKey;
    }

    // 生成查询字符串
    $queryString = http_build_query($params);

    // 构建完整URL
    $url = rtrim($baseUrl, '/') . '/' . ltrim($endpoint, '/');

    if (!empty($queryString)) {
        $url .= '?' . $queryString;
    }

    return $url;
}

/**
 * 构建分页参数
 */
function buildPaginationParams($page = 1, $limit = 20, $sortBy = 'id', $sortOrder = 'asc') {
    return [
        'page' => $page,
        'limit' => $limit,
        'sort_by' => $sortBy,
        'sort_order' => $sortOrder
    ];
}

/**
 * 构建搜索过滤器
 */
function buildSearchFilters($filters = []) {
    $result = [];

    foreach ($filters as $key => $value) {
        if (is_array($value)) {
            // 处理数组值(如多个选项)
            foreach ($value as $index => $item) {
                $result["filters[{$key}][]"] = $item;
            }
        } else {
            $result["filters[{$key}]"] = $value;
        }
    }

    return $result;
}

// 示例:构建用户搜索API请求
$baseUrl = 'https://api.example.com';
$endpoint = '/v1/users/search';

// 分页参数
$pagination = buildPaginationParams(2, 30, 'name', 'desc');

// 搜索过滤器
$filters = buildSearchFilters([
    'status' => 'active',
    'role' => ['admin', 'editor'], // 多选
    'created_after' => '2023-01-01',
    'age_range' => ['min' => 18, 'max' => 60]
]);

// 合并所有参数
$allParams = array_merge($pagination, $filters);

// 构建URL
$apiKey = 'your_api_key_here';
$apiUrl = buildApiUrl($baseUrl, $endpoint, $allParams, $apiKey);

echo "<h4>生成的API URL:</h4>";
echo "<div class='alert alert-info'><a href='" . htmlspecialchars($apiUrl) . "' target='_blank'>" . htmlspecialchars($apiUrl) . "</a></div>";

echo "<h4>参数详情:</h4>";
echo "<table class='table table-bordered'>";
echo "<thead><tr><th>参数</th><th>值</th><th>说明</th></tr></thead>";
echo "<tbody>";

$paramDetails = [
    'page=2' => '第2页',
    'limit=30' => '每页30条记录',
    'sort_by=name' => '按名称排序',
    'sort_order=desc' => '降序排列',
    'filters[status]=active' => '状态为活跃',
    'filters[role][]=admin' => '角色包含管理员',
    'filters[role][]=editor' => '角色包含编辑',
    'filters[created_after]=2023-01-01' => '创建时间在2023年1月1日之后',
    'filters[age_range][min]=18' => '最小年龄18岁',
    'filters[age_range][max]=60' => '最大年龄60岁',
    'api_key=your_api_key_here' => 'API密钥'
];

foreach ($paramDetails as $param => $description) {
    echo "<tr><td><code>" . htmlspecialchars($param) . "</code></td><td>" . htmlspecialchars(explode('=', $param)[1] ?? '') . "</td><td>" . htmlspecialchars($description) . "</td></tr>";
}

echo "</tbody></table>";
?>

示例 4:处理对象和特殊字符

下面的示例演示如何处理对象以及包含特殊字符的数据。

<?php
// 创建一个示例对象
class UserProfile {
    public $username = 'john_doe';
    public $email = 'john@example.com';
    public $preferences = ['theme' => 'dark', 'language' => 'en'];
    private $password = 'secret'; // 私有属性不会被包含

    public function getData() {
        return [
            'username' => $this->username,
            'email' => $this->email
        ];
    }
}

// 包含特殊字符的数据
$specialData = [
    'search' => 'price > 100 & rating < 5', // 包含HTML特殊字符
    'message' => 'Hello "World" & Friends', // 包含引号和特殊字符
    'path' => '/home/user/files/document.pdf', // 包含斜杠
    'unicode' => '中文 Español Français', // 包含Unicode字符
    'spaces' => 'multiple    spaces   here', // 多个空格
    'special_chars' => '!@#$%^&*()_+-=[]{}|;:,./<>?' // 各种特殊字符
];

echo "<h4>1. 处理对象:</h4>";
$user = new UserProfile();
$objectQuery = http_build_query($user);
echo "<div class='alert alert-light'>" . htmlspecialchars($objectQuery) . "</div>";
echo "<p><small>注意:只有公共属性会被包含在查询字符串中。</small></p>";

echo "<h4>2. 处理特殊字符(RFC1738编码):</h4>";
$query1 = http_build_query($specialData);
echo "<div class='alert alert-light' style='word-break: break-all;'>" . htmlspecialchars($query1) . "</div>";

echo "<h4>3. 处理特殊字符(RFC3986编码):</h4>";
$query2 = http_build_query($specialData, '', '&', PHP_QUERY_RFC3986);
echo "<div class='alert alert-light' style='word-break: break-all;'>" . htmlspecialchars($query2) . "</div>";

// 比较两种编码的区别
echo "<h4>4. 编码差异对比:</h4>";
echo "<table class='table table-bordered table-sm'>";
echo "<thead><tr><th>原始字符</th><th>RFC1738编码</th><th>RFC3986编码</th><th>说明</th></tr></thead>";
echo "<tbody>";

$comparisons = [
    ['字符' => ' ', 'RFC1738' => '+', 'RFC3986' => '%20', '说明' => '空格'],
    ['字符' => '&', 'RFC1738' => '%26', 'RFC3986' => '%26', '说明' => '与符号'],
    ['字符' => '=', 'RFC1738' => '%3D', 'RFC3986' => '%3D', '说明' => '等号'],
    ['字符' => '?', 'RFC1738' => '%3F', 'RFC3986' => '%3F', '说明' => '问号'],
    ['字符' => '#', 'RFC1738' => '%23', 'RFC3986' => '%23', '说明' => '井号'],
    ['字符' => '/', 'RFC1738' => '%2F', 'RFC3986' => '%2F', '说明' => '斜杠'],
    ['字符' => '中', 'RFC1738' => '%E4%B8%AD', 'RFC3986' => '%E4%B8%AD', '说明' => '中文字符']
];

foreach ($comparisons as $row) {
    echo "<tr>";
    echo "<td><code>" . htmlspecialchars($row['字符']) . "</code></td>";
    echo "<td><code>" . htmlspecialchars($row['RFC1738']) . "</code></td>";
    echo "<td><code>" . htmlspecialchars($row['RFC3986']) . "</code></td>";
    echo "<td>" . htmlspecialchars($row['说明']) . "</td>";
    echo "</tr>";
}

echo "</tbody></table>";
?>

示例 5:实际应用场景 - 表单处理和重定向

下面的示例演示在实际应用中使用 http_build_query() 的场景。

<?php
// 场景1:处理表单数据并重定向
function processFormAndRedirect($formData) {
    // 过滤和验证数据
    $filteredData = array_filter($formData, function($value) {
        return !empty($value) || $value === 0 || $value === '0';
    });

    // 添加时间戳和哈希(防止重复提交)
    $filteredData['timestamp'] = time();
    $filteredData['hash'] = md5(serialize($filteredData) . 'secret_salt');

    // 生成查询字符串
    $queryString = http_build_query($filteredData);

    // 重定向到结果页面
    $redirectUrl = '/result.php?' . $queryString;

    return $redirectUrl;
}

// 模拟表单数据
$formData = [
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'phone' => '13800138000',
    'age' => 25,
    'interests' => ['编程', '阅读', '运动'],
    'newsletter' => true,
    'agree_terms' => 1,
    'empty_field' => '', // 空字段将被过滤
    'zero_value' => 0 // 0值将被保留
];

echo "<h4>场景1:表单处理与重定向</h4>";
$redirectUrl = processFormAndRedirect($formData);
echo "<div class='alert alert-info'>重定向URL: " . htmlspecialchars($redirectUrl) . "</div>";

// 场景2:构建分页链接
function buildPaginationLinks($baseUrl, $totalItems, $currentPage = 1, $itemsPerPage = 10, $maxLinks = 5) {
    $totalPages = ceil($totalItems / $itemsPerPage);
    $links = [];

    // 计算显示的页码范围
    $start = max(1, $currentPage - floor($maxLinks / 2));
    $end = min($totalPages, $start + $maxLinks - 1);

    // 调整起始页码
    if ($end - $start + 1 < $maxLinks) {
        $start = max(1, $end - $maxLinks + 1);
    }

    // 构建页码链接
    for ($page = $start; $page <= $end; $page++) {
        $params = [
            'page' => $page,
            'limit' => $itemsPerPage,
            'sort' => 'date',
            'order' => 'desc'
        ];

        $queryString = http_build_query($params);
        $links[$page] = $baseUrl . '?' . $queryString;
    }

    return [
        'total_pages' => $totalPages,
        'current_page' => $currentPage,
        'links' => $links
    ];
}

echo "<h4>场景2:分页链接生成</h4>";
$pagination = buildPaginationLinks('/articles.php', 156, 3, 10, 5);

echo "<p>总页数: {$pagination['total_pages']}</p>";
echo "<p>当前页: {$pagination['current_page']}</p>";
echo "<div class='btn-group' role='group'>";
foreach ($pagination['links'] as $page => $url) {
    $activeClass = ($page == $pagination['current_page']) ? 'btn-primary' : 'btn-outline-primary';
    echo "<a href='" . htmlspecialchars($url) . "' class='btn $activeClass'>$page</a>";
}
echo "</div>";

// 场景3:API请求签名
function signApiRequest($baseUrl, $params, $secretKey) {
    // 按参数名排序(签名常用要求)
    ksort($params);

    // 生成查询字符串(不带签名)
    $queryString = http_build_query($params);

    // 计算签名
    $signature = hash_hmac('sha256', $queryString, $secretKey);

    // 添加签名到参数
    $params['signature'] = $signature;

    // 重新生成查询字符串(带签名)
    $signedQueryString = http_build_query($params);

    return $baseUrl . '?' . $signedQueryString;
}

echo "<h4>场景3:API请求签名</h4>";
$apiParams = [
    'action' => 'get_user',
    'user_id' => 12345,
    'timestamp' => time(),
    'nonce' => uniqid()
];

$secretKey = 'your_secret_key';
$signedUrl = signApiRequest('https://api.example.com/v1/user', $apiParams, $secretKey);

echo "<div class='alert alert-success' style='word-break: break-all;'>";
echo "签名后的URL: <br>" . htmlspecialchars($signedUrl);
echo "</div>";
?>

注意事项和常见问题

  • 嵌套数组处理http_build_query() 会自动处理嵌套数组,生成如 filters[category]=electronics&filters[price][min]=100 的格式。
  • 数字键名:默认情况下,数字键名会直接使用。如果希望添加前缀,可以使用 $numeric_prefix 参数。
  • 特殊字符编码:注意 PHP_QUERY_RFC1738PHP_QUERY_RFC3986 对空格等字符的编码方式不同。
  • 对象属性:当传递对象时,只有公共属性会被包含在查询字符串中。
  • 布尔值处理:布尔值 true 会转换为 "1",false 会转换为 "0"。
  • NULL值处理:NULL 值会被转换为空字符串。
  • 资源类型:资源类型(如数据库连接)无法被转换为查询字符串,会导致错误。
  • 大数组处理:非常大的数组可能会生成很长的查询字符串,可能超过服务器的 URL 长度限制。
  • 与parse_str()配合http_build_query()parse_str() 是互逆操作,可以相互转换。

相关函数对比

函数 用途 输入 输出 说明
http_build_query() 生成URL编码的查询字符串 数组或对象 字符串 自动处理嵌套结构,支持多种编码
parse_str() 解析查询字符串到变量 字符串 数组 http_build_query()的逆操作
urlencode() 编码URL字符串 字符串 字符串 编码单个字符串,不处理数组结构
rawurlencode() 按照RFC 3986编码URL 字符串 字符串 空格编码为%20,适合路径部分
urldecode() 解码URL编码的字符串 字符串 字符串 解码urlencode()编码的字符串

相关函数