База данных · Конфигурация

Конфигурация БД

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

Пакет flytachi/winter-ppaПоверх winter-cdoДрайверы PostgreSQL · MySQL · SQLite

Почему классом, а не массивом

Массив в config/database.php — привычная форма, но она ничего не знает о себе: опечатку в ключе видно в рантайме, а «какие вообще базы есть в проекте» приходится искать глазами.

Класс решает три задачи разом: имя класса становится идентификатором базы, на который ссылается репозиторий; сканер находит все конфигурации сам, поэтому миграции и health-проверки не требуют реестра; свойства типизированы, и $port строкой не окажется.

Объявление

main/Configurations/MainDbConfig.php
<?php

namespace Main\Configurations;

use Flytachi\Winter\Cdo\Config\PgDbConfig;

class MainDbConfig extends PgDbConfig
{
  public function setUp(): void
  {
      $this->host     = env('DB_HOST', 'localhost');
      $this->port     = (int) env('DB_PORT', 5432);
      $this->database = env('DB_NAME', 'app');
      $this->username = env('DB_USER', 'postgres');
      $this->password = env('DB_PASS', '');
  }
}

setUp() вызывается один раз на каждый создаваемый экземпляр — то есть на каждое соединение в пуле. Читать окружение здесь нормально; ходить в сеть или в базу — нет.

Генератор

php call make -C Main создаёт заготовку в main/Configurations/. Класс, попавший в проект, дальше находит сканер — регистрировать его нигде не нужно.


Справочник

Базовые классы

Выбор базового класса — это выбор драйвера. Набор свойств у них разный, потому что разные базы принимают разное.

PgDbConfig

Свойство Тип По умолчанию Что задаёт
$host string localhost адрес сервера
$port int 5432 порт
$database string postgres имя базы
$username string postgres пользователь
$password string '' пароль
$schema string public схема по умолчанию
$charset ?string null кодировка соединения
$sslmode string disable режим TLS: require, verify-full, …
text
pgsql:host=db;port=5432;dbname=app;sslmode=disable;

$sslmode по умолчанию disable — это удобно локально и неверно для продакшена: трафик до базы пойдёт открытым. В управляемых базах обычно нужен как минимум require.

MySqlDbConfig

Свойство Тип По умолчанию Что задаёт
$host string localhost адрес сервера
$port int 3306 порт
$database string '' имя базы
$username string root пользователь
$password string '' пароль
$charset ?string null кодировка; для эмодзи нужен utf8mb4
text
mysql:host=db;port=3306;dbname=app;charset=utf8mb4;

Схемы у MySQL нет: там база и есть пространство имён, поэтому getSchema() вернёт null.

SqliteDbConfig

Свойство Тип По умолчанию Что задаёт
$path string :memory: путь к файлу или :memory:
text
sqlite:/var/app.sqlite

Ни сервера, ни учётных данных — поэтому в тестах он и удобен. :memory: живёт ровно столько, сколько соединение.

DbConfig

Общий базовый класс: те же свойства плюс $driver, который вы задаёте сами. Нужен для драйвера, под который отдельного класса нет.

Общие свойства

Свойство Тип По умолчанию Что задаёт
$isPersistent bool false постоянное соединение PDO

`$isPersistent` и пул — разные вещи, и вместе не нужны

Постоянное соединение PDO переживает запрос внутри процесса PHP, и придумано оно для FPM, где процесс иначе закрывал бы сокет каждый раз. У резидентного воркера соединения и так живут — этим занимается пул, и он же их проверяет и обновляет.

Включив оба, вы получаете соединение, которое пул считает своим, а PDO — своим, и закрытие одним не видно другому.

Трейты

PpaPoolTrait

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

php
use Flytachi\Winter\Ppa\Pool\{PpaPoolConfigInterface, PpaPoolTrait};

class MainDbConfig extends PgDbConfig implements PpaPoolConfigInterface
{
  use PpaPoolTrait;

  public int   $poolMaxConnections = 10;
  public float $poolWaitTimeout    = 5.0;

  public function setUp(): void {  }
}
Свойство По умолчанию Что задаёт
$poolMaxConnections 5 потолок соединений на воркер
$poolWaitTimeout 3.0 сколько ждать свободного соединения
$keepaliveTime 0.0 фоновая проверка простаивающих; 0 — выключено
$idleTimeout 0.0 закрывать простаивающие; 0 — не закрывать
$minimumIdle 0 тёплый минимум соединений

