Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 142 · Практика

Apache: HTTP-заголовки, серверные заметки, окружение и подзапросы

Полная категория Apache: все функции, настройки, режимы запуска PHP, различия заголовков и практическая диагностика.

Зачем PHP знать о веб-сервере

Представьте сайт с формой заказа. Браузер обращается по HTTP; веб-сервер принимает запрос и решает, как запустить PHP. Код PHP получает данные, формирует ответ и возвращает его через сервер. Apache — один из таких серверов, а SAPI — способ связи интерпретатора с окружением. Это объясняет, почему Apache-функции доступны не в любом PHP.

Один запрос к сайту
  1. Браузер: POST /order + заголовки + тело
  2. Apache принимает и маршрутизирует запрос
  3. PHP запускается внутри Apache-модуля либо отдельным FastCGI/FPM-процессом
  4. Приложение проверяет данные и готовит ответ
  5. Сервер отправляет ответ и пишет журнал

В обычных задачах формы, JSON и базы знаний Apache API часто вообще не нужны. Здесь они полезны для диагностики модульного окружения, HTTP-заголовков, связи с серверными журналами и старых SSI/CGI-компонентов.

Сначала представить один заголовок обычной парой ключ/значениеСамостоятельный пример
<?php
declare(strict_types=1);

$headers = ['Content-Type' => 'application/json'];
echo $headers['Content-Type'], "\n";
Ожидаемый результат
application/json

Это обычный учебный PHP-массив. Здесь нет сети: значение записали сами. Дальше getallheaders получает подобные данные от реального запроса, а проверка JSON-тела остаётся отдельным действием.

Apache перед PHP и PHP внутри Apache — разные схемы

РежимЧто происходитПрактическое следствие
apache2handlerИнтерпретатор — модуль в процессе Apache.Доступны notes, subprocess_env, lookup и virtual.
fpm-fcgiСервер передаёт запрос отдельному PHP-FPM worker.Apache-specific notes/virtual нет; заголовки запроса доступны в современных PHP.
cgi-fcgiPHP CGI/FastCGI-бинарник, управляемый сервером/менеджером.Возможности не тождественны FPM; проверяйте конкретную функцию и режим.
cli-serverphp -S — локальный встроенный HTTP-сервер.Есть HTTP-заголовки, но нет Apache-модулей/notes/virtual.
cliphp script.php, cron или обычный PHPUnit.HTTP-запроса нет. Подготовьте данные для бизнес-функции явным параметром.
Узнать способ запуска, прежде чем диагностировать APIСамостоятельный пример
<?php
declare(strict_types=1);

echo PHP_SAPI, "\n";
echo function_exists('apache_note') ? "Apache notes доступны\n" : "Apache notes отсутствуют\n";
Ожидаемый результат
cli
Apache notes отсутствуют

Ожидается CLI; через локальный веб-сервер первая строка cli-server. Проверка выполняется там, где возникла проблема: терминал и сайт могут использовать разные бинарники, ini и расширения.

Не делайте из function_exists разрешение пользователю. Это только наличие программной возможности. Для HTTP-проверки держите диагностический endpoint локальным/закрытым; публичный phpinfo раскрывает больше данных, чем нужно.

Установка и проверка выбранного способа запуска

Категория Apache в PHP Manual отсылает к установке сервера; отдельную extension=apache DLL в php.ini не подключают. На Windows модулю Apache требуется совместимая Thread Safe сборка PHP с php8apache2_4.dll, одинаковой архитектурой и подходящими зависимостями. FastCGI использует другой способ запуска. На Unix выбирают пакеты/сборку под SAPI и MPM; prefork и threaded MPM имеют разные ограничения совместимости.

Сначала настройте обслуживаемый каталог, обработчик .php и нужный ini в локальном virtual host; затем проверьте короткий endpoint с PHP_SAPI и результат собственного скрипта. Если PHP-файл скачивается или показывается исходником, обработчик не работает: публикацию останавливают и закрывают доступ к исходникам. Простое наличие установленного Apache этого не проверяет.

Практическая последовательность: проверить синтаксис конфигурации средствами своей сборки Apache, загрузить её на тестовом окружении, открыть тестовый URL, убедиться в ожидаемом SAPI. Перезапуск production-сервера не является частью этих учебных примеров.

Настройки Apache-категории и исторические отличия

НастройкаСтандарт и доступСмысл
engine1, INI_ALLРазрешает обработку PHP модулем. engine off не является запретом HTTP-доступа к файлу: дальнейший handler может отдать его содержимое.
last_modified0, INI_ALLМодуль добавляет Last-Modified по времени PHP-файла. Это не время изменения строк базы и не правильный автоматический кеш динамического заказа.
xbithack0, INI_ALLВ apache2handler8.5.11 обрабатывает как PHP text/html-ресурс с owner execute bit. Историческое обобщение «любой исполняемый файл» слишком широко.
child_terminate0, историческая INI_ALLПрисутствует в списке старого manual, но не зарегистрирована в текущем apache2handler8.5.11. Не исправит отсутствие apache_child_terminate в нём.

