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

Дерево руководства
Глава 142 · Практика
Apache: HTTP-заголовки, серверные заметки, окружение и подзапросы
Полная категория Apache: все функции, настройки, режимы запуска PHP, различия заголовков и практическая диагностика.
Зачем PHP знать о веб-сервере
Представьте сайт с формой заказа. Браузер обращается по HTTP; веб-сервер принимает запрос и решает, как запустить PHP. Код PHP получает данные, формирует ответ и возвращает его через сервер. Apache — один из таких серверов, а SAPI — способ связи интерпретатора с окружением. Это объясняет, почему Apache-функции доступны не в любом PHP.
- Браузер: POST /order + заголовки + тело
- Apache принимает и маршрутизирует запрос
- PHP запускается внутри Apache-модуля либо отдельным FastCGI/FPM-процессом
- Приложение проверяет данные и готовит ответ
- Сервер отправляет ответ и пишет журнал
В обычных задачах формы, 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-fcgi | PHP CGI/FastCGI-бинарник, управляемый сервером/менеджером. | Возможности не тождественны FPM; проверяйте конкретную функцию и режим. |
| cli-server | php -S — локальный встроенный HTTP-сервер. | Есть HTTP-заголовки, но нет Apache-модулей/notes/virtual. |
| cli | php script.php, cron или обычный PHPUnit. | HTTP-запроса нет. Подготовьте данные для бизнес-функции явным параметром. |
<?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/PHP на Windows — handler, CGI и FastCGI.
- Установка Apache/PHP на Unix — совместимость сборки и MPM.
Практическая последовательность: проверить синтаксис конфигурации средствами своей сборки Apache, загрузить её на тестовом окружении, открыть тестовый URL, убедиться в ожидаемом SAPI. Перезапуск production-сервера не является частью этих учебных примеров.
Настройки Apache-категории и исторические отличия
| Настройка | Стандарт и доступ | Смысл |
|---|---|---|
| engine | 1, INI_ALL | Разрешает обработку PHP модулем. engine off не является запретом HTTP-доступа к файлу: дальнейший handler может отдать его содержимое. |
| last_modified | 0, INI_ALL | Модуль добавляет Last-Modified по времени PHP-файла. Это не время изменения строк базы и не правильный автоматический кеш динамического заказа. |
| xbithack | 0, INI_ALL | В apache2handler8.5.11 обрабатывает как PHP text/html-ресурс с owner execute bit. Историческое обобщение «любой исполняемый файл» слишком широко. |
| child_terminate | 0, историческая 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 notes | apache_note и серверному журналу/модулям. | Текущий request, чтение в LogFormat через %{name}n. |
| subprocess_env | apache_getenv / apache_setenv и обработчикам Apache. | Таблица запроса, не автоматическое обновление $_SERVER/ОС. |
| $_SESSION | сессии приложения | Отдельное хранилище между запросами с собственной блокировкой и защитой. |
Из страницы браузер делает новые запросы за JS, CSS и картинками. Это самостоятельные HTTP-запросы; серверная заметка первого не размножается на них. Такой вопрос регулярно возникает в интеграциях журналов. Для доступа к учётной записи нужно приложение/сессия, а для журналов — безопасные отдельные метки; cookie с session ID не копируют в журнал ради удобства.
Когда нужен Apache subrequest, а когда достаточно PHP
- Фиксированный URI из настройки приложения
- apache_lookup_uri: определить ресурс и обработчик
- virtual: отдельный серверный подзапрос, исполнение и вывод
- Родительский 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/jsonHTTP-условие: клиент прислал Content-Type: application/json. Заголовок может отсутствовать: это обработано явно. Значение ещё не доказывает, что тело действительно корректный JSON.
<?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. Наличие метки не аутентифицирует клиента: любой отправитель может её поставить.
<?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-модуль. |
| Применение | Проверка переноса собственного сайта; не публикация конфигурации и не замена проверки пакетов администратора. |
<?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 читает текущее значение, как пропущенный второй аргумент. Это не удаление заметки. Пустая строка, напротив, записывается как значение.
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_top | false: текущий 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_top | false: текущий запрос; true: цепочка prev внутренних redirects. Это не процессное/постоянное глобальное окружение. |
| Результат | true при успехе, false при отказе; некорректные типы обрабатываются обычными правилами параметров PHP. |
| Версии | С PHP4.2.0, Apache-модуль. Значение в PHP $_SERVER само не изменяется. |
<?php
declare(strict_types=1);
$saved = apache_setenv('MANUAL_SECTION', 'catalog');
echo $saved ? "Метка установлена\n" : "Метка не установлена\n";
Ожидаемый результат
Метка установлена
В пределах текущего Apache-запроса. Следующий отдельный HTTP-запрос не обязан видеть эту отметку.
<?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-массива, и она не предназначена для сканирования любого адреса, присланного посетителем.
<?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.
<?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; это разные задачи. |
Рабочий процесс обслужил тяжёлый импорт. Аллокатор мог освободить объекты для повторного использования, но его процессная память не обязательно немедленно возвращается ОС. Историческая идея функции — попросить обслуживающий процесс уйти после текущего запроса, чтобы менеджер процессов создал свежий. Это не средство завершить только функцию или освободить отдельный массив.
<?php
declare(strict_types=1);
echo function_exists('apache_child_terminate')
? "API есть: требуется проверить режим сервера\n"
: "API в этом SAPI отсутствует\n";
Ожидаемый результат
API в этом SAPI отсутствует
Ожидание для CLI PHP8.5.11. Наличие функции в другом SAPI ещё не означает, что сервер исполняет нужный жизненный цикл.
<?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. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


