Get the full URL in PHP: как не изобретать велосипед и не потерять производительность
Каждый PHP-разработчик рано или поздно сталкивается с задачей: «А как мне получить полный URL текущей страницы?». Казалось бы, что тут сложного — взял $_SERVER['REQUEST_URI'], добавил домен — и готово. Но дьявол, как всегда, в деталях. Вопрос «Get the full URL in PHP» на Stack Overflow собрал тысячи просмотров и десятки ответов. И это неспроста.
В этой статье мы разберём как получить полный URL в PHP правильно, быстро и без багов. Вы узнаете, чем отличается HTTP_HOST от SERVER_NAME, как правильно определить протокол (HTTPS или HTTP), и почему не стоит слепо копировать код из интернета. Поехали!
Почему простой способ — не всегда лучший?
Самый очевидный вариант выглядит так:
<?php
$fullUrl = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
?>
Этот код работает, но только если у вас:
- Обязательно HTTP (не HTTPS)
- Нет порта, отличного от 80 или 443
- Сервер корректно передаёт заголовок Host
В реальном мире эти условия выполняются далеко не всегда. Если сайт работает через HTTPS, вы получите URL с http://, что приведёт к ошибке смешанного контента в браузере. Если порт нестандартный (например, 8080), он потеряется. А если злоумышленник подделает заголовок Host, вы можете получить URL на чужой домен.
Компоненты полного URL: разбор полётов
Прежде чем писать универсальное решение, давайте вспомним, из чего состоит полный URL:
scheme://host:port/path?query#fragment
В PHP нам доступны следующие элементы из суперглобального массива $_SERVER:
- scheme — протокол (HTTP или HTTPS). Прямого ключа нет, но можно определить через
$_SERVER['HTTPS']или$_SERVER['REQUEST_SCHEME'](доступен с PHP 7.4). - host — домен. Есть два кандидата:
HTTP_HOST(из заголовка Host) иSERVER_NAME(из конфигурации сервера). - port — порт.
$_SERVER['SERVER_PORT']. Если порт стандартный (80 для HTTP, 443 для HTTPS), его можно опустить. - path — путь.
$_SERVER['REQUEST_URI']содержит путь и query-строку. - query — строка запроса. Можно получить отдельно через
$_SERVER['QUERY_STRING']. - fragment — якорь. Он не передаётся на сервер, поэтому его не получить.
Правильное решение: функция getFullUrl()
Вот надёжный и производительный способ получить полный URL в PHP:
<?php
function getFullUrl(): string {
// Определяем протокол
$scheme = 'http';
if (
(isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] === '1'))
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
) {
$scheme = 'https';
}
// Хост: предпочитаем HTTP_HOST, но проверяем на пустоту
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
// Порт: добавляем только если нестандартный
$port = (int) ($_SERVER['SERVER_PORT'] ?? 80);
$portStr = '';
if (
($scheme === 'http' && $port !== 80) ||
($scheme === 'https' && $port !== 443)
) {
$portStr = ':' . $port;
}
// URI
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return $scheme . '://' . $host . $portStr . $uri;
}
?>
Эта функция учитывает:
- Протокол HTTPS (в том числе через прокси-серверы с заголовком
X-Forwarded-Proto). - Нестандартные порты.
- Отсутствие заголовка Host (падение на локальном сервере).
- Пустой REQUEST_URI (крайний случай).
Производительность: что быстрее?
Многие разработчики пишут что-то вроде:
<?php
$fullUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http')
. '://' . $_SERVER['HTTP_HOST']
. $_SERVER['REQUEST_URI'];
?>
Этот код короче, но он не проверяет порт и может работать некорректно за прокси. Если вам не нужна поддержка прокси и нестандартных портов, используйте упрощённый вариант — он быстрее. Но если сайт работает в облаке или за балансировщиком (AWS, nginx, Cloudflare), обязательно используйте полную версию.
Совет по оптимизации: если вы вызываете getFullUrl() много раз (например, в цикле или в каждом шаблоне), кешируйте результат в статической переменной или в реестре приложения:
<?php
function getFullUrlCached(): string {
static $url = null;
if ($url === null) {
$url = getFullUrl();
}
return $url;
}
?>
Это сократит количество обращений к $_SERVER до одного за запрос.
HTTP_HOST vs SERVER_NAME: что выбрать?
Это извечный вопрос. Давайте разберёмся:
- HTTP_HOST — значение заголовка Host из HTTP-запроса. Может быть подделано клиентом (но в большинстве случаев это не проблема для обычных сайтов).
- SERVER_NAME — имя сервера из конфигурации Apache/Nginx. Безопаснее, но может не совпадать с реальным доменом, если настроен виртуальный хост.
На практике рекомендуется использовать HTTP_HOST, так как он всегда содержит то, что ввёл пользователь. Если вы опасаетесь подделки (например, в критичных системах), добавьте проверку белого списка доменов:
<?php
$allowedHosts = ['site.ru', 'www.site.ru'];
$host = $_SERVER['HTTP_HOST'];
if (!in_array($host, $allowedHosts, true)) {
$host = $allowedHosts[0]; // или выбросить исключение
}
?>
Как быть с URL без query-строки?
Иногда нужно получить только путь без параметров. Для этого можно использовать parse_url():
<?php
$fullUrl = getFullUrl();
$urlWithoutQuery = parse_url($fullUrl, PHP_URL_SCHEME) .