Для mod_php значения могут задаваться в серверной конфигурации и, где разрешено сервером, в .htaccess через php_value/php_flag. Административные php_admin_value/php_admin_flag не дают обычному скрипту переопределять заданный предел. В FPM строка php_flag в .htaccess не настраивает PHP и способна вызвать HTTP500; у FPM свой ini/pool и применимые .user.ini-правила.

Разобрать отключение обработки и запрет выдачи как разные правилаФрагмент или команда
<Directory "/srv/manual-demo/private-source">
    php_flag engine off
    Require all denied
</Directory>

Фрагмент только для модульного Apache2.4. Первая строка отключает PHP-обработку, вторая запрещает HTTP-доступ. Для секретных файлов лучше хранение вне document root; одно engine off могло бы привести к выдаче исходника. Это объяснение для собственного стенда, не изменение сервера.

У категории нет отдельного набора публичных PHP-констант/классов. Параметры и функции зависят от SAPI; в справочнике сохранены и исторические сведения, и уточнения по текущему исходному коду.

От задачи к нужному направлению заголовков

ПотребностьИнструментОграничение
Клиент сообщает JSON или HTML? apache_request_headers / getallheadersВходные значения требуют проверки.
Что приложение приготовило клиенту?apache_response_headers / headers_listПоздние server/proxy-заголовки ещё могут отличаться.
Уже начали отправлять ответ?headers_sentЭто другая задача, не перечисление входа.

Имя Content-Type, content-type и CONTENT-TYPE обозначает одно HTTP-поле. strtolower применяют к имени, не ко всем значениям. Важные специальные поля вроде Content-Type могут попадать в $_SERVER без префикса HTTP_. Содержимое POST-формы живёт в $_POST, JSON-тело читается отдельно; getallheaders не декодирует ни то, ни другое.

Проверить свою логику без веб-сервераСамостоятельный пример
<?php
declare(strict_types=1);

function requestMediaType(array $headers): string {
    foreach ($headers as $name => $value) {
        if (strcasecmp($name, 'Content-Type') === 0) {
            return strtolower(trim(explode(';', $value, 2)[0]));
        }
    }
    return '';
}
echo requestMediaType(['content-type' => 'application/json; charset=UTF-8']), "\n";
echo requestMediaType([]) === '' ? "Формат не указан\n" : "Есть формат\n";
Ожидаемый результат
application/json
Формат не указан

Массив строк передаётся явно; функция ничего не знает про Apache и легко проверяется. В endpoint можно вызвать requestMediaType(getallheaders()). Это и есть отделение получения запроса от проверки данных; тип тела всё равно проверяется декодером.

Не смешивать PHP-переменную, заметку, окружение и сессию

ХранилищеКому нужноСрок и граница
$value / локальный массивКоду PHP.Область видимости кода, аргументы/return.
Apache notesapache_note и серверному журналу/модулям.Текущий request, чтение в LogFormat через %{name}n.
subprocess_envapache_getenv / apache_setenv и обработчикам Apache.Таблица запроса, не автоматическое обновление $_SERVER/ОС.
$_SESSIONсессии приложенияОтдельное хранилище между запросами с собственной блокировкой и защитой.

Из страницы браузер делает новые запросы за JS, CSS и картинками. Это самостоятельные HTTP-запросы; серверная заметка первого не размножается на них. Такой вопрос регулярно возникает в интеграциях журналов. Для доступа к учётной записи нужно приложение/сессия, а для журналов — безопасные отдельные метки; cookie с session ID не копируют в журнал ради удобства.

Когда нужен Apache subrequest, а когда достаточно PHP

Частичный поиск и выполнение — разные шаги
  1. Фиксированный URI из настройки приложения
  2. apache_lookup_uri: определить ресурс и обработчик
  3. virtual: отдельный серверный подзапрос, исполнение и вывод
  4. Родительский PHP продолжает код после возвращения true/false

Если нужно повторно использовать кусок PHP, выделите функцию/шаблон. Если нужен ответ внешнего сервиса, используйте HTTP-клиент с таймаутом. Если старый SSI/CGI уже обслуживается Apache, появляется смысл virtual. Подзапрос не превращается в обычный PHP-вызов: буферы, заголовки, окружение и журнал работают по его правилам.

Большой цикл из lookup/virtual — это большой объём серверной работы. Перенос повторяющегося фрагмента в шаблон или кеш может быть уместнее, но кеш должен учитывать персональные данные и срок актуальности. Не кешируйте корзину всех посетителей под одним общим ключом.

Все функции категории Apache

ФункцияНазначение
apache_request_headersПолучить заголовки входящего HTTP-запроса в ассоциативный массив.
getallheadersПрочитать HTTP-заголовки через второе имя apache_request_headers.
apache_response_headersПолучить доступную SAPI карту заголовков исходящего ответа.
apache_get_modulesУзнать, какие модули загружены в Apache, обрабатывающем PHP.
apache_get_versionПрочитать строку версии/banner Apache для диагностики окружения.
apache_noteСогласовать информацию PHP и Apache-модулей в пределах запроса.
apache_getenvПрочитать строковую переменную Apache subprocess_env текущего запроса.
apache_setenvУстановить переменную в Apache subprocess_env для обработки запроса.
apache_lookup_uriВыполнить частичное разрешение локального URI сервером Apache.
virtualВывести результат Apache subrequest для фиксированного локального ресурса.
apache_child_terminateСпециальный запрос на завершение обслуживающего процесса после текущего запроса.

