Работа с URL в PHP: от парсинга до перенаправления
Основной инструментарий для работы с URL
Набор функций для разбора, сборки и кодирования URL
В PHP существует несколько встроенных функций, которые в совокупности позволяют эффективно обрабатывать URL. Основные из них: parse_url(), http_build_query(), parse_str(), urlencode(), rawurlencode(), urldecode(), rawurldecode(). С их помощью можно разложить любой адрес на компоненты, изменить параметры запроса, корректно закодировать специальные символы и собрать URL обратно.
Пример разбора URL:
<?php
$url = 'http://example.com/page?name=John&age=30#section';
$parts = parse_url($url);
print_r($parts);
?>Array
(
[scheme] => http
[host] => example.com
[path] => /page
[query] => name=John&age=30
[fragment] => section
)Далее можно извлечь строку запроса, преобразовать её в ассоциативный массив с помощью parse_str(), изменить значения и собрать обратно при помощи http_build_query():
<?php
$query = 'name=John&age=30';
parse_str($query, $params);
$params['name'] = 'Jane';
$params['city'] = 'New York';
$newQuery = http_build_query($params);
echo $newQuery;
?>name=Jane&age=30&city=New+YorkДля кодирования отдельных компонентов (например, значений параметров) применяется urlencode() (пробел кодируется как '+') или rawurlencode() (пробел кодируется как '%20'). Первый больше подходит для строки запроса, второй - для пути.
Типичные ошибки:
- Двойное кодирование: если данные уже закодированы, повторный вызов urlencode испортит строку.
- Путаница между urlencode и rawurlencode: использование '+' в пути может быть воспринято некорректно.
- Неэкранирование амперсанда (&) при вставке значений в HTML.
Как получить полный адрес текущей страницы?
Текущий URL можно собрать из суперглобального массива $_SERVER. Потребуется протокол, имя хоста и URI запроса.
<?php
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$uri = $_SERVER['REQUEST_URI'];
$currentUrl = $protocol . '://' . $host . $uri;
echo $currentUrl;
?>Проблема: в HTTP_HOST может быть указан нестандартный порт (например, example.com:8080). В таком случае порт уже включён, и дополнительно добавлять его не нужно. Если же порт стандартный (80 для HTTP, 443 для HTTPS), его можно опустить. Приведённый выше код корректно обрабатывает эту ситуацию.
Как разложить URL на компоненты с помощью одной функции?
Функция parse_url() возвращает ассоциативный массив со всеми элементами адреса. Можно также использовать второй аргумент для получения только одного компонента (например, PHP_URL_HOST).
<?php
$url = 'https://user:pass@host.com:8080/path?q=1#frag';
echo parse_url($url, PHP_URL_HOST); // host.com
echo parse_url($url, PHP_URL_PORT); // 8080
?>Как собрать URL из отдельных частей?
Для сборки строки запроса используется http_build_query(). Полный URL можно собрать конкатенацией.
<?php
$scheme = 'https';
$host = 'example.com';
$path = '/api';
$params = ['key' => 'value', 'page' => 2];
$query = http_build_query($params);
$url = $scheme . '://' . $host . $path . '?' . $query;
echo $url; // https://example.com/api?key=value&page=2
?>Как корректно закодировать символы для разных частей URL?
Для кодирования данных, вставляемых в строку запроса, применяется urlencode(). Для пути и других частей лучше использовать rawurlencode(). Различие заметно на пробеле: urlencode превращает его в +, rawurlencode - в %20.
<?php
echo urlencode('name=John Smith'); // name%3DJohn+Smith
echo rawurlencode('name=John Smith'); // name%3DJohn%20Smith
?>Как изменить один из параметров в существующей строке запроса?
С помощью parse_str() нужно преобразовать строку в массив, внести изменения и снова применить http_build_query(). При этом важно учитывать, что parse_str() по умолчанию перезаписывает переменные в текущей области видимости, поэтому лучше передать второй аргумент - массив.
<?php
$query = 'page=1&limit=10';
parse_str($query, $params);
$params['page'] = 3;
$params['sort'] = 'name';
$newQuery = http_build_query($params); // page=3&limit=10&sort=name
?>Как проверить, является ли строка валидным URL?
Функция filter_var() с флагом FILTER_VALIDATE_URL возвращает исходную строку, если она соответствует формату URL, или false в противном случае.
<?php
$url = 'http://example.com';
if (filter_var($url, FILTER_VALIDATE_URL)) {
echo 'Это корректный URL';
} else {
echo 'Некорректный URL';
}
?>Важно: проверяется только синтаксис, а не реальное существование ресурса. URL без схемы (например, example.com) будет признан некорректным.
Как выполнить перенаправление браузера на другой адрес?
Отправка HTTP-заголовка Location с последующим вызовом exit.
<?php
header('Location: /new-page');
exit;
?>Ошибка: вызов header() должен происходить до любого вывода в браузер. Если ранее был выведен HTML или пробел, заголовок не сработает.
Как загрузить данные по удалённому URL?
Самый простой способ - file_get_contents(), если в php.ini включена директива allow_url_fopen. Для более тонкой настройки (таймауты, заголовки, POST-запросы) используется расширение cURL.
<?php
// file_get_contents
$content = file_get_contents('http://example.com/data.json');
// cURL
$ch = curl_init('http://example.com/data.json');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
?>Проблемы: если allow_url_fopen выключен, file_get_contents вернёт false. cURL может требовать установленного расширения. Также следует обрабатывать ошибки соединения и HTTP-коды ответа.
Продвинутые примеры работы с URL
Ниже приведены расширенные и менее распространённые сценарии использования URL-функций PHP.
Многомерные параметры в http_build_query
Функция http_build_query() поддерживает вложенные массивы, создавая строку запроса с индексами.
<?php
$params = [
'filter' => [
'category' => 'books',
'price' => ['min' => 10, 'max' => 100]
],
'sort' => 'price_asc'
];
$query = http_build_query($params);
echo $query;
?>filter%5Bcategory%5D=books&filter%5Bprice%5D%5Bmin%5D=10&filter%5Bprice%5D%5Bmax%5D=100&sort=price_ascПолученная строка может быть декодирована обратно в массив при помощи parse_str().
Разбор сложного URL с авторизацией и портом
<?php
$url = 'https://user:pass@host.example.com:8080/path/to/resource?query=value&key=val#frag';
$parts = parse_url($url);
print_r($parts);
?>Array
(
[scheme] => https
[host] => host.example.com
[port] => 8080
[user] => user
[pass] => pass
[path] => /path/to/resource
[query] => query=value&key=val
[fragment] => frag
)Эти данные позволяют реконструировать URL или извлечь отдельные поля для аутентификации.
Преобразование относительного URL в абсолютный
Стандартная библиотека PHP не содержит готовой функции для разрешения относительных адресов. Ниже приведена реализация, основанная на parse_url() и обработке путей согласно RFC 3986 (упрощённо).
<?php
function resolveUrl($base, $relative) {
$parts = parse_url($relative);
if (isset($parts['scheme'])) {
return $relative;
}
$baseParts = parse_url($base);
$scheme = $baseParts['scheme'] ?? 'http';
$host = $baseParts['host'] ?? '';
$port = isset($baseParts['port']) ? ':' . $baseParts['port'] : '';
$path = '';
if (empty($parts['path'])) {
$path = $baseParts['path'] ?? '/';
} elseif ($parts['path'][0] === '/') {
$path = $parts['path'];
} else {
$basePath = $baseParts['path'] ?? '/';
$baseDir = dirname($basePath);
if ($baseDir === '\\') { $baseDir = '/'; }
$path = $baseDir . '/' . $parts['path'];
// нормализация: удаление '.'
$segments = explode('/', $path);
$resolved = [];
foreach ($segments as $seg) {
if ($seg === '.' || $seg === '') { continue; }
if ($seg === '..') { array_pop($resolved); continue; }
$resolved[] = $seg;
}
$path = '/' . implode('/', $resolved);
}
$query = isset($parts['query']) ? '?' . $parts['query'] : '';
$fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';
return $scheme . '://' . $host . $port . $path . $query . $fragment;
}
echo resolveUrl('http://example.com/a/b/c.php?p=1', '../images/logo.png?size=large');
?>http://example.com/a/images/logo.png?size=largeФункция обрабатывает относительные пути с '..' и '.', но не учитывает некоторые крайние случаи (например, корневой относительный путь). Для production рекомендуется использовать сторонние библиотеки (например, компонент URL от Symfony).
Использование cURL с обработкой ошибок и HTTP-кодами
<?php
function fetchUrl($url, $timeout = 10) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5
]);
$response = curl_exec($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($error) {
return ['error' => $error, 'http_code' => $httpCode];
}
return ['body' => $response, 'http_code' => $httpCode];
}
$result = fetchUrl('http://example.com/api');
if (isset($result['error'])) {
echo 'Ошибка: ' . $result['error'];
} else {
echo 'HTTP ' . $result['http_code'] . ': ' . substr($result['body'], 0, 200);
}
?>HTTP 200: <html>... (первые 200 символов ответа)Такой подход позволяет гибко обрабатывать неуспешные запросы, перенаправления и таймауты.
Нормализация URL: удаление лишних слешей и сортировка параметров
Для приведения URL к единообразному виду можно выполнить несколько операций: убрать повторяющиеся слеши, отсортировать параметры запроса и декодировать лишние символы.
<?php
function normalizeUrl($url) {
// Удаляем повторяющиеся слеши в пути (кроме протокола)
$url = preg_replace('#(?<=://)([^/]*)/{2,}#', '$1/', $url);
$parts = parse_url($url);
if (!isset($parts['query'])) {
return $url;
}
parse_str($parts['query'], $params);
ksort($params);
$sortedQuery = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$parts['query'] = $sortedQuery;
$scheme = $parts['scheme'] . '://';
$host = $parts['host'];
$port = isset($parts['port']) ? ':' . $parts['port'] : '';
$path = $parts['path'] ?? '/';
$query = $sortedQuery ? '?' . $sortedQuery : '';
$fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';
return $scheme . $host . $port . $path . $query . $fragment;
}
echo normalizeUrl('HTTP://Example.COM:80/path//to//page?b=2&a=1');
?>http://example.com:80/path/to/page?a=1&b=2Нормализация полезна при кэшировании, сравнении URL или индексации.