Трейт объявляет только методы, без свойств, — поэтому класс объявляет свойства сам и не ломается, когда в интерфейс добавляют новое. Как выбирать числа — в Пуле соединений.

PpaCallTrait

Flytachi\\Winter\\Ppa\\PpaCallTrait — короткий доступ к соединению из пула прямо от класса конфигурации:

php
$cdo = MainDbConfig::instance();   // соединение текущей единицы работы
$cte = MainDbConfig::cte();        // CTE-репозиторий на этой же базе
Метод Что возвращает
instance() CDO из пула — то же соединение, что и у репозиториев этой базы
cte() CteRepo, привязанный к этой конфигурации

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

Методы конфигурации

Метод Что возвращает
setUp() заполняет свойства; вызывается один раз на экземпляр
connection() CDO, открывая соединение при первом обращении
connect() / disconnect() / reconnect() управление сокетом
ping() true/false, не бросает
pingDetail() ['status' => true, 'latency' => 0.32, 'error' => null]
getDns() DSN без пароля — можно логировать
getDriver() 'pgsql', 'mysql', 'sqlite'
getSchema() схему или null
getUsername() / getPassword() учётные данные
getPersistentStatus() включено ли постоянное соединение

Переменные окружения

Конфигурация читает .env через env() — со значением по умолчанию вторым аргументом:

.env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=app
DB_USER=postgres
DB_PASS=secret

Имена переменных — ваши, слой их не диктует. Значения по умолчанию стоит держать безопасными для локальной разработки: приложение должно подниматься на пустом .env, а не падать с «нет такой переменной».

Привязка репозитория

Репозиторий указывает на конфигурацию классом, а не строкой:

php
class UserRepository extends Repository
{
  public static string $table         = 'users';
  protected string $dbConfigClassName = MainDbConfig::class;
}

Отсюда и берётся, в какую базу пойдёт запрос. Переименование класса подхватит IDE, а опечатка станет ошибкой на этапе разбора, а не в рантайме.

Схема

Схема указывается в конфигурации ($schema у PostgreSQL) и, при необходимости, у репозитория:

php
class AuditRepository extends Repository
{
  public static string $table = 'events';
  protected ?string $schema   = 'audit';      // → audit.events
}

Схема репозитория перекрывает схему конфигурации. Это нужно, когда одна база держит несколько логических пространств: public для приложения, audit для журналов.

Несколько баз

Столько классов, сколько точек подключения:

php
class MainDbConfig extends PgDbConfig    { /* основная база */ }
class AuditDbConfig extends PgDbConfig   { /* журналы, другой сервер */ }
class LegacyDbConfig extends MySqlDbConfig { /* старая система */ }

У каждой — свой пул со своим потолком, и в call db pool они видны по отдельности. Репозитории расходятся по базам через $dbConfigClassName.

Запрос не ходит между базами

Джойн, UNION и CTE выполняются на одном соединении. Репозитории разных конфигураций соединить в одном запросе нельзя — данные придётся сводить в приложении либо средствами самой базы (внешние таблицы, репликация).

Проверить связь

bash
php call db ping

Команда находит все конфигурации проекта, подключается к каждой и печатает задержку. Те же цифры отдаёт /actuator/health — там это делает pingDetail():

json
{ "status": true, "latency": 1.24, "error": null }

ping() никогда не бросает: недоступная база — это false, а не исключение. Поэтому проверку можно звать из health-эндпоинта, не оборачивая в try.

Включить миграции

Конфигурация участвует в миграциях только с явным опт-ином:

php
use Flytachi\Winter\Ppa\Mapping\Attributes\Config\{Extension, Migratable};
use Flytachi\Winter\Ppa\Mapping\Constants\MigratablePriority;

#[Migratable(priority: MigratablePriority::High)]
#[Extension('uuid-ossp')]
class MainDbConfig extends PgDbConfig {  }
Атрибут Что делает
#[Migratable] разрешает call db migrate трогать эту базу; priority задаёт порядок
#[Extension] расширение PostgreSQL, создаваемое до таблиц

Без #[Migratable] команда честно скажет, чего не хватает:

text
No migratable configs — add #[Migratable] to a DbConfig to opt in.

Опт-ин здесь не формальность: он не даёт инструменту схемы дотянуться до базы, которой владеет кто-то другой — например, до легаси-системы, куда приложение только читает.

Дальше