Практические приёмы работы с URL в сценариях PHP
Основные подходы к работе с URL в PHP
Наиболее универсальное решение: комбинация parse_url() и http_build_query()
Для модификации параметров запроса в URL без ручной работы со строками применяется совместное использование функций parse_url() и http_build_query(). Сначала URL разбирается на компоненты, затем через parse_str() извлекаются текущие параметры, после чего массив параметров изменяется и с помощью http_build_query() собирается новая строка запроса. Финальный URL восстанавливается через компоновку частей.
$url = 'http://example.com/page?name=John&age=30#section';
$parts = parse_url($url);
parse_str($parts['query'] ?? '', $params);
$params['age'] = 31; // изменение параметра
$params['city'] = 'Moscow'; // добавление
$newQuery = http_build_query($params);
$newUrl = $parts['scheme'] . '://' . $parts['host'] . $parts['path'] . '?' . $newQuery . (isset($parts['fragment']) ? '#' . $parts['fragment'] : '');
echo $newUrl;
http://example.com/page?name=John&age=31&city=Moscow#section
Типичные ошибки:
- parse_url() возвращает false для абсолютно некорректного URL – всегда проверяйте результат.
- parse_str() не различает типы данных, все параметры становятся строками – для числовых значений требуется явное приведение.
- http_build_query() по умолчанию использует кодировку urlencode (пробел как '+'), что допустимо для query string, но для других частей может потребоваться rawurlencode.
Как разобрать URL на составные компоненты?
Функция parse_url() возвращает ассоциативный массив с ключами: scheme, host, port, user, pass, path, query, fragment. Для URL с нестандартными портами или авторизацией это незаменимо.
$url = 'https://user:pass@api.example.com:8080/data?id=5&type=json#result';
$parsed = parse_url($url);
print_r($parsed);
Array
(
[scheme] => https
[host] => api.example.com
[port] => 8080
[user] => user
[pass] => pass
[path] => /data
[query] => id=5&type=json
[fragment] => result
)
Проблемы:
- Функция не поддерживает относительные URL (начинающиеся с '/' или без схемы) – для таких случаев сначала определите базовый URL.
- Если в URL присутствует символ '#' в query (что некорректно), он будет интерпретирован как начало фрагмента – экранируйте такие символы заранее.
Как собрать URL из набора параметров?
Для создания строки запроса из массива используется http_build_query($params). Она автоматически кодирует ключи и значения, обрабатывает многомерные массивы (превращает их в key[subkey]=value).
$params = [
'name' => 'Анна',
'tags' => ['php', 'url'],
'page' => 2
];
echo http_build_query($params);
name=%D0%90%D0%BD%D0%BD%D0%B0&tags%5B0%5D=php&tags%5B1%5D=url&page=2
Распространённые ошибки:
- Не учитывается порядок параметров – функция сортирует по ключам (если не передан флаг сортировки).
- Многомерные массивы создают индексы (как tags[0]), что может быть нежелательно – используйте второй аргумент http_build_query($params, '', '&') для кастомного разделителя.
- Параметры с пустыми значениями (null, false, '') ведут себя по-разному – false становится '0', null пропускается.
Как правильно закодировать и декодировать URL?
urlencode() и urldecode() следуют стандарту application/x-www-form-urlencoded (пробелы заменяются на '+'). rawurlencode() и rawurldecode() следуют RFC 3986 (пробелы – '%20'). Для query string допустимы оба варианта, но для пути или фрагмента предпочтительнее raw-версия.
$param = 'значение с пробелами';
echo 'urlencode: ' . urlencode($param) . PHP_EOL;
echo 'rawurlencode: ' . rawurlencode($param);
urlencode: %D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5+%D1%81+%D0%BF%D1%80%D0%BE%D0%B1%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8 rawurlencode: %D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%D1%81%20%D0%BF%D1%80%D0%BE%D0%B1%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8
Частые ошибки:
- Использование urlencode() для сегментов пути – там '+' недопустим, применяйте rawurlencode().
- Декодирование ранее закодированной строки несколько раз приводит к повреждению данных.
- Проблемы с кодировкой UTF-8 – обе функции корректно обрабатывают многобайтовые символы.
Как получить текущий URL скрипта и его части?
Текущий URL доступен через суперглобальный массив $_SERVER: $_SERVER['REQUEST_URI'] (путь и query), $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_SCHEME'] или $_SERVER['HTTPS']. Для полного URL можно собрать:
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$uri = $_SERVER['REQUEST_URI'] ?? '/';
$fullUrl = $scheme . '://' . $host . $uri;
echo $fullUrl;
http://example.com/page?name=John (пример вывода)
Ошибки и нюансы:
- $_SERVER['HTTP_HOST'] может содержать порт (например, localhost:8080) – используйте parse_url() для извлечения.
- В некоторых окружениях REQUEST_SCHEME отсутствует – полагайтесь на HTTPS.
- При обратном прокси заголовки могут быть изменены – проверяйте HTTP_X_FORWARDED_PROTO и другие.
Как модифицировать отдельные параметры в строке запроса?
Удалить, добавить или изменить один параметр можно через манипуляции с массивом после parse_str(). Для удаления параметра используйте unset($params['key']). Пример удаления параметра name из URL:
$url = 'http://site.com/?name=John&age=30';
$parts = parse_url($url);
parse_str($parts['query'], $params);
unset($params['name']);
$newQuery = http_build_query($params);
$newUrl = $parts['scheme'] . '://' . $parts['host'] . $parts['path'] . '?' . $newQuery;
echo $newUrl;
http://site.com/?age=30
Подводные камни:
- После удаления всех параметров строка запроса будет пустой – необходимо убрать символ '?' для чистоты URL (проверяйте, пуст ли $newQuery).
- Параметры с одинаковыми именами (массивы) – parse_str() преобразует в массив, и удаление одного значения потребует более сложной логики.
Расширенные примеры работы с URL
Пример 1. Извлечение доменного имени из произвольного URL с обработкой ошибок
Функция parse_url() возвращает false для некорректных строк. Данный пример демонстрирует безопасное извлечение хоста и проверку входящего адреса.
function extractHost(string $url): ?string {
$parts = parse_url($url);
if ($parts === false) {
return null; // невалидный URL
}
return $parts['host'] ?? null;
}
$testUrls = [
'https://www.example.com/path',
'invalid url without scheme',
'ftp://files.server.org:21'
];
foreach ($testUrls as $url) {
$host = extractHost($url);
echo 'URL: ' . $url . ' => Host: ' . ($host ?? 'null') . PHP_EOL;
}
URL: https://www.example.com/path => Host: www.example.com URL: invalid url without scheme => Host: null URL: ftp://files.server.org:21 => Host: files.server.org
Пример 2. Преобразование относительного URL в абсолютный с учётом базового
Для относительных ссылок (начинающихся с '/' или без схемы) необходимо объединить их с базовым URL. Реализация учитывает разные случаи: полный относительный путь, только путь, запрос или фрагмент.
function resolveUrl(string $base, string $relative): string {
if (parse_url($relative, PHP_URL_SCHEME) !== null) {
return $relative; // уже абсолютный
}
$baseParts = parse_url($base);
if ($baseParts === false) {
throw new InvalidArgumentException('Base URL is malformed');
}
$scheme = $baseParts['scheme'] ?? 'http';
$host = $baseParts['host'] ?? '';
$port = isset($baseParts['port']) ? ':' . $baseParts['port'] : '';
$path = $baseParts['path'] ?? '/';
$query = $baseParts['query'] ?? '';
$fragment = $baseParts['fragment'] ?? '';
// Если относительный начинается с '//' (protocol-relative)
if (strpos($relative, '//') === 0) {
return $scheme . ':' . $relative;
}
// Если относительный начинается с '/'
if (strpos($relative, '/') === 0) {
return $scheme . '://' . $host . $port . $relative;
}
// Обработка ссылок вида '?query=1' или '#frag'
if ($relative[0] === '?') {
return $scheme . '://' . $host . $port . $path . $relative;
}
if ($relative[0] === '#') {
return $scheme . '://' . $host . $port . $path . ($query ? '?' . $query : '') . $relative;
}
// Иначе базовая директория + относительный путь
$baseDir = dirname($path);
if ($baseDir === '\\' || $baseDir === '.') {
$baseDir = '/';
}
$normalizedDir = rtrim($baseDir, '/') . '/';
return $scheme . '://' . $host . $port . $normalizedDir . $relative;
}
$base = 'https://example.com/sub/page?ref=1';
echo resolveUrl($base, 'newpage') . PHP_EOL;
echo resolveUrl($base, '/absolute/path') . PHP_EOL;
echo resolveUrl($base, '?lang=en') . PHP_EOL;
echo resolveUrl($base, '#anchor') . PHP_EOL;
echo resolveUrl($base, '//cdn.example.com/file.js') . PHP_EOL;
https://example.com/sub/newpage https://example.com/absolute/path https://example.com/sub/page?lang=en https://example.com/sub/page?ref=1#anchor https://cdn.example.com/file.js
Пример 3. Формирование URL с авторизацией и нестандартным портом
Комбинирование всех частей URL с использованием http_build_url() (имитация, т.к. встроенной нет). Показана ручная сборка с кодированием логина и пароля.
function buildUserUrl(string $scheme, string $host, int $port, string $user, string $pass, string $path, array $params, ?string $frag): string {
$auth = rawurlencode($user) . ':' . rawurlencode($pass) . '@';
$query = $params ? '?' . http_build_query($params) : '';
$fragment = $frag ? '#' . rawurlencode($frag) : '';
return $scheme . '://' . $auth . $host . ':' . $port . $path . $query . $fragment;
}
$url = buildUserUrl('https', 'api.example.com', 8443, 'myUser', 'p@s$word', '/v2/data', ['type' => 'json', 'limit' => 10], 'result');
echo $url;
https://myUser:p%40s%24word@api.example.com:8443/v2/data?type=json&limit=10#result
Пример 4. Работа с фрагментом (hash) без перезагрузки страницы
Фрагмент URL (часть после '#') не отправляется на сервер, но доступен на клиенте через JavaScript. Однако в PHP его можно получить при формировании ссылки или при использовании $_SERVER['REQUEST_URI'] (если фрагмент не отбрасывается браузером). Пример извлечения фрагмента из вручную переданного URL.
function getFragment(string $url): ?string {
$parts = parse_url($url);
return $parts['fragment'] ?? null;
}
$url = 'https://example.com/page#section2';
echo 'Fragment: ' . getFragment($url);
Fragment: section2
Пример 5. Использование rawurlencode для безопасного построения пути REST API
При создании URL для API часто требуется встраивать идентификаторы, содержащие слэши или другие спецсимволы. rawurlencode() гарантирует корректное кодирование сегмента пути.
function buildResourceUri(string $baseApi, string $resource, string $id): string {
$encodedId = rawurlencode($id);
return rtrim($baseApi, '/') . '/' . rawurlencode($resource) . '/' . $encodedId;
}
$api = 'https://api.example.com/v1';
echo buildResourceUri($api, 'users', 'user@example.com') . PHP_EOL;
echo buildResourceUri($api, 'files', 'path/to/file.txt');
https://api.example.com/v1/users/user%40example.com https://api.example.com/v1/files/path%2Fto%2Ffile.txt