Практика: локальный endpoint и диагностика без раскрытия токена

Создайте на локальном HTTP-стенде endpoint, который принимает Content-Type: application/json, отвечает text/plain, пишет безопасную метку маршрута только если поддерживаются Apache notes. Сначала проверьте отсутствие Content-Type, затем корректный запрос и неподдерживаемый text/xml. Нельзя печатать Authorization целиком, вызывать виртуальный путь из $_GET или ожидать, что notes сохранится при следующем запросе.

Собрать проверку режима и данных одного запросаПример для указанного окружения
<?php
declare(strict_types=1);

if (!function_exists('getallheaders')) {
    echo 'Этот пример запускается через HTTP';
    return;
}
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
$type = strtolower(trim(explode(';', $headers['content-type'] ?? '', 2)[0]));
header('Content-Type: text/plain; charset=UTF-8');
if ($type !== 'application/json') {
    http_response_code(415);
    echo 'Нужен JSON';
    return;
}
if (function_exists('apache_note')) apache_note('manual-route', 'json-demo');
echo 'Формат принят; дальше требуется проверка тела';
Ожидаемый результат
Формат принят; дальше требуется проверка тела

При HTTP-запросе Content-Type: application/json. Это подготовка к следующему шагу, а не готовое сохранение заказа: ещё нужны чтение/проверка JSON, авторизация и бизнес-правила. Встроенный сервер выполнит путь заголовков без Apache note.

Следующий шаг: связь формы с PHP, входные суперглобальные массивы, контракты JSON, порядок HTTP-ответа.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

apache_request_headersПолучить заголовки входящего HTTP-запроса в ассоциативный массив.

См. версии и SAPI в карточке · SAPI-зависимая

apache_request_headers(): array

Получить заголовки входящего HTTP-запроса в ассоциативный массив.

Часть контрактаОбъяснение
ПараметрыНет. Функция читает текущий запрос, не запрашивает другой URL.
РезультатМассив имя заголовка → строковое значение. Не $_POST и не тело запроса.
ДоступностьApache-модуль; также некоторые другие SAPI. В PHP-FPM доступна с7.3. Встроенный HTTP-сервер php -S поддерживает её; обычный запуск php script.php — другой SAPI и HTTP-запроса не имеет.
ОшибкиЕсли функции нет, без проверки будет Error: undefined function. function_exists проверяет возможность вызова, но не корректность заголовков.

Браузер или клиент API присылает заголовки вместе с запросом: например Content-Type объясняет формат тела. Полученный массив позволяет выбрать дальнейшую обработку. Имя HTTP-заголовка сравнивают без учёта регистра, но обычный PHP-ключ массива регистр учитывает.

Прочитать один заголовок запроса без дампа секретовПример для указанного окружения
<?php
declare(strict_types=1);

$headers = apache_request_headers();
$contentType = null;
foreach ($headers as $name => $value) {
    if (strcasecmp($name, 'Content-Type') === 0) {
        $contentType = $value;
        break;
    }
}
echo $contentType ?? 'Content-Type отсутствует';
Ожидаемый результат
application/json

HTTP-условие: клиент прислал Content-Type: application/json. Заголовок может отсутствовать: это обработано явно. Значение ещё не доказывает, что тело действительно корректный JSON.

Отделить тип данных от параметра charsetПример для указанного окружения
<?php
declare(strict_types=1);

$headers = array_change_key_case(apache_request_headers(), CASE_LOWER);
$contentType = $headers['content-type'] ?? '';
$mediaType = strtolower(trim(explode(';', $contentType, 2)[0]));
if ($mediaType !== 'application/json') {
    http_response_code(415);
    echo 'Для этого endpoint нужен application/json';
    return;
}
echo 'Можно переходить к чтению и проверке JSON';
Ожидаемый результат
Можно переходить к чтению и проверке JSON

Для Content-Type: application/json; charset=UTF-8. Это политика конкретного endpoint: другие форматы, включая application/problem+json, не приняты автоматически. Следующий шаг — лимит тела и json_decode с обработкой ошибки.

Authorization, Cookie и токены не выводят пользователю и не складывают целиком в журнал. X-Forwarded-For/X-Forwarded-Host не становятся доверенными только потому, что есть в массиве: их принимает и нормализует настроенный доверенный proxy. HTTP-имена без регистра не означают, что регистр значения Bearer-токена тоже можно менять.

Это представление заголовков, которое предоставил SAPI; исходные повторяющиеся строки и порядок не гарантируются. Перезапись одинаковых PHP-ключей может скрыть повтор. Решение о конфликтующих Content-Length/Transfer-Encoding относится к корректно настроенному HTTP-серверу, не к попытке исправить сырой HTTP в приложении.

