Александр Некрасов · Автономное руководство

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

Разделы этой страницы
  1. Запуск в production: PHP-FPM, релизы, cron и очереди
  2. Почему работающий локально файл ещё не готовый релиз
  3. Локальный сервер и production решают разные задачи
  4. Собираем артефакт, проверяем, переключаем
  5. Фоновая задача должна уметь повторяться и останавливаться
  6. У долгоживущего процесса другая память
  7. Релиз проверяется вместе с данными и очередью
  8. Практика: проверьте решение, а не только успешный путь
  9. Какие данные оставить в техническом журнале
  10. Функции и методы этой темы
  11. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

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

Запуск в production: PHP-FPM, релизы, cron и очереди

Как перенести работающий код на сервер и сохранить управляемость при сбоях.

Почему работающий локально файл ещё не готовый релиз

На своём компьютере вы запускаете PHP вручную. На сервере запросы принимаются постоянно, конфигурация отличается, а ошибка видна посетителям. Развёртывание переносит проверенный код вместе с зависимостями и настройками в это окружение.

Что уже пригодится. зависимости, проверки

Что переносим и проверяем
  1. Исходный код и composer.lock
  2. Установка нужных зависимостей
  3. Конфигурация окружения отдельно от кода
  4. Проверки и совместимые миграции
  5. Переключение и наблюдение

Секреты не записывают в публичные PHP-примеры или Git. getenv читает значение из окружения процесса и возвращает строку либо false, если переменной нет. Даже конфигурацию проверяют по типу и допустимым значениям.

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

function readDebugFlag(string|false $value): bool
{
    return $value === '1';
}
foreach (['1', '0', false] as $value) {
    echo readDebugFlag($value) ? "debug on\n" : "debug off\n";
}
Ожидаемый результат
debug on
debug off
debug off

Здесь явно заданы возможные результаты getenv('APP_DEBUG'). false означает отсутствие настройки; строка "false" тоже не включила бы режим. В production ошибки записывают в защищённый журнал, а не показывают посетителям.

Следующая ступень. Ниже разбираются PHP-FPM, права, OPcache, миграции, воркеры и проверяемый откат. Копирование файлов — лишь один шаг.

Локальный сервер и production решают разные задачи

Наглядная модель

Новый код должен попасть и в сайт, и в workers

Зелёные тесты ещё не означают обновлённый сервис. Важны окружение, миграции и процессы, которые уже работают.

  1. Собрать один артефакт

    Код, зависимости из lock-файла и версия собираются вместе. Этот же артефакт проходит проверки.

  2. Проверить окружение

    Версия PHP, расширения, конфигурация FPM, права каталогов и совместимые миграции данных.

  3. Переключить релиз

    Сайт начинает использовать проверенный код; порядок миграции согласован со старой и новой версиями.

  4. Обновить долгоживущие workers

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

  5. Проверить реальный сценарий

    Запрос, запись в БД, очередь и журнал ошибок показывают, что сервис работает после обновления.

Откат кода сам по себе не откатывает данные. Встроенный php -S удобен для разработки, но не заменяет production-настройку.

Встроенный php -S удобен для разработки и ручной проверки маршрутов. Для публичной эксплуатации нужен поддерживаемый веб-сервер и правильно настроенный PHP runtime, часто PHP-FPM. Веб-сервер принимает соединения и отдаёт статику, FPM управляет рабочими процессами PHP. Доступность сайта зависит не только от синтаксиса: важны права каталогов, расширения, CA-сертификаты, конфигурация сессий и соединения с базой.

Разделите dev, test, staging и production по данным и секретам. В production display_errors выключен, log_errors включён, журнал закрыт от веб-доступа. Документный корень указывает на public, а не корень репозитория. У пользователя процесса минимальные права: исходники читаются, а запись разрешена только в специально выделенные storage/cache/session каталоги. Загруженные файлы не должны становиться исполняемыми скриптами.

Версию PHP, расширения и настройки фиксируют в описании окружения. php --ini и php -m для CLI не доказывают конфигурацию FPM: это могут быть разные ini и даже разные версии. Проверяйте рабочий SAPI безопасным диагностическим способом, не оставляя публичный phpinfo с секретной конфигурацией. После проверки диагностический endpoint удаляют или закрывают.

Собираем артефакт, проверяем, переключаем

Проверки сборки перед выкладкойФрагмент или команда
php -v
php --ini
php -m
composer validate --strict
composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction
composer check-platform-reqs
php -l public/index.php
# После этого: тесты проекта, smoke-test собранного артефакта,
# контролируемая миграция и переключение релиза по процедуре сервера.

Команды приведены для проекта с Composer, не выполняются автоматически. php -l проверяет один файл, поэтому сборка должна охватывать все PHP-файлы. Секреты и реальные адреса не включены.

