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

phpdotenv в PHP: .env, getenv(), секреты и 12 практических примеров

Настройки PHP через .env: переменные окружения, getenv(), проверка значений, типы и хранение секретов. Разбираем phpdotenv на 12 примерах с готовым проектом.

Александр Некрасов
phpdotenv — .env и настройки PHP: файл переменных окружения и замок

Адрес базы, режим отладки, таймаут API и ключ доступа меняются между компьютером разработчика, тестовым сервером и рабочим сайтом. Если хранить их прямо в PHP-коде, настройка и развёртывание быстро становятся неудобными. Библиотека phpdotenv загружает настройки из текстового файла .env, а приложение проверяет их и собирает собственную конфигурацию.

Два важных различия. Файл .env не шифрует секреты. А обычный Dotenv::createImmutable() загружает значения в $_ENV и $_SERVER, но не записывает их через putenv(). Поэтому ожидать, что после него getenv() обязательно увидит значения из файла, нельзя.
Выберите свой маршрут

Примеры рассчитаны на PHP 8.2+ и phpdotenv 5. Используется Composer-пакет vlucas/phpdotenv; официальный репозиторий сейчас находится в организации phpdotenv, но имя пакета в команде установки осталось прежним. Все ключи в учебном проекте вымышленные. Префикс PD_DEMO_ помогает отличать их от настроек ваших приложений.

1. Файл .env и переменные окружения

Что Где находится Для чего нужно
.env Текстовый файл проекта Хранит пары имя=значение; PHP сам его автоматически не читает
Окружение процесса Передаётся запускаемому процессу Позволяет задать параметры из оболочки, сервиса или платформы развёртывания
$_ENV Массив внутри PHP Содержит импортированные или записанные приложением значения
$_SERVER Массив внутри PHP Содержит сведения сервера и запроса; обычный phpdotenv также пишет туда свои ключи
getenv('NAME') Функция PHP Читает именованное значение окружения; при отсутствии возвращает false

Например, строка PD_DEMO_PORT=8080 в файле ещё ничего не меняет в работающем приложении. Сначала код загружает файл, затем проверяет значение и превращает строку "8080" в число. Файл, источник настройки и готовая типизированная конфигурация — разные этапы.

.env также не является php.ini. Запись display_errors=0 в такой файл сама по себе не меняет директиву PHP: приложение должно явно использовать соответствующее значение, если такая настройка предусмотрена.

Подробнее: PHP: $_ENV.

2. Установка и первый запуск

Если Composer ещё не установлен, откройте руководство по Composer: установка, зависимости и автозагрузка. В существующем PHP-проекте библиотека добавляется командой:

composer require "vlucas/phpdotenv:5.*"

Для практики по статье скачайте архив, распакуйте его и перейдите в папку с composer.json. Затем:

composer install
composer setup
composer first

setup — команда именно нашего учебного проекта, а не встроенная команда phpdotenv. Она создаёт .env из .env.example и не перезаписывает уже существующий файл. В архиве есть lock-файл, но нет vendor/; интернет нужен для первой установки зависимостей.

Содержимое учебного .env.example:

# Только демонстрационные значения. Не подставляйте сюда настоящие секреты.
PD_DEMO_APP_NAME="Учебный PHP-проект"
PD_DEMO_MODE=local
PD_DEMO_DEBUG=false
PD_DEMO_PORT=8080
PD_DEMO_RETRIES=0
PD_DEMO_API_TOKEN=demo-token-not-a-secret
PD_DEMO_BASE_DIR=var
PD_DEMO_UPLOAD_DIR="${PD_DEMO_BASE_DIR}/uploads"

Переименованный или скопированный .env хранит настройки конкретного окружения. Убедитесь, что редактор не добавил расширение .txt. Для файла удобно использовать UTF-8.

Полный first.php:

<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';