Связи: getallheaders — тот же входной контракт; apache_response_headers — другое направление; $_SERVER и данные запроса, проверка JSON.

getallheadersПрочитать HTTP-заголовки через второе имя apache_request_headers.

См. версии и SAPI в карточке · SAPI-зависимая

getallheaders(): array

Прочитать HTTP-заголовки через второе имя apache_request_headers.

Часть контрактаОбъяснение
ПараметрыНет.
РезультатАссоциативный массив заголовков текущего запроса; не HTML и не объект Response.
ВерсииВ PHP-FPM с7.3. Проверяйте реальный SAPI, а не название сервера перед ним.
ГраницаCLI PHPUnit/cron может не иметь этой функции, даже если сайт на том же компьютере успешно её вызывает.

Название удобно при чтении кода, который работает за nginx/PHP-FPM: оно не создаёт ложного впечатления, будто приложение обязано использовать Apache. В поддерживаемом SAPI результат соответствует apache_request_headers; это не отдельный сетевой запрос и не расширенный парсер.

Проверить наличие пользовательского заголовкаПример для указанного окружения
<?php
declare(strict_types=1);

$headers = array_change_key_case(getallheaders(), CASE_LOWER);
echo isset($headers['x-demo-client']) ? "Метка присутствует\n" : "Метки нет\n";
Ожидаемый результат
Метка присутствует

Тестовый HTTP-запрос содержит X-Demo-Client: mobile. Наличие метки не аутентифицирует клиента: любой отправитель может её поставить.

Не вызывать HTTP-функцию в CLI-командеСамостоятельный пример
<?php
declare(strict_types=1);

if (!function_exists('getallheaders')) {
    echo "В этом SAPI getallheaders недоступна\n";
} else {
    echo "HTTP-функция доступна\n";
}
Ожидаемый результат
В этом SAPI getallheaders недоступна

Ожидается обычный CLI PHP8.5.11. При запуске через php -S результат другой. Для тестов бизнес-логики передайте подготовленный массив заголовков параметром, вместо имитации веб-сервера глобальной функцией.

Не объявляйте собственную getallheaders без function_exists: при переносе на SAPI со встроенной функцией возникнет повторное объявление. Наивный fallback только по HTTP_* пропускает специальные CONTENT_TYPE/CONTENT_LENGTH и особенности AUTHORIZATION. Для HTTP-приложения используйте слой запроса фреймворка; для маленького endpoint можно читать конкретный нужный ключ $_SERVER, понимая его источник. См. apache_request_headers и параметр функции как явный вход.

apache_response_headersПолучить доступную SAPI карту заголовков исходящего ответа.

См. версии и SAPI в карточке · SAPI-зависимая

apache_response_headers(): array

Получить доступную SAPI карту заголовков исходящего ответа.

Часть контрактаОбъяснение
ПараметрыНет.
РезультатАссоциативный массив имя → значение, а не строка статуса и не тело ответа.
ДоступностьApache, некоторые серверные SAPI, встроенный cli-server. Обычный CLI и PHP-FPM8.5.11 не следует считать поддерживаемыми без function_exists.
ОграниченияОдинаковые имена могут схлопываться; не все поздние серверные/proxy-заголовки уже доступны.

Приложение собирает исходящие заголовки: Content-Type, Cache-Control, собственную диагностическую метку. Их можно изучить до вывода тела. Это не чтение заголовков удалённого сайта и не полный снимок конечного ответа после CDN/proxy.

Проверить подготовленный тип ответаПример для указанного окружения
<?php
declare(strict_types=1);

header('Content-Type: text/plain; charset=UTF-8');
$headers = array_change_key_case(apache_response_headers(), CASE_LOWER);
echo $headers['content-type'] ?? 'Тип ещё не виден';
Ожидаемый результат
text/plain; charset=UTF-8

На встроенном HTTP-сервере PHP8.5.11 эти подготовленные заголовки видны до тела. В Apache момент заполнения headers_out определяется серверным циклом. Код не доказывает, что proxy сохранит тот же заголовок.

Увидеть, почему массив не заменяет список повторяющихся заголовковПример для указанного окружения
<?php
declare(strict_types=1);

header('X-Demo-Tag: first', false);
header('X-Demo-Tag: second', false);
$headers = apache_response_headers();
$list = array_values(array_filter(headers_list(),
    static fn(string $line): bool => str_starts_with(strtolower($line), 'x-demo-tag:')));
echo $headers['X-Demo-Tag'], "\n";
echo count($list), "\n";
Ожидаемый результат
second
2

Результат для PHP8.5.11 cli-server: одинаковый ключ массива оставляет последнее значение, а headers_list содержит обе строки. Не используйте такую карту для точного копирования Set-Cookie. Это демонстрационный X-заголовок, не правило объединения любых HTTP-полей.

Заголовки сервера вроде Date и длины ответа могут добавляться позднее. Для диагностики приложения обычно подходят headers_list и headers_sent. Наличие функции проверяют отдельно: несмотря на широкое описание в старом справочнике, в исходниках PHP8.5.11 FPM эта функция не зарегистрирована. Увидеть поддержку apache_request_headers — ещё не доказательство поддержки response-варианта.