Хороший релиз устанавливает проверенный артефакт с lock-файлом, а не редактирует рабочие файлы по одному. Переключение каталога или эквивалентный механизм уменьшает период смешения старого и нового кода. Миграции схемы должны быть совместимы с порядком переключения: сначала добавление, затем переход к использованию, позднее удаление старого. Откат кода не всегда способен откатить уже изменённые данные.

Проверяйте резервную копию восстановлением в изолированной среде. Наличие файла backup.zip не доказывает, что в нём полная база и нужные файлы. После релиза выполняют короткий smoke-test чтения и безопасного тестового сценария, смотрят ошибки, latency и очередь. При отключённой проверке timestamp OPcache нужно предусмотренное обновление кеша/процессов, иначе сервер продолжит выполнять старый код.

Фоновая задача должна уметь повторяться и останавливаться

Защита локальной cron-задачи от наложенияСамостоятельный пример
<?php
declare(strict_types=1);

$handle = fopen('import.lock','c');
if ($handle === false) throw new RuntimeException('Cannot open lock');
try {
    if (!flock($handle,LOCK_EX|LOCK_NB)) {
        echo "already-running
";
    } else {
        try { echo "one import batch
"; }
        finally { flock($handle,LOCK_UN); }
    }
} finally {
    fclose($handle);
}
Ожидаемый результат
one import batch

Пример создаёт lock только в изолированном рабочем каталоге. flock подходит для процессов с общим поддерживаемым файловым хранилищем и одинаковым протоколом блокировки; это не распределённый lock для нескольких независимых серверов.

Cron запускает команду по расписанию, но не ведёт автоматически историю задания и не предотвращает наложение. Укажите абсолютные пути к PHP, конфигурации и рабочему каталогу, сохраняйте exit code и безопасный журнал. Если выполнение дольше интервала, нужна блокировка или очередь. Отсутствие вывода не означает успех: команда должна завершаться корректным кодом и фиксировать результат.

Очередь хранит задания, воркер резервирует их на ограниченное время, выполняет и подтверждает. Падение до подтверждения приводит к повтору, поэтому обработчик идемпотентен. Число попыток ограничивают, проблемные задания переводят в отдельное состояние для разбора. Lease/visibility timeout должен учитывать длительность работы, иначе два воркера начнут одно задание. Heartbeat или продление аренды требуют отдельного протокола.

У долгоживущего процесса другая память

CLI-воркер обрабатывает много заданий одним процессом. Статические свойства, кеши и singleton-объекты живут между ними. То, что автоматически очищалось в конце HTTP-запроса, теперь может накапливаться или хранить данные предыдущего клиента. После каждого задания освобождайте крупные ссылки, сбрасывайте контекст пользователя и проверяйте транзакции. Периодический перезапуск по числу задач/памяти полезен, но не заменяет поиск утечки.

Сигнал завершения должен останавливать приём новых заданий и позволять текущему безопасно закончиться либо вернуть его в очередь. После релиза воркеры перезапускают, чтобы они загрузили новый код. Максимальные времена PHP, FPM, прокси, HTTP-клиента и очереди должны быть согласованы; бесконечный set_time_limit(0) не создаёт надёжный процесс сам по себе.

Мониторинг разделяет здоровье приложения и готовность обслуживать запросы. Простая страница 200 может работать при сломанной базе. Проверки зависимостей делают с короткими таймаутами и без изменения данных, а подробности не раскрывают публично. Наблюдайте возраст старейшего задания, число повторов, заполнение диска, ошибки соединений и память воркеров.

Релиз проверяется вместе с данными и очередью

Перед переключением оцените совместимость старых заданий с новым кодом. В очереди может лежать сообщение прежней версии схемы, а воркер после релиза получит его через час. Версионируйте формат сообщения и поддерживайте разумное переходное окно. Не сериализуйте произвольный PHP-объект как долгосрочный контракт: изменение имени класса или свойств усложнит восстановление.

Health-check не должен создавать заказ, отправлять письмо или выполнять дорогой полный отчёт. Он проверяет минимально достаточную готовность с коротким бюджетом. Глубокая диагностика доступна только обслуживающему персоналу. При падении зависимости важно не раскрыть пароль DSN в публичном ответе и не заставить балансировщик ещё сильнее нагружать уже больной сервис.

План отката включает код, конфигурацию, миграции, кеш и воркеры. Для удаления столбца или преобразования данных одного переключения symlink недостаточно. Используйте совместимые этапы, резервную копию и заранее проверенную процедуру восстановления. После аварийного отката отдельно проверьте отложенные события: они могли быть созданы новой версией и требуют совместимого обработчика.

Практика: проверьте решение, а не только успешный путь

После релиза веб-страницы обновились, но очередь продолжает отправлять старый шаблон письма. Почему и что входит в исправление?

Разбор решения

Долгоживущие воркеры уже загрузили старый код. Нужен управляемый перезапуск с завершением текущих заданий, затем проверка нового процесса и тестового сообщения в безопасном транспорте. Одного обновления файлов и OPcache веб-SAPI недостаточно.

Какие данные оставить в техническом журнале

Полный контракт и режимы записи: error_log.

В обычном режиме 0 отправляет сообщение настроенному обработчику PHP. Возвращаемый bool не является гарантией наблюдаемой доставки во всех режимах.

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

function logOrderFailure(string $requestId,int $orderId): void {
    error_log(json_encode(['event'=>'order_failed','requestId'=>$requestId,'orderId'=>$orderId],JSON_THROW_ON_ERROR));
}

Определение без записи в журнал. Идентификаторы должны быть проверены, а политика персональных данных согласована.

Классификация сбоя без текста SQLПример для указанного окружения
<?php
declare(strict_types=1);

function logDatabaseFailure(Throwable $error): void {
    error_log(json_encode(['event'=>'database_failure','class'=>$error::class],JSON_THROW_ON_ERROR));
}

Это минимальный безопасный контекст. Закрытый диагностический слой может хранить больше с редактированием секретов.

Рядом по задаче. PSR-3 logger удобнее для структурированного контекста; HTTP-пользователь получает request_id, а не трассировку.

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

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

getenvПрочитать одну переменную окружения или карту окружения текущего процесса.

PHP 4–8. local_only добавлен в 7.0.9; чтение всей карты без name — в 7.1; name nullable с 8.0. · standard

getenv(?string $name = null, bool $local_only = false): array|string|false

Прочитать одну переменную окружения или карту окружения текущего процесса.

Окружение часто передаёт endpoint, имя очереди и режим запуска в CLI/container. Без name функция возвращает массив, который способен содержать секреты; обычный код читает один белый список ключей. В FastCGI SAPI может подменять значения окружения: local_only=true просит только локально установленное ОС/putenv значение.

Первый запуск и развитие задачи

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

putenv('PHP_MANUAL_REGION=eu-test');
echo getenv('PHP_MANUAL_REGION', true), "\n";
putenv('PHP_MANUAL_REGION');
var_dump(getenv('PHP_MANUAL_REGION', true));
Ожидаемый результат
eu-test
bool(false)

Строка NAME=value меняет окружение процесса, NAME без = удаляет переменную. Значение не записывается в php.ini и не является безопасным хранилищем секрета.

Отличить отсутствующую переменную от пустойСамостоятельный пример
<?php
declare(strict_types=1);

putenv('PHP_MANUAL_MISSING');
$value = getenv('PHP_MANUAL_MISSING', true);
var_dump($value);
putenv('PHP_MANUAL_EMPTY=');
var_dump(getenv('PHP_MANUAL_EMPTY', true));
putenv('PHP_MANUAL_EMPTY');
Ожидаемый результат
bool(false)
string(0) ""

Строгое сравнение с false сохраняет пустую, но существующую строку. Это важно для флагов, где пустое значение может иметь отдельный смысл.

Полный договор вызова

ПараметрЧто передаём и зачем
nameИмя переменной или null для полного массива. Пустое имя не заменяет null.
local_onlyПри строковом name: true игнорирует значение, внесённое SAPI, и читает ОС/putenv; особенно важно при FastCGI. При name=null этот аргумент не влияет на результат.
Часть договораЧто получает программа
Сигнатураgetenv(?string $name = null, bool $local_only = false): array|string|false
Результатstring для существующего имени, false при отсутствии, array при name=null.
Ошибки и особые значенияПустая строка — существующее значение, поэтому проверяйте === false. Полный массив нельзя бездумно писать в лог/ответ.
ВерсииPHP 4–8. local_only добавлен в 7.0.9; чтение всей карты без name — в 7.1; name nullable с 8.0.
Официальный диапазон узла: (PHP 4, PHP 5, PHP 7, PHP 8).
СредаВсе SAPI, если ниже не указано иное.

Более простой или безопасный выбор. Конфигурационный слой приложения валидирует отдельные ключи и передаёт типизированные значения дальше.

Рядом: putenv, php_sapi_name.

Вернуться к карте задач или полному дереву функций.

Для имени возвращает строку либо false, если переменная отсутствует; без имени — массив. Не следует печатать всё окружение в диагностике.

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

function requiredEnvironment(string $name): string {
    $value=getenv($name);
    if ($value===false || $value==='') throw new RuntimeException('Missing configuration: '.$name);
    return $value;
}

Определение не читает реальные секреты. В журнале допустимо имя настройки, но не значение.

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

function environmentFlag(string $name): bool {
    $raw=getenv($name);
    if ($raw===false) return false;
    $value=filter_var($raw,FILTER_VALIDATE_BOOL,FILTER_NULL_ON_FAILURE);
    if ($value===null) throw new RuntimeException('Invalid boolean configuration');
    return $value;
}

Не использует (bool) "false", которое дало бы true. Конфигурация валидируется при запуске.

Рядом по задаче. Явный объект конфигурации проверяет типы один раз в точке сборки; filter_var помогает boolean-значениям.

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

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

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

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

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