try {
    $dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
    $dotenv->load();
    $dotenv->required(['PD_DEMO_APP_NAME', 'PD_DEMO_MODE', 'PD_DEMO_API_TOKEN'])->notEmpty();
    $dotenv->required('PD_DEMO_MODE')->allowedValues(['local', 'test', 'production']);
    $dotenv->required('PD_DEMO_PORT')->isInteger();
    $dotenv->required('PD_DEMO_RETRIES')->isInteger();
    $dotenv->required('PD_DEMO_DEBUG')->isBoolean();
    $port = filter_var($_ENV['PD_DEMO_PORT'], FILTER_VALIDATE_INT, [
        'options' => ['min_range' => 1, 'max_range' => 65535],
    ]);
    if ($port === false) { throw new RuntimeException('Порт должен быть от 1 до 65535.'); }
    $retries = filter_var($_ENV['PD_DEMO_RETRIES'], FILTER_VALIDATE_INT, [
        'options' => ['min_range' => 0, 'max_range' => 5],
    ]);
    if ($retries === false) { throw new RuntimeException('Число повторов должно быть от 0 до 5.'); }
    $config = [
        'name' => $_ENV['PD_DEMO_APP_NAME'],
        'mode' => $_ENV['PD_DEMO_MODE'],
        'port' => $port,
        'retries' => $retries,
        'debug' => filter_var($_ENV['PD_DEMO_DEBUG'], FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE),
    ];
    echo json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL;
    echo "Секретная настройка задана; её значение не выводится." . PHP_EOL;
} catch (Dotenv\Exception\ExceptionInterface | RuntimeException $e) {
    fwrite(STDERR, 'Ошибка конфигурации. Проверьте .env и требования в first.php.' . PHP_EOL);
    exit(1);
}

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

Ожидаемый результат composer first с исходным шаблоном:

{
    "name": "Учебный PHP-проект",
    "mode": "local",
    "port": 8080,
    "retries": 0,
    "debug": false
}

После JSON появится строка «Секретная настройка задана; её значение не выводится.». Здесь порт и число повторов — числа, а debug — логическое false.

Сразу о getenv(). В этом примере источник значений — обычный createImmutable(). Для загруженных ключей используйте согласованное чтение из $_ENV. Если getenv() показывает false, это ещё не означает, что файл не прочитан: ниже есть отдельная проверка.
Как запускать остальные фрагменты?

Создайте рядом с composer.json файл experiment.php. Начало:

<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';

// Ниже добавьте один выбранный PHP-фрагмент.

Далее запускайте php experiment.php. Если пример содержит свой <?php, он уже является отдельным файлом — не вставляйте его второй раз после подготовки. Каждый запуск начинайте отдельной командой; не объединяйте демонстрации mutable/immutable и putenv в один процесс.

Подробнее: phpdotenv: установка.

3. Синтаксис .env: строки, кавычки и ссылки

Ниже — иллюстрация синтаксиса файла, а не PHP-код. Это сокращённый набор настроек: не заменяйте им весь .env.example из проекта, поскольку для first.php нужны и остальные обязательные поля.

# Комментарий на отдельной строке
PD_DEMO_TITLE="Учебный сайт"
PD_DEMO_DEBUG=false
PD_DEMO_PORT=8080
PD_DEMO_EMPTY=
PD_DEMO_LITERAL='Цена $5 и знак #'
PD_DEMO_BASE_DIR=var
PD_DEMO_UPLOAD_DIR="${PD_DEMO_BASE_DIR}/uploads"

Кавычки позволяют сохранить пробелы и специальные символы. В примере одинарные кавычки сохраняют буквальный текст, а ссылка ${PD_DEMO_BASE_DIR} в двойных подставляет ранее заданное значение. Поэтому PD_DEMO_UPLOAD_DIR становится var/uploads.

Числа и false пока остаются строками. PD_DEMO_EMPTY= означает пустое значение, а не отсутствующий ключ. Это важно для проверки required() и notEmpty().

