Работа с URL в PHP: от парсинга до перенаправления

Раздел: Веб-разработка на PHP -> Работа с 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 или индексации.

URL в PHP - comments

En
Php url (php)