11. Config

На кроках 1–10 значення (APP_DEBUG, MySQL, рівень логів, masking) жили в getenv() і літералах у ApplicationStackBootstrap — навмисно, щоб було видно джерело правди. Коли параметрів стає багато, Bootstrap перетворюється на «конфіг-файл на PHP». Крок 11 виносить значення у config/ і .env; шляхи ($rootPath . 'storage/…') лишаються до кроку 12 (PathManager).

Що додається

  • config/*.php — п'ять файлів під contact-app (лише те, що вже використовує Stack)
  • ConfigBootstrap + ConfigServiceProvider
  • ConfigKey + AppConfig — ключі і готові масиви для Stack
  • ApplicationRuntimeBootstrap — timezone з config
  • .env.example — для локального/Docker override

composer.json

ConfigServiceProvider живе в extension-config. Stack не підтягує його транзитивно — додаток реєструє config сам, коли з’являється config/.

{
    "require": {
        "php": "^8.4",
        "php-concept/core-2": "^1.0",
        "php-concept/stack": "^1.0",
        "php-concept/extension-config": "^1.0",
        "php-concept/extension-json": "^1.0"
    }
}

config/

Extension завантажує усю директорію і мерджить файли. У рецепті — мінімальний набір; у великому додатку з’являться інші домени (session, view, events…).

  • app.php — name, version, timezone, debug
  • db.php — connection, migrations table, seeders, query log
  • log.php — Monolog channel/level/file
  • validator.php — diagnostic validation log (крок 9)
  • masking.php — patterns і key_patterns (крок 8)

.env: APP_DEBUG=1 → app.debug, APP_ENV=dev → підвантаження config/dev/ (overlay; у рецепті такої директорії поки немає — ключ лишається для майбутнього), DB_HOST=concept_recipes_db → db.host. Extension мапить перший сегмент імені змінної (до _) на корінь ключа: тому DB_*, а не MYSQL_* (останнє дало б mysql.host, не db.host). Файл .env обов’язковий — змінні лише в docker-compose environment: Config не підхопить.

ConfigKey + AppConfig

ConfigKey — константи ключів (glue додатку). AppConfig — лише складні масиви для builder-ів (dbConnection(), masking, seeders), щоб не дублювати десятки рядків у Bootstrap. Скаляри (debug, рівень логу) читаємо напряму: $config->getBool(ConfigKey::APP_DEBUG) — без однорядкових обгорток у AppConfig.

$config = ContainerDependency::get($container, ConfigInterface::class);
$app = new AppConfig($config);
$debug = $config->getBool(ConfigKey::APP_DEBUG);

$stack->addDatabase()->setConnection($app->dbConnection());

bootstrap/app.php

$root = dirname(__DIR__);

(new ConfigBootstrap($root))->boot($container);
(new ApplicationStackBootstrap())->boot($container);
(new ApplicationRuntimeBootstrap())->boot($container);

Запуск і перевірка

Тег step-11-config. Поведінка як на кроці 10; змінилось лише джерело значень.

Запуск

git checkout step-11-config
cp .env.example .env   # обов’язково: Config читає файл .env
rm -rf storage/cache storage/sessions storage/logs
composer install
cp docker-compose.yml.example docker-compose.yml
docker compose up -d --build

Перевірка

  1. /contact і /api/stats — як на кроці 10.
  2. Змініть config/app.php → debug або APP_DEBUG у Compose — Whoops on/off.
  3. Змініть config/db.php → host на невірний — очікувана помилка з’єднання (поверніть назад).

Далі — 12. PathManager: іменовані шляхи замість $rootPath у Bootstrap.