Не воспринимайте .env как исполняемый shell-скрипт. phpdotenv разбирает свой формат, не запускает команды оболочки. Не используйте ручное подключение source .env как универсальную замену загрузчику PHP: правила и побочные эффекты будут другими.

Что делать с #, $, пробелами и многострочным значением?

Заключайте сложные строки в подходящие кавычки и проверяйте именно тем парсером, который использует приложение. Для точного буквального значения часто подходят одинарные кавычки. Большие сертификаты и ключи удобнее держать отдельными защищёнными файлами, передавая в конфигурации их путь, если это поддерживает ваш сервис. Не считайте форматы разных dotenv-библиотек полностью взаимозаменяемыми.

4. Чтение через $_ENV, $_SERVER и getenv()

Запустите после composer setup. Здесь показывается только название учебного приложения:

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

var_export([
    '_ENV' => $_ENV['PD_DEMO_APP_NAME'] ?? null,
    '_SERVER' => $_SERVER['PD_DEMO_APP_NAME'] ?? null,
    'getenv' => getenv('PD_DEMO_APP_NAME'),
]);

При отсутствии заранее заданной переменной процесса первые два поля содержат значение из файла, а getenv будет false. Если переменная уже передана процессу, результат getenv() может быть другим: он не обязан совпадать с тем, что вы только что записали в PHP-массив.

Нативное чтение с корректным значением по умолчанию:

$value = getenv('PD_DEMO_RETRIES');
$retriesText = $value === false ? '3' : $value;
echo $retriesText . PHP_EOL;

Проверка === false сохраняет строки "0" и "". Конструкция getenv('NAME') ?: 'default' заменит также строку "0", что может изменить смысл настройки. Оператор ?? тоже не решает эту задачу: он реагирует на null, а отсутствующий getenv возвращает false.

Почему $_ENV пустой, а getenv() видит значение?

Начальное заполнение суперглобальных массивов зависит от PHP/SAPI и variables_order. Буква E управляет импортом окружения в $_ENV; запись в массив самим phpdotenv — другая операция. Проверяйте окружение именно того PHP, который запускает приложение.

Можно ли читать любую настройку из $_SERVER?

Массив также содержит данные HTTP-запроса. Не используйте произвольные клиентские заголовки как источник секретных или административных настроек. Выберите явные имена конфигурации и определённый порядок источников.

Подробнее: PHP: getenv и false при отсутствии.

Подробнее: PHP: variables_order.

5. load() и safeLoad(): когда файл обязателен

Вызов Если файла нет Когда подходит
load() Ошибка чтения обязательного источника Приложение должно иметь конкретный файл настроек
safeLoad() Отсутствие файла не прерывает загрузку Настройки могут прийти из окружения без локального .env
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();

$dotenv->required('PD_DEMO_APP_NAME')->notEmpty();

safeLoad() не означает «проглотить любую проблему конфигурации». Ошибочный синтаксис и провал дальнейшей валидации остаются ошибками. В приведённом фрагменте отсутствие файла допустимо, но отсутствие обязательного значения — нет.

Для проверки этого поведения без изменения своего .env используйте composer demo -- missing. Сценарий работает с отдельным учебным путём.

6. Обязательные поля и валидация

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

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$dotenv->required(['PD_DEMO_APP_NAME', 'PD_DEMO_MODE'])->notEmpty();
$dotenv->required('PD_DEMO_PORT')->isInteger();
$dotenv->required('PD_DEMO_DEBUG')->isBoolean();
$dotenv->required('PD_DEMO_MODE')->allowedValues(['local', 'test', 'production']);
$dotenv->ifPresent('PD_DEMO_RETRIES')->isInteger();
Проверка Что означает
required() Ключ должен существовать
notEmpty() Значение не должно быть пустым
isInteger() Значение соответствует проверке целого числа
isBoolean() Значение допускается как булево представление
allowedValues() Значение входит в заданный список
ifPresent() Следующая проверка применяется при наличии ключа