apache_get_modulesУзнать, какие модули загружены в Apache, обрабатывающем PHP.

См. версии и SAPI в карточке · SAPI-зависимая

apache_get_modules(): array

Узнать, какие модули загружены в Apache, обрабатывающем PHP.

Часть контрактаОбъяснение
ПараметрыНет.
РезультатИндексированный массив строк — имён серверных модулей. Это не пути к файлам и не список расширений PHP.
Версии и SAPIС PHP4.3.2; требуется Apache-модуль PHP.
НюансЗагруженный модуль может быть выключен настройкой для конкретного каталога или virtual host.
Найти модуль маршрутизации на своём сервереПример для указанного окружения
<?php
declare(strict_types=1);

$modules = apache_get_modules();
echo in_array('mod_rewrite', $modules, true)
    ? "Модуль загружен\n" : "Модуль не найден\n";
Ожидаемый результат
Модуль загружен

Ожидание относится к Apache, в котором реально включён mod_rewrite. Это ещё не доказательство, что правила RewriteRule конкретного сайта разрешены и верны.

Проверить два серверных компонента перед переносом сайтаПример для указанного окружения
<?php
declare(strict_types=1);

$loaded = apache_get_modules();
foreach (['mod_rewrite', 'mod_headers'] as $required) {
    echo $required, ': ', in_array($required, $loaded, true) ? 'есть' : 'нет', "\n";
}
Ожидаемый результат
mod_rewrite: есть
mod_headers: есть

Список зависит от сборки Apache. Имена берутся из Apache-модулей; get_loaded_extensions, напротив, отвечает про расширения PHP. Не публикуйте подробный служебный отчёт на общедоступной странице.

Если PHP работает через FPM, установить Apache перед ним недостаточно: модульные сведения недоступны PHP-процессу этим API. Администратор использует httpd -M/apachectl -M в своём окружении. Не вызывайте внешнюю административную команду с параметрами посетителя ради диагностики. Связь: apache_get_version.

apache_get_versionПрочитать строку версии/banner Apache для диагностики окружения.

См. версии и SAPI в карточке · SAPI-зависимая

apache_get_version(): string|false

Прочитать строку версии/banner Apache для диагностики окружения.

Часть контрактаОбъяснение
ПараметрыНет.
РезультатСтрока banner либо false, если она недоступна. Может быть сокращённой настройками сервера.
ВерсииС PHP4.3.2, Apache-модуль.
ПрименениеПроверка переноса собственного сайта; не публикация конфигурации и не замена проверки пакетов администратора.
Прочитать серверный banner для закрытой диагностикиПример для указанного окружения
<?php
declare(strict_types=1);

$version = apache_get_version();
echo $version === false ? "Banner недоступен\n" : "Banner получен\n";
Ожидаемый результат
Banner получен

Фактический текст зависит от сборки и ServerTokens. Здесь не зашита придуманная версия сервера.

Добавить сведения к внутреннему отчётуПример для указанного окружения
<?php
declare(strict_types=1);

$diagnostic = [
    'php' => PHP_VERSION,
    'sapi' => PHP_SAPI,
    'apache_banner' => apache_get_version(),
];
echo $diagnostic['sapi'] === 'apache2handler' ? "PHP встроен в Apache\n" : "Другой SAPI\n";
Ожидаемый результат
PHP встроен в Apache

Сам массив остаётся внутри закрытой диагностики. Banner не является машиночитаемой гарантией всех security-патчей: дистрибутив может backport-ить исправления.

Для выбора пути кода проверяют нужную функцию или возможность, а не ищут подстроку версии в banner. Apache_get_version сообщает об Apache, PHP_VERSION — об интерпретаторе; это разные компоненты. apache_get_modules отвечает о модулях, а не о версии.

apache_noteСогласовать информацию PHP и Apache-модулей в пределах запроса.

См. версии и SAPI в карточке · SAPI-зависимая

apache_note(string $note_name, ?string $note_value = null): string|false

Согласовать информацию PHP и Apache-модулей в пределах запроса.

Часть контрактаОбъяснение
note_nameИмя записи в таблице notes текущего request_rec.
note_valueПропущено/null: чтение. Строка: запись, включая пустую строку.
РезультатПри чтении — текущее значение, при записи — предыдущее; false при отсутствии читаемого прежнего значения.
ВерсииВ PHP8.0 явно nullable второй аргумент. Сами notes — Apache-специфичный механизм.

Заметка нужна, когда приложение знает результат бизнес-операции, а журнал запроса пишет Apache. Например: пометить, что запрос оформлял заказ, и затем найти его время/статус в access.log. Она живёт в текущем запросе, не в браузере и не между запросами.

Записать и затем прочитать серверную заметкуПример для указанного окружения
<?php
declare(strict_types=1);

$previous = apache_note('manual-route', 'checkout');
$current = apache_note('manual-route');
echo $current, "\n";
Ожидаемый результат
checkout

