Конфигурация БД
Конфигурация описывает одну точку подключения: драйвер, адрес, учётные данные, схему. Из неё пул строит все свои соединения, поэтому здесь лежит всё, что должно быть одинаковым у каждого соединения — и здесь же включаются миграции.
Почему классом, а не массивом
Массив в config/database.php — привычная форма, но она ничего не знает о себе:
опечатку в ключе видно в рантайме, а «какие вообще базы есть в проекте» приходится искать
глазами.
Класс решает три задачи разом: имя класса становится идентификатором базы, на который
ссылается репозиторий; сканер находит все конфигурации сам, поэтому миграции и
health-проверки не требуют реестра; свойства типизированы, и $port строкой не
окажется.
Объявление
<?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, … |
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 |
mysql:host=db;port=3306;dbname=app;charset=utf8mb4;Схемы у MySQL нет: там база и есть пространство имён, поэтому getSchema() вернёт null.
SqliteDbConfig
| Свойство | Тип | По умолчанию | Что задаёт |
|---|---|---|---|
$path |
string |
:memory: |
путь к файлу или :memory: |
sqlite:/var/app.sqliteНи сервера, ни учётных данных — поэтому в тестах он и удобен. :memory: живёт ровно
столько, сколько соединение.
DbConfig
Общий базовый класс: те же свойства плюс $driver, который вы задаёте сами. Нужен для
драйвера, под который отдельного класса нет.
Общие свойства
| Свойство | Тип | По умолчанию | Что задаёт |
|---|---|---|---|
$isPersistent |
bool |
false |
постоянное соединение PDO |
`$isPersistent` и пул — разные вещи, и вместе не нужны
Постоянное соединение PDO переживает запрос внутри процесса PHP, и придумано оно для FPM, где процесс иначе закрывал бы сокет каждый раз. У резидентного воркера соединения и так живут — этим занимается пул, и он же их проверяет и обновляет.
Включив оба, вы получаете соединение, которое пул считает своим, а PDO — своим, и закрытие одним не видно другому.
Трейты
PpaPoolTrait
Позволяет конфигурации задать свой размер пула. Без него берутся значения по умолчанию.
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 — короткий доступ к соединению из пула прямо от
класса конфигурации:
$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() — со значением по умолчанию вторым аргументом:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=app
DB_USER=postgres
DB_PASS=secretИмена переменных — ваши, слой их не диктует. Значения по умолчанию стоит держать
безопасными для локальной разработки: приложение должно подниматься на пустом .env, а
не падать с «нет такой переменной».
Привязка репозитория
Репозиторий указывает на конфигурацию классом, а не строкой:
class UserRepository extends Repository
{
public static string $table = 'users';
protected string $dbConfigClassName = MainDbConfig::class;
}Отсюда и берётся, в какую базу пойдёт запрос. Переименование класса подхватит IDE, а опечатка станет ошибкой на этапе разбора, а не в рантайме.
Схема
Схема указывается в конфигурации ($schema у PostgreSQL) и, при необходимости, у
репозитория:
class AuditRepository extends Repository
{
public static string $table = 'events';
protected ?string $schema = 'audit'; // → audit.events
}Схема репозитория перекрывает схему конфигурации. Это нужно, когда одна база держит
несколько логических пространств: public для приложения, audit для журналов.
Несколько баз
Столько классов, сколько точек подключения:
class MainDbConfig extends PgDbConfig { /* основная база */ }
class AuditDbConfig extends PgDbConfig { /* журналы, другой сервер */ }
class LegacyDbConfig extends MySqlDbConfig { /* старая система */ }У каждой — свой пул со своим потолком, и в call db pool они видны по отдельности.
Репозитории расходятся по базам через $dbConfigClassName.
Запрос не ходит между базами
Джойн, UNION и CTE выполняются на одном соединении. Репозитории разных конфигураций
соединить в одном запросе нельзя — данные придётся сводить в приложении либо средствами
самой базы (внешние таблицы, репликация).
Проверить связь
php call db pingКоманда находит все конфигурации проекта, подключается к каждой и печатает задержку. Те же
цифры отдаёт /actuator/health — там это делает pingDetail():
{ "status": true, "latency": 1.24, "error": null }ping() никогда не бросает: недоступная база — это false, а не исключение. Поэтому
проверку можно звать из health-эндпоинта, не оборачивая в try.
Включить миграции
Конфигурация участвует в миграциях только с явным опт-ином:
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] команда честно скажет, чего не хватает:
No migratable configs — add #[Migratable] to a DbConfig to opt in.Опт-ин здесь не формальность: он не даёт инструменту схемы дотянуться до базы, которой владеет кто-то другой — например, до легаси-системы, куда приложение только читает.
Дальше
- Сущности — что описывает таблицы этой базы
- Миграции — как описанное доезжает до сервера
- Пул соединений — сколько соединений откроется и когда