Валидация не выполняет приведение типа. После isInteger() значение по-прежнему может быть строкой. Порт также требует диапазона 1–65535: синтаксически корректное целое число не обязательно подходит вашей задаче.

Веб-посетителю не нужно показывать полный текст исключения загрузчика: ошибка синтаксиса может включать фрагмент строки конфигурации. Для учебного CLI можно сообщить название не прошедшей проверки; на рабочем сайте используйте безопасное сообщение, а диагностические сведения контролируйте отдельно.

7. Типы: почему строка false превращается в true

Значения из обычного .env поступают как текст. В PHP непустая строка "false" при прямом приведении к bool становится true. Поэтому такой код включит флаг вопреки ожиданию:

var_export((bool) 'false'); // true
echo PHP_EOL;

Явное преобразование после проверки:

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$dotenv->required('PD_DEMO_DEBUG')->notEmpty()->isBoolean();
$dotenv->required('PD_DEMO_PORT')->notEmpty()->isInteger();

$debug = filter_var(
    $_ENV['PD_DEMO_DEBUG'],
    FILTER_VALIDATE_BOOLEAN,
    FILTER_NULL_ON_FAILURE
);
$port = filter_var(
    $_ENV['PD_DEMO_PORT'],
    FILTER_VALIDATE_INT,
    ['options' => ['min_range' => 1, 'max_range' => 65535]]
);
if ($debug === null || $port === false) {
    throw new RuntimeException('Некорректные типы настроек.');
}
$config = ['debug' => $debug, 'port' => $port];
echo json_encode($config, JSON_THROW_ON_ERROR) . PHP_EOL;

С учебным файлом получите {"debug":false,"port":8080}. Проверка === false для числа позволяет отличать ошибку от допустимого нуля там, где диапазон разрешает 0. Здесь порт 0 специально недопустим.

Как быть со списками и JSON в одной переменной?

Формат выбирает приложение. Для небольшого списка можно договориться о разделителе и разобрать строку через explode(), затем очистить и проверить элементы. Для JSON используйте json_decode(..., JSON_THROW_ON_ERROR) и проверку структуры. Загрузчик .env сам не превращает такую строку в PHP-массив.

Подробнее: PHP: filter_var.

Подробнее: PHP: преобразование в bool.

8. Immutable и mutable: кто может заменить значение

createImmutable() сохраняет уже существующие значения, которые видит настроенное хранилище библиотеки. Покажем это явно через PHP-массивы. Фрагмент запускайте отдельно:

$_ENV['PD_DEMO_MODE'] = 'production';
$_SERVER['PD_DEMO_MODE'] = 'production';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
echo $_ENV['PD_DEMO_MODE'] . PHP_EOL; // production

В .env стоит local, но существующее значение сохранилось. Теперь отдельный запуск альтернативного варианта:

$_ENV['PD_DEMO_MODE'] = 'production';
$_SERVER['PD_DEMO_MODE'] = 'production';

$dotenv = Dotenv\Dotenv::createMutable(__DIR__);
$dotenv->load();
echo $_ENV['PD_DEMO_MODE'] . PHP_EOL; // local

Mutable допускает замену значений при загрузке. Оба варианта читают файл; ни один не переписывает его содержимое. Immutable также не делает $_ENV неизменяемым для остального PHP-кода.

Важна видимость источника. Обычная конфигурация phpdotenv работает с $_ENV/$_SERVER. Если значение существует только для getenv() и не представлено в читаемых адаптерами массивах, нельзя считать, что обычный immutable автоматически защитил именно его. Проверяйте порядок источников в целевом окружении и используйте единый способ чтения конфигурации.

9. Совместимость с getenv() и putenv()

Некоторым старым библиотекам нужна именно getenv(). Для такого режима phpdotenv предоставляет отдельный opt-in:

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

echo getenv('PD_DEMO_APP_NAME') . PHP_EOL;