В новом запросе previous может быть false: это отсутствие прежнего значения, а не неудачная запись. Apache_note возвращает старое значение при записи.

Отличить старое значение от новогоПример для указанного окружения
<?php
declare(strict_types=1);

apache_note('manual-result', 'started');
$old = apache_note('manual-result', 'saved');
$now = apache_note('manual-result', null);
echo $old, ' -> ', $now, "\n";
Ожидаемый результат
started -> saved

Переданный null читает текущее значение, как пропущенный второй аргумент. Это не удаление заметки. Пустая строка, напротив, записывается как значение.

Вывести заметку в отдельное поле access.logФрагмент или команда
LogFormat "%h %>s %{manual-route}n" manual_route
CustomLog logs/manual-route.log manual_route

Фрагмент конфигурации Apache для администратора собственного тестового virtual host. Суффикс n читает note; e читает environment, i — входной заголовок. Здесь нет session ID, cookie или токенов.

В note можно передавать данные между модулями в рамках одного запроса, в том числе при согласованном Apache subrequest. Другой браузерный запрос за CSS/картинкой — отдельный запрос, он не наследует эти отметки автоматически. Для трассировки используйте отдельный безопасный correlation ID, не session_id как в старых примерах. Рядом apache_setenv, virtual и журналирование ошибок.

apache_getenvПрочитать строковую переменную Apache subprocess_env текущего запроса.

См. версии и SAPI в карточке · SAPI-зависимая

apache_getenv(string $variable, bool $walk_to_top = false): string|false

Прочитать строковую переменную Apache subprocess_env текущего запроса.

Часть контрактаОбъяснение
variableИмя переменной окружения Apache-запроса, не имя $переменной PHP.
walk_to_topfalse: текущий request_rec; true: предыдущий верхний запрос в цепочке внутренних redirects реализации.
РезультатСтрока или false, если нет значения.
Версии/альтернативаС PHP4.3.0. getenv относится к окружению процесса/SAPI и имеет иной контракт; не объявляйте эти функции взаимозаменяемыми.
Получить настройку маршрута из окружения запросаПример для указанного окружения
<?php
declare(strict_types=1);

$region = apache_getenv('APP_REGION');
echo $region === false ? "Регион не задан\n" : $region . "\n";
Ожидаемый результат
Регион не задан

Для запроса, где APP_REGION не установлен. Если администратор задал переменную, будет её строковое значение. Проверка === false не путает отсутствие с "0" или пустой строкой.

Сравнить два хранилища на одном примереПример для указанного окружения
<?php
declare(strict_types=1);

$before = $_SERVER['MANUAL_REGION'] ?? null;
apache_setenv('MANUAL_REGION', 'north');
echo apache_getenv('MANUAL_REGION'), "\n";
echo ($_SERVER['MANUAL_REGION'] ?? null) === $before ? "SERVER не изменён\n" : "SERVER изменён\n";
Ожидаемый результат
north
SERVER не изменён

Apache subprocess_env и уже сформированный PHP-массив $_SERVER — не одна ссылка на один объект. Изменение первого не обновляет второй автоматически.

walk_to_top=true по реализации apache2handler8.5 идёт по цепочке prev внутренних перенаправлений. Это не поиск глобальной переменной всех посетителей и не обещание пройти цепочку main всех subrequest. Параметр должен соответствовать конкретному Apache-модулю/редиректу. Не используйте его вместо области видимости PHP или сессии пользователя.

apache_setenvУстановить переменную в Apache subprocess_env для обработки запроса.

См. версии и SAPI в карточке · SAPI-зависимая

apache_setenv(string $variable, string $value, bool $walk_to_top = false): bool

Установить переменную в Apache subprocess_env для обработки запроса.

Часть контрактаОбъяснение
variable/valueИмя и новое строковое значение.
walk_to_topfalse: текущий запрос; true: цепочка prev внутренних redirects. Это не процессное/постоянное глобальное окружение.
Результатtrue при успехе, false при отказе; некорректные типы обрабатываются обычными правилами параметров PHP.
ВерсииС PHP4.2.0, Apache-модуль. Значение в PHP $_SERVER само не изменяется.
Передать строковую отметку Apache-компонентуПример для указанного окружения
<?php
declare(strict_types=1);

$saved = apache_setenv('MANUAL_SECTION', 'catalog');
echo $saved ? "Метка установлена\n" : "Метка не установлена\n";
Ожидаемый результат
Метка установлена

В пределах текущего Apache-запроса. Следующий отдельный HTTP-запрос не обязан видеть эту отметку.

Подготовить значение для доверенного SSI-фрагментаПример для указанного окружения
<?php
declare(strict_types=1);

apache_setenv('MANUAL_LABEL', 'Catalog');
if (!virtual('/manual-fragments/label.shtml')) {
    echo 'Фрагмент недоступен';
}
Ожидаемый результат
Catalog

Контекст: Apache разрешает SSI для фиксированного label.shtml, содержащего . Реальное окружение/наследование subrequest надо согласовать на стенде. В данном примере значение фиксировано; пользовательский HTML туда не передаётся.

Запись не меняет $_SERVER и не вызывает putenv. Использовать через «разные страницы» можно лишь в подходящем серверном контексте, например включённом SSI; обычный переход пользователя на другой URL не сохраняет значение. Не переносите секреты в потенциально выводимые SSI-переменные. apache_getenv читает значение, apache_note подходит для служебной отметки журнала.

apache_lookup_uriВыполнить частичное разрешение локального URI сервером Apache.

См. версии и SAPI в карточке · SAPI-зависимая

apache_lookup_uri(string $filename): object|false

Выполнить частичное разрешение локального URI сервером Apache.

Часть контрактаОбъяснение
filenameНесмотря на имя аргумента, передают URI для Apache lookup. Не произвольный URL внешнего API.
РезультатОбъект сведений либо false. В apache2handler8.5.11 объект возвращается только для HTTP200; для другого статуса — warning и false, а не объект с status404.
ОшибкиОтказ lookup/ресурса сопровождается предупреждением. Нулевой байт недопустим для path-аргумента. Объект не имеет стабильной схемы всех полей во всех версиях.
Побочные затратыСервер выполняет часть маршрутизации/проверок. Цикл по сотням URI способен быть дорогим; не подменяйте им обычный каталог приложения.

У приложения есть локальный URI /manual-fragments/menu.shtml; нужно узнать, как Apache сопоставит его ресурсу, не подставляя весь результат в страницу. Частичный subrequest проходит серверные правила поиска. Это более тяжёлая операция, чем чтение PHP-массива, и она не предназначена для сканирования любого адреса, присланного посетителем.

Получить описание известного локального URIПример для указанного окружения
<?php
declare(strict_types=1);

$info = apache_lookup_uri('/manual-fragments/menu.shtml');
if ($info === false) {
    echo "URI не разрешён\n";
    return;
}
echo $info->uri ?? 'URI не указан';
Ожидаемый результат
/manual-fragments/menu.shtml

Условие: ресурс существует и разрешается Apache со статусом HTTP200. false проверяется до доступа к свойствам; прямое $info->filename без этой проверки ломается на ошибке.

Отобрать нужные сведения вместо публикации физического путиПример для указанного окружения
<?php
declare(strict_types=1);