В свежем учебном CLI-процессе название приложения теперь доступно и через getenv. Unsafe в имени связан с использованием putenv(): документация библиотеки предупреждает о потокобезопасности такого подхода. Не включайте этот режим просто ради устранения неожиданного false, если приложение может читать подготовленную конфигурацию без него.

Нативный putenv без загрузчика. Отдельный учебный пример меняет только вымышленный ключ и восстанавливает прежнее значение:

$name = 'PD_DEMO_TEMP_VALUE';
$previous = getenv($name);
try {
    if (!putenv($name . '=hello')) {
        throw new RuntimeException('Не удалось установить учебное значение.');
    }
    echo getenv($name) . PHP_EOL;
} finally {
    putenv($previous === false ? $name : $name . '=' . $previous);
}

Это не постоянная настройка операционной системы и не команда изменения всех PHP workers. Для веб-запроса изменение ограничено его жизненным циклом; в долгоживущем CLI-процессе учитывайте время жизни самого процесса. Запись через putenv также не синхронизирует автоматически все PHP-массивы.

Что означает getenv($name, true)?

Параметр local_only=true просит значение локального окружения процесса, установленное ОС или putenv, без предпочтения SAPI-значения. Это бывает важно в FastCGI. Он не заставляет getenv читать $_ENV и не загружает файл .env.

Подробнее: phpdotenv: режим getenv/putenv.

Подробнее: PHP: область действия putenv.

10. Разобрать настройки без изменения окружения

Если требуется только получить массив из текста, можно вызвать Dotenv::parse(). Это удобно в тестах и для собственного слоя конфигурации:

$text = "PD_DEMO_LABEL=\"Тестовый профиль\"\nPD_DEMO_LIMIT=10\n";
$values = Dotenv\Dotenv::parse($text);

echo $values['PD_DEMO_LABEL'] . PHP_EOL;
var_export($values['PD_DEMO_LIMIT']); // '10' — строка

Метод вернёт разобранные значения и не выполнит обычную загрузку в $_ENV, $_SERVER или putenv. В архиве это отдельно проверяется. После разбора всё равно нужны правила обязательных полей, типов и допустимых значений.

Правильный путь к файлу. При загрузке указывайте каталог относительно известной точки входа, а не текущей папки терминала:

<?php
declare(strict_types=1);

// Файл project/config/bootstrap.php:
$projectRoot = dirname(__DIR__);
require $projectRoot . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable($projectRoot);
$dotenv->load();

Этот фрагмент предназначен для указанного расположения файла, а не для корневого experiment.php. Корень проекта и публичный каталог веб-сервера обычно полезно разделять.

Автоматически ли читаются .env.local и .env.production?

Нет, не рассчитывайте на поведение конкретного фреймворка в обычном phpdotenv. Для другого имени передайте его явно: Dotenv\Dotenv::createImmutable(__DIR__, '.env.testing'). Если используете несколько источников, определите их порядок и правила замены; список имён файлов не следует без проверки считать автоматическим объединением всех слоёв.

11. Хранение секретов: .env, Git и доступ

.env — обычный текст. Любой, кто может прочитать файл, получит его содержимое. Библиотека отделяет конфигурацию от исходного кода, но не заменяет управление доступом или хранилище секретов.

Задача Что сделать
Передать структуру настроек команде Хранить в Git .env.example с именами полей и вымышленными значениями
Не коммитить реальные значения Исключить локальные .env-файлы и проверить, не отслеживались ли они раньше
Закрыть доступ посетителям Держать файл вне document root и проверить правила публикации файлов
Ограничить чтение на сервере Дать необходимые права сервисному пользователю; учитывать резервные копии и артефакты сборки
Не раскрыть секрет при диагностике Не выводить всё окружение, phpinfo, полный config или сырой текст исключения загрузчика
Управлять рабочими ключами Использовать механизм секретов платформы или отдельное хранилище с ротацией и контролем доступа

Пример правил .gitignore для корня проекта:

/.env
/.env.*
!/.env.example
/vendor/

Проверьте поведение правил и отслеживание файла:

git check-ignore -v .env
git ls-files -- .env
Файл уже попал в Git. Достаточно ли добавить .gitignore?

Нет: правила ignore не прекращают отслеживание уже добавленного файла. Убрать его из индекса, сохранив локально, можно командой ниже; затем изменение фиксируют в репозитории:

git rm --cached -- .env

Эта команда не удаляет секрет из старой истории. Если настоящий ключ стал доступен посторонним, отзовите или замените его у поставщика; очистку истории и копий выполняйте отдельно по принятой процедуре. Удаление строки из последнего коммита не отменяет раскрытие.

Не добавляйте настоящий секрет в пример .env.example даже «на время». В учебном архиве токен явно демонстрационный, в выводе он не показывается. В рабочем проекте переменные окружения тоже требуют защиты: диагностические страницы, дампы и процессы с соответствующими правами могут их раскрывать.

Подробнее: Git: ignore и уже отслеживаемые файлы.

Подробнее: OWASP: управление секретами.

12. Development, production и единая конфигурация

Для локальной разработки удобен файл, созданный из шаблона. На рабочем сервере настройки может передавать система запуска, контейнерная платформа или хранилище секретов. Наличие файла .env в production не является обязательным правилом.

Задайте один понятный порядок источников. Например, если ваша платформа передаёт значения через getenv, можно читать именно их первыми, а загруженный локальный файл использовать как запасной источник. Пример ниже показывает такой договор для одного несекретного поля:

Dotenv\Dotenv::createImmutable(__DIR__)->safeLoad();

function configText(string $name, ?string $default = null): ?string
{
    $fromProcess = getenv($name);
    if ($fromProcess !== false) {
        return $fromProcess;
    }
    return $_ENV[$name] ?? $default;
}

$mode = configText('PD_DEMO_MODE', 'local');
if (!in_array($mode, ['local', 'test', 'production'], true)) {
    throw new RuntimeException('Некорректный режим приложения.');
}
echo $mode . PHP_EOL;

Это явная политика нашего кода, а не скрытое поведение phpdotenv. В такой схеме проверяйте уже итоговое выбранное значение: валидация массива загрузчика не заменяет проверку другого источника. Обязательному секрету не назначайте рабочее значение «по умолчанию» из примера.

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

  • CLI и веб-процесс: переменная в вашем терминале не гарантирует её наличие у PHP-FPM или другого сервиса. Проверяйте целевой процесс, а не только командную строку.
  • Фреймворк: используйте его штатную загрузку и конфигурационный слой. Повторное подключение загрузчика может дать другой порядок значений.
  • Долгоживущие workers и кэш: изменение файла не обновляет уже созданный объект конфигурации само по себе. Применяйте предусмотренную процедуру обновления/перезапуска.
  • Проверка при запуске: отсутствующий ключ или недопустимый порт лучше обнаружить до обработки пользовательской операции.

Практика по соседним темам: Guzzle — настройки HTTP-запросов и таймауты, Monolog — журнал событий без вывода секретов посетителю.

Подробнее: PHP-FPM: конфигурация окружения.

13. Готовый проект: 12 практических сценариев

Скачать примеры phpdotenv — ZIP

В архиве исходники, .env.example, .gitignore, README и lock-файл. Рабочий .env и vendor/ в архив не включены. Команды выполняйте из распакованной папки проекта:

composer install
composer setup
composer first
composer demo -- list
composer demo -- all
Сценарий Команда Что проверяет
Загрузка composer demo -- load Значения в $_ENV и $_SERVER
Нет файла composer demo -- missing Различие load и safeLoad
Обязательные поля composer demo -- required Отсутствие и пустое значение
Целое число composer demo -- integer Допустимое и ошибочное представление
Булево значение composer demo -- boolean Проверку и преобразование false
Список вариантов composer demo -- allowed Допустимые режимы приложения
Синтаксис composer demo -- syntax Кавычки, комментарии и подстановку
Immutable composer demo -- immutable Сохранение заранее заданного значения
Mutable composer demo -- mutable Разрешённую замену значения
Обычный getenv composer demo -- getenv Отсутствие автоматического putenv
Совместимость getenv composer demo -- unsafe-getenv Явный режим с PutenvAdapter
Разбор текста composer demo -- parse Массив без загрузки в окружение