$info = apache_lookup_uri('/manual-fragments/menu.shtml');
if ($info !== false) {
    $publicInfo = [
        'uri' => $info->uri ?? null,
        'type' => $info->content_type ?? null,
    ];
    echo json_encode($publicInfo, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
} else {
    echo 'Не найдено';
}
Ожидаемый результат
{"uri":"/manual-fragments/menu.shtml","type":"text/html"}

Примерный результат для собственного SSI-ресурса с text/html. Точный content_type/handler зависит от конфигурации. Полный объект может раскрыть путь сервера и внутренние аргументы, поэтому его не возвращают посетителю целиком.

СвойстваЧто означают и где граница
status, the_request, status_line, methodСтатус и описание частичного запроса; строки добавляются только если Apache их заполнил.
uri, unparsed_uri, filename, path_info, argsНормализованный/исходный URI, физический путь, хвост и query string. Отсутствующее поле проверяют через ?? или isset.
content_type, handlerВыбранный тип и обработчик; это сведения о разрешении URI, не выполненный вывод.
no_cache, no_local_copy, allowedСлужебные флаги и маска методов Apache, не пользовательская роль и не PHP boolean-обещание авторизации.
sent_bodyct, bytes_sent, clength, chunked, rangeУчёт тела и диапазона по реализации8.5.11. На стадии lookup тело ещё не выполнялось; нули не означают, что будущий ответ пуст.
mtime, request_timeВременные сведения. request_time преобразуется к секундам; mtime в текущем C-коде записан дважды, последнее значение сырое APR. Не передавайте его слепо в date как Unix-секунды.
boundary, byterange, send_bodyctИмена из старого описания manual. В apache2handler8.5.11 они не добавляются; актуальное имя счётчика sent_bodyct. Наличие старого списка не гарантирует свойства объекта.

Успешный filesystem file_exists и успешный Apache lookup отвечают на разные вопросы. Первый не учитывает серверный URI-маршрут/доступ. Второй не резервирует файл и не устраняет гонку между проверкой и последующим использованием. Для включения обычного PHP-шаблона обычно достаточно фиксированного require, для Apache-обработчика смотрите virtual.

virtualВывести результат Apache subrequest для фиксированного локального ресурса.

См. версии и SAPI в карточке · SAPI-зависимая

virtual(string $uri): bool

Вывести результат Apache subrequest для фиксированного локального ресурса.

Часть контрактаОбъяснение
uriЛокальный URI Apache-обработчика: SSI, CGI и другие допустимые ресурсы. Не необработанный путь из формы.
Результатtrue при успешном выполнении, false при отказе; тело выводится побочным действием. Ошибки lookup/выполнения могут дать warning.
ВыводСбрасывает буферы и отправляет заголовки перед выполнением; проверка bool не возвращает уже отправленные байты назад.
ГраницаТолько Apache-модуль. Для include PHP-файла серверный subrequest обычно не нужен.

На старом сайте меню уже собирается SSI или CGI. virtual позволяет вставить его результат через тот же Apache без переноса обработчика в PHP. Для чистого PHP-шаблона include/require проще: они не создают Apache subrequest и не требуют серверного API.

Вставить результат известного SSI-обработчикаПример для указанного окружения
<?php
declare(strict_types=1);

header('Content-Type: text/html; charset=UTF-8');
echo '<main>';
if (!virtual('/manual-fragments/menu.shtml')) {
    echo '<p>Меню временно недоступно</p>';
}
echo '</main>';
Ожидаемый результат
<main><nav>Каталог</nav></main>

Ожидание для тестового menu.shtml, выдающего ровно

. virtual печатает результат, а возвращает boolean. Это не строка с HTML. Для CGI обработчик обязан сформировать корректные CGI-заголовки, как минимум Content-Type.

Выбрать серверный фрагмент из явного спискаПример для указанного окружения
<?php
declare(strict_types=1);

$requested = $_GET['part'] ?? 'menu';
$allowed = ['menu' => '/manual-fragments/menu.shtml'];
if (!is_string($requested) || !array_key_exists($requested, $allowed)) {
    http_response_code(400);
    echo 'Неизвестная часть страницы';
    return;
}
header('Content-Type: text/html; charset=UTF-8');
if (!virtual($allowed[$requested])) echo '<p>Раздел недоступен</p>';
Ожидаемый результат
<nav>Каталог</nav>

Условие: part=menu и настроенный фрагмент. Посетитель выбирает ключ, но не путь. Если ошибка virtual наступила после сброса заголовков, нельзя рассчитывать поменять основной HTTP-статус; обработка такого сбоя проектируется заранее.

В историческом PHP-подзапросе $_GET копируется из родительского скрипта, а переданный query string отражается в $_SERVER["QUERY_STRING"]; нельзя переносить ожидания обычного отдельного HTTP-запроса. Передача query для этого механизма поддерживалась Apache2. Subrequest не создаёт отдельной строки обычного access.log. Изменения окружения внутри включённого обработчика не обязаны быть видны вызывающему PHP.

Вызываемый обработчик может выполнять полноценную работу: каждый такой вызов имеет стоимость маршрутизации/выполнения. Вложенные вызовы способны зациклить маршруты. Для данных внешнего API выбирают HTTP-клиент cURL, для PHP-шаблона — подключение файлов и autoload, для передачи заметки между Apache-модулями — apache_note.

apache_child_terminateСпециальный запрос на завершение обслуживающего процесса после текущего запроса.

См. версии и SAPI в карточке · SAPI-зависимая

apache_child_terminate(): void

Специальный запрос на завершение обслуживающего процесса после текущего запроса.

Часть контрактаОбъяснение
ПараметрыНет.
РезультатНичего: не bool и не код возврата. Оставшийся PHP-код продолжает выполняться.
Версии/SAPIИсторически с PHP4.0.5. В8.5.11 отсутствует в apache2handler, реализация есть в CGI/FastCGI; не универсальный API PHP-FPM.
ОС и альтернативаДокументация исключает Windows. exit завершает PHP-скрипт, unset убирает переменную, настройки process manager управляют worker; это разные задачи.

Рабочий процесс обслужил тяжёлый импорт. Аллокатор мог освободить объекты для повторного использования, но его процессная память не обязательно немедленно возвращается ОС. Историческая идея функции — попросить обслуживающий процесс уйти после текущего запроса, чтобы менеджер процессов создал свежий. Это не средство завершить только функцию или освободить отдельный массив.

Проверить наличие специального механизма в текущем SAPIСамостоятельный пример
<?php
declare(strict_types=1);

echo function_exists('apache_child_terminate')
    ? "API есть: требуется проверить режим сервера\n"
    : "API в этом SAPI отсутствует\n";
Ожидаемый результат
API в этом SAPI отсутствует

Ожидание для CLI PHP8.5.11. Наличие функции в другом SAPI ещё не означает, что сервер исполняет нужный жизненный цикл.

Попросить подходящий FastCGI worker завершиться после запросаПример для указанного окружения
<?php
declare(strict_types=1);

// Только согласованный Unix CGI/FastCGI-стенд с этим API.
if (function_exists('apache_child_terminate')) {
    apache_child_terminate();
}
echo "Текущий код продолжает выполняться\n";
Ожидаемый результат
Текущий код продолжает выполняться

Контекстный пример: функция не делает exit и не возвращает признак успеха. В PHP8.5.11 реализация CGI вызывает fcgi_terminate только при настоящем FastCGI-режиме. На этом стенде такой lifecycle не запускался.

Название и старое описание могут ввести в заблуждение: в текущем apache2handler8.5.11 функции вообще нет; она присутствует в CGI/FastCGI-исходниках. Для Windows справочник указывает отсутствие поддержки. Для FPM срок жизни worker обычно задаёт pm.max_requests, для Apache — MaxConnectionsPerChild: это согласованные серверные настройки. Не добавляйте вызов «на всякий случай» в каждый запрос — перезапуски стоят ресурсов.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.