Каждый сценарий выполняется в отдельном PHP-процессе и создаёт свой файл var/run-…/sample.env. Корневой .env сценарии не меняют. Так изменения учебных суперглобальных массивов и putenv не переносятся в соседний пример. Системное окружение машины и php.ini проект не меняет.

Ожидаемая ошибка может означать успешную проверку. Если сценарий специально подал неправильное число, получил ожидаемое исключение и проверил его, самопроверка вернёт 0. Код 1 означает расхождение с ожидаемым поведением. Сценарии не запрашивают настоящие токены и не отправляют их внешним сервисам.

Проверено 6 ноября 2026 года: PHP 8.3.33, Composer 2.10.3, Windows, vlucas/phpdotenv 5.8.0. Все 12 сценариев прошли в отдельных PHP-процессах. Проверены загрузка, отсутствие файла, синтаксис, обязательные значения, типы и диапазон порта, сохранение/замена настроек, различия с getenv() и разбор строки без изменения окружения. Первый пример формирует ожидаемые int/bool; повторный setup не перезаписывает существующий .env. Отсутствующий файл и недопустимый порт дают ошибку без вывода токена.

Все 15 PHP-блоков статьи проверены на синтаксис и выполнены с указанной подготовкой; оба примера .env проверены парсером библиотеки. Всего для материала выполнено 39 проверок в отдельных PHP-процессах, включая ошибочные входные значения, сохранение строк «0» и «», приоритет getenv, восстановление временной переменной и путь config/bootstrap.php.

Исходные PHP-файлы прошли проверку синтаксиса, зависимости — composer validate --strict, composer check-platform-reqs и composer audit --locked. Известных advisories на момент проверки не обнаружено. Проверки CLI не подтверждают настройки PHP-FPM/Apache, потокобезопасность, публичную недоступность .env на вашем сервере или работу системы доставки секретов.

14. Частые ошибки и диагностика

Unable to read any of the environment file(s)

Проверьте запуск composer setup, точное имя .env, каталог в createImmutable() и права чтения. __DIR__ относится к файлу PHP, где написан код, а не к папке, из которой вы открыли терминал.

$_ENV содержит значение, но getenv() возвращает false

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

Переменная есть, но значение не обновилось

Проверьте существующее значение в хранилище, immutable/mutable, выбранный файл, кэш конфигурации и жизненный цикл worker. Убедитесь, что читаете тот источник, который обновили.

DEBUG=false, а отладка включена

Не приводите строку напрямую через (bool). Выполните проверку и явное преобразование через FILTER_VALIDATE_BOOLEAN, затем используйте полученный bool.

Вместо 0 подставилось значение по умолчанию

Проверьте сокращённый оператор ?: и empty(). Строка "0" может считаться ложной; для отсутствующего getenv проверяйте строго === false.

safeLoad сработал, но приложение всё равно падает

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

В терминале всё работает, а на сайте переменных нет

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

Файл игнорируется Git, значит секрет защищён?

Ignore помогает избежать нового коммита, но не ограничивает чтение файла, публикацию по HTTP, попадание в резервную копию или уже существующую историю Git. Эти задачи решаются отдельно.

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

php --version
php --ini
composer show vlucas/phpdotenv
composer check-platform-reqs
composer diagnose
composer audit

15. Источники и следующий шаг

Для закрепления измените демонстрационный порт и режим, запустите первый пример, затем подставьте недопустимое значение и посмотрите, на какой проверке оно отклоняется. После этого вынесите одну настройку своего приложения в единый конфигурационный слой — с ясным источником, проверкой типа и понятным поведением при отсутствии.