timbrs / database-dumps
Framework-agnostic PHP package for managing database dumps with SQL exports/imports, supporting PostgreSQL, MySQL and Oracle
Requires
- php: ^7.1|^8.0
- ext-json: *
- symfony/finder: ^4.4|^5.4|^6.0|^7.0
- symfony/yaml: ^4.4|^5.4|^6.0|^7.0
Requires (Dev)
- doctrine/dbal: ^2.13|^3.0|^4.0
- friendsofphp/php-cs-fixer: ^3.0
- illuminate/console: ^7.0|^8.0|^9.0|^10.0|^11.0
- illuminate/database: ^7.0|^8.0|^9.0|^10.0|^11.0
- illuminate/support: ^7.0|^8.0|^9.0|^10.0|^11.0
- phpcompatibility/php-compatibility: ^9.3
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^8.5|^9.5|^10.0|^11.0
- squizlabs/php_codesniffer: ^3.7
- symfony/config: ^4.4|^5.4|^6.0|^7.0
- symfony/console: ^4.4|^5.4|^6.0|^7.0
- symfony/dependency-injection: ^4.4|^5.4|^6.0|^7.0
- symfony/http-kernel: ^4.4|^5.4|^6.0|^7.0
Suggests
- ext-curl: Required for the optional LLM analysis features (prepare-config --ai / --deep); not needed for plain export/import
- doctrine/dbal: Required for DoctrineDbalAdapter
- illuminate/console: Required for Laravel artisan commands
- illuminate/database: Required for LaravelDatabaseAdapter
- illuminate/support: Required for Laravel bridge
- symfony/config: Required for Symfony bundle DI extension
- symfony/console: Required for Symfony bridge commands
- symfony/dependency-injection: Required for Symfony bundle DI extension
- symfony/http-kernel: Required for Symfony bundle registration
Provides
None
Conflicts
None
Replaces
None
- dev-master / 1.1.x-dev
- v1.1.49
- v1.1.48
- v1.1.47
- v1.1.46
- v1.1.45
- v1.1.44
- v1.1.43
- v1.1.42
- v1.1.41
- v1.1.40
- v1.1.39
- v1.1.38
- v1.1.37
- v1.1.36
- v1.1.35
- v1.1.34
- v1.1.33
- v1.1.32
- v1.1.31
- v1.1.30
- v1.1.29
- v1.1.28
- v1.1.27
- v1.1.26
- v1.1.25
- v1.1.24
- v1.1.23
- v1.1.22
- v1.1.21
- v1.1.20
- v1.1.19
- v1.1.18
- v1.1.17
- v1.1.16
- v1.1.15
- v1.1.14
- v1.1.13
- v1.1.12
- v1.1.11
- v1.1.10
- v1.1.8
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.1
- v1.0.0
This package is auto-updated.
Last update: 2026-09-29 07:02:45 UTC
README
Русский язык
PHP-пакет для экспорта и импорта дампов баз данных в SQL. Поддерживает PostgreSQL, MySQL и Oracle (12c+). Работает с Symfony, Laravel и любым PHP-проектом.
Оглавление
- Описание
- Возможности
- Установка
- Быстрый запуск (LLM + OPENCODE)
- Быстрый старт
- Конфигурация
- Углублённый анализ (LLM + OPENCODE)
- Проверка конфига без БД (validate)
- Настройка Symfony
- Настройка Laravel
- Скрипты before/after
- Поддержка IDE (JSON Schema)
- Архитектура
- Безопасность
- Тестирование
- Локальная разработка
- Требования
- Лицензия
Описание
Database Dumps — это PHP-пакет, который помогает разработчикам создавать и разворачивать дампы базы данных для локальной и тестовой среды.
Какую проблему решает? На проекте с большой боевой базой разработчику нужно иметь актуальные тестовые данные — но копировать всю базу долго, а персональные данные клиентов нельзя использовать в dev-окружении. Этот пакет позволяет настроить правила экспорта один раз: какие таблицы забирать целиком, какие — частично, и забыть о ручной подготовке дампов.
Как работает? Вы описываете в YAML-файле, какие таблицы и как экспортировать. Пакет сам:
- генерирует SQL-дампы из боевой (или staging) базы с учётом FK-зависимостей между таблицами
- заменяет персональные данные (ФИО, email, телефоны) на сгенерированные, чтобы дампы были безопасны
- разворачивает дампы в нужную базу одной командой, с защитой от случайного запуска на продакшене
Где применим? В любом PHP-проекте на Symfony или Laravel (или без фреймворка), где нужно:
- быстро разворачивать тестовую базу для разработчиков
- передавать дампы между командами без утечки персональных данных
- держать seed-данные в репозитории и обновлять их из реальной базы
- работать с несколькими базами данных одновременно
Возможности
- Не привязан к фреймворку — работает с Symfony, Laravel и любым PHP-проектом
- PostgreSQL, MySQL и Oracle — автоматически генерирует правильный SQL для каждой СУБД
- Несколько подключений — экспорт/импорт сразу из нескольких баз данных
- Пакетные INSERT — автоматическая группировка по 1000 строк на выражение
- Откат при ошибках — импорт выполняется в транзакции
- Защита от продакшена — импорт заблокирован при
APP_ENV=prod - Скрипты до/после — свои SQL-скрипты до и после импорта
- Гибкая настройка — YAML-файл с правилами полного и частичного экспорта
- Сброс счётчиков — автоматический сброс sequence / auto-increment после импорта
- Автогенерация конфига — команда
prepare-configсоздаёт YAML по структуре БД - FK-сортировка — автоматическая топологическая сортировка таблиц при экспорте и импорте (родители первыми)
- Каскадные зависимости —
cascade_fromгенерирует WHERE-подзапросы для связности данных через FK - Замена ПД (faker) — автоматическое обнаружение и замена персональных данных (ФИО, email, телефон, пол) при экспорте
- Разделение конфига — автоматическое разбиение конфигурации на отдельные файлы по схемам
- Smart Cycle Breaking — автоматический разрыв циклических FK-зависимостей (двухфазный INSERT NULL → UPDATE) вместо fallback на алфавитный порядок
- Self-referential FK — поддержка таблиц со ссылками на себя (деревья категорий, оргструктуры, комментарии)
- Валидация схемы при импорте — предупреждение при расхождении столбцов дампа и БД, опция
--ignore-schema-mismatch - Настройки в конфиге — секция
settings:для batch_size, sample_size, max_cascade_depth - Dry-run экспорт — опция
--dry-runдля просмотра плана экспорта без выполнения - ИИ-анализ данных (LLM) — точная классификация ПД, профилирование колонок и подсказки по выборке через OpenAI-совместимый LLM; при первом запуске пакет интерактивно спрашивает URL/модель/token и запоминает их
- ИИ-анализ кода (OPENCODE) — обнаружение связей без FK и бизнес-сегментов в коде хост-приложения (Eloquent/Doctrine/сырые JOIN) → авто-обогащение
cascade_fromиsample.criteria - Выборка по критериям (sample) — набор именованных «корзин» (red/new/inactive/vip…) с дедупом по PK — «все фломастеры»
Установка
composer require --dev timbrs/database-dumps
Быстрый запуск (LLM + OPENCODE)
Рекомендуемый, основной сценарий — анализ с участием ИИ. Пакет использует два источника:
- Прямой LLM (OpenAI-совместимый API, например
openai/gpt-oss-120b) — для анализа данных: точная классификация ПД (faker), профилирование колонок, подсказки по выборке. - OPENCODE (внешний агент) — для анализа кода хост-приложения: связи без FK, бизнес-сегменты (scopes/репозитории) →
sample.criteria, карта «колонка → код».
Без настроенного LLM пакет тоже работает — анализ деградирует на regex-эвристики. Но рекомендуется именно LLM-сценарий.
Шаг 1. Сгенерировать конфиг (при первом запуске пакет сам спросит про LLM)
# Laravel php artisan dbdump:prepare-config all # Symfony php bin/console app:dbdump:prepare-config all
При первом запуске, если настройки LLM ещё не заданы, команда спросит:
Настроить LLM сейчас?— да/нет (по умолчанию да);API URL— базовый адрес OpenAI-совместимого API (напримерhttps://gpt.example.com/v1);Модель— имя модели (по умолчаниюopenai/gpt-oss-120b);Token— Bearer-токен; можно оставить пустым (Enter).
Несекретные ответы (URL/модель) сохраняются в файл настроек — config/packages/database_dumps.yaml (Symfony) / config/database-dumps.php (Laravel), а Token — в .env.local (DBDUMP_LLM_TOKEN, при отсутствии — в .env); настройки применяются сразу в этом же запуске. Повторно спрашивать не будет. Если откажетесь — выбор тоже запомнится (анализ пойдёт на regex); включить позже — командой configure-llm (см. ниже).
ℹ️ Токен в файл настроек не пишется — он хранится в
.env.local, поэтому файл настроек безопасно коммитить. В Symfony бандл регистрируется только вне prod.
Результат: dump_config.yaml (+ per-schema файлы) с автоопределёнными full_export/partial_export, секцией faker (LLM-детекция ПД) и каскадами по FK.
Шаг 2 (опционально). Обогатить конфиг анализом кода через OPENCODE
Нужен установленный opencode в PATH и настроенный в нём провайдер LLM (~/.config/opencode/opencode.json).
# Одной командой: провижинит агента + инвентарь, прогонит opencode по схемам и применит результат php artisan dbdump:prepare-analysis # Laravel php bin/console app:dbdump:prepare-analysis # Symfony
Команда сгенерирует в хост-проект всё необходимое для внешнего агента:
docker/database/analysis/schema_inventory.json+schema_inventory.<schema>.json— инвентарь БД для агента (без значений данных — PII не выгружается);docker/database/analysis/output_schema.json— контракт JSON-вывода;docker/database/analysis/out/— куда агент кладёт результат.
Документацию инструмента (рабочий цикл, команды, коды находок) генерирует app:dbdump:docs в docker/database/analysis/docs/ — она собирается из самого кода и потому не устаревает отдельно от него.
Команда собирает пакет и печатает, где он лежит; агента она не запускает — это делается снаружи (см. Анализ кода через OPENCODE). Результат агента (связи из кода → cascade_from: source: code, бизнес-сегменты → sample.criteria) применяет apply-analysis; отчёт — в {data_dir}/analysis/REPORT.md. Пользовательские правки в YAML в приоритете.
Шаг 3. Экспортировать и импортировать дампы
php artisan dbdump:export all # экспорт (faker применяется автоматически) php artisan dbdump:import # импорт (заблокирован на проде)
Переконфигурировать LLM в любой момент
php artisan dbdump:configure-llm # Laravel php bin/console app:dbdump:configure-llm # Symfony
Интерактивно меняет URL/модель/token, умеет проверить соединение и пересохранить настройки: несекретное — в файл настроек config/packages/database_dumps.yaml (Symfony) / config/database-dumps.php (Laravel), токен — в .env.local (DBDUMP_LLM_TOKEN).
Быстрый старт
Symfony
- Зарегистрируйте бандл вручную в
config/bundles.php(Flex-рецепта нет):
Timbrs\DatabaseDumps\Bridge\Symfony\DatabaseDumpsBundle::class => ['dev' => true, 'test' => true],
- Создайте файл
docker/database/dump_config.yaml(путь по умолчанию; переопределяется ключомconfig_path):
full_export: public: - users - roles partial_export: public: clients: limit: 1000 order_by: created_at DESC
- Экспортируйте дампы:
php bin/console app:dbdump:export all
- Импортируйте дампы:
php bin/console app:dbdump:import
Laravel
-
Сервис-провайдер подключается автоматически. Файл
docker/database/dump_config.yamlсоздаётся при первом запуске. -
Отредактируйте
docker/database/dump_config.yaml(формат тот же, что и для Symfony). -
Экспортируйте дампы:
php artisan dbdump:export all
- Импортируйте дампы:
php artisan dbdump:import
Конфигурация
Экспорт настраивается через YAML-файл. В нём две секции: full_export (все строки) и partial_export (с ограничениями).
Полный экспорт (full_export)
Экспортирует все строки из указанных таблиц:
full_export: public: # имя схемы - users # таблицы — все строки - roles system: - settings
Частичный экспорт (partial_export)
Экспортирует часть строк с фильтрацией:
partial_export: public: clients: limit: 1000 # максимум строк order_by: created_at DESC # сортировка where: "is_active = true" # условие WHERE orders: limit: 5000 order_by: id DESC
Доступные опции:
| Опция | Описание |
|---|---|
limit |
Максимум строк (для sample — общий потолок на объединённую выборку) |
order_by |
Сортировка (должна заканчиваться на ASC или DESC) |
where |
Условие WHERE |
cascade_from |
Каскадная фильтрация по FK-родителю (см. ниже) |
sample |
Выборка по именованным критериям («все фломастеры», см. ниже) |
Каскадные зависимости (cascade_from)
При частичном экспорте связанных таблиц данные могут стать несогласованными: дочерняя таблица может ссылаться на строки, которые не попали в дамп родителя. Опция cascade_from решает эту проблему — она автоматически генерирует WHERE-подзапрос, ограничивающий выборку только теми строками, чей FK-родитель присутствует в дампе.
partial_export: public: users: limit: 500 order_by: id DESC orders: limit: 1000 order_by: created_at DESC cascade_from: - parent: public.users fk_column: user_id parent_column: id order_items: limit: 5000 order_by: id DESC cascade_from: - parent: public.orders fk_column: order_id parent_column: id
В этом примере:
ordersэкспортирует только те заказы, чейuser_idесть среди экспортированныхusersorder_itemsэкспортирует только позиции заказов, попавших в дампorders- Подзапросы вложенные:
order_items→orders→users(глубина до 10 уровней)
Команда prepare-config автоматически определяет FK-зависимости и генерирует cascade_from. Чтобы отключить: --no-cascade.
Выборка по именованным критериям (sample)
Иногда нужно набрать «все фломастеры» — по 10–100 строк каждого бизнес-сегмента (красные/жёлтые/зелёные по статусу, новые/старые по дате, неактивные, VIP…), часто по разным колонкам и кросс-таблично. Опция sample задаёт набор именованных «корзин», каждая со своим WHERE и квотой; итоговая выборка — объединение всех корзин без дублей (дедуп по первичному ключу).
partial_export: public: clients: order_by: id DESC limit: 100 # необяз.: общий потолок на объединённую выборку sample: criteria: # именованные корзины; у каждой свой WHERE и квота - { name: red, where: "status = 'red'", limit: 10 } - { name: new, where: "created_at >= NOW() - INTERVAL '30 days'", limit: 50 } - { name: inactive, where: "last_login_at < NOW() - INTERVAL '90 days'", limit: 20 } - { name: vip, where: "EXISTS (SELECT 1 FROM public.client_flags f WHERE f.client_id = clients.id AND f.flag = 'vip')", limit: 30 } stratify_by: status # сахар: по корзине на каждое DISTINCT-значение; можно список — [status, kind] per_value: 100 # квота на корзину stratify_by (по умолчанию 100 или settings.sample.per_value)
Как это работает:
- Фаза 1 — по каждой корзине выбираются первичный ключ и колонки, на которые ссылаются дети:
SELECT <pk>[, <parent_column>…] FROM clients WHERE (<where>) AND (<cascade_from>) AND (<crit.where>) [ORDER BY ...] LIMIT <crit.limit>.stratify_by(колонка или список колонок) разворачивается в по-корзине на каждое DISTINCT-значение: значения берутся из статистики планировщика (pg_stats), когда она полная, иначеSELECT DISTINCT … LIMIT 51— потолок 50 корзин, лишнее значение выдаёт усечение. На большой таблице без каскада и без статистикиDISTINCTбыл бы полным сканом — корзины пропускаются с предупреждением. - Квота под limit — если
per_value × корзин > limit, квота корзины заранее ужимается до⌊limit/корзин⌋(но не ниже 5): покрытие видов данных важнее объёма. - Дедуп и слияние — строки всех корзин объединяются без повторов. Если объединение больше
limit, корзины сливаются round-robin (по строке из каждой по кругу): ни одна корзина не теряется целиком. - Фаза 2 — финальный
SELECT * FROM clients WHERE <pk> IN (...)(для составного PK — дизъюнкция равенств; список длиннее 1000 значений режется на несколькоINчерезOR).
Требования и поведение:
- У таблицы должен быть первичный ключ (нужен для дедупа и фазы 2). Без него — или когда роль выгрузки его не видит — корзины не выполняются: экспорт не падает, а берёт плоский срез по
limit(безlimit— 1000 строк), вsample-report.jsonу таблицыfallback_reason: no_primary_key.checkловит это до выгрузки (Q-9, стадияlive) и после (V-6). В PostgreSQL ключ читается изpg_catalog, а не изinformation_schema, поэтому его видит и роль с однимSELECT.criteria[].whereпроходит ту же проверку, что и обычныйwhere(запрет;и SQL-комментариев, баланс кавычек/скобок) — корректныеEXISTS (...)допускаются.name— идентификатор,limit— целое ≥ 1. - Cascade-консистентность: если у родителя задан
sample, дочерние таблицы (cascade_from) ссылаются на фактически выбранные значения родителя — в реестр попадают и PK, и колонки, по которым на родителя ссылаются дети (parent_column: core_idу версионных таблиц), — а не повторяют критерии подзапросом. sampleиcascade_fromна одной таблице совместимы: каскад входит в базовое условие каждой корзины, и «все виды» набираются среди строк, связанных с выгруженным родителем. Пустая корзина у такого ребёнка значит, что родитель не дал связанных строк этого вида (G-6— заметка валидатора об этом).- После
export allв{data_dir}/analysis/sample-report.jsonлежит ход выборки: по таблице — корзины (квота, строк, миллисекунды, ошибка), источник и усечение значенийstratify_by, потолок и усечение объединения. Значения корзин показываются только если похожи на код (^[A-Za-z0-9_.\-]{1,32}$) и колонка не выглядит как ПД; остальные корзины именуются<колонка>#<номер>.
Вложенная и обратная стратификация
«Сто зелёных закрытых» — корзина на пару значений двух колонок, и «цвет клиента», который живёт не в
clients, а в clients_attrs.value_string при attr_id = 4, — обе задачи решаются в той же секции sample:
partial_export: clients: clients_attrs: limit: 5000 sample: stratify: # вложенная: корзина на attr_id, внутри — на значение атрибута - column: attr_id per_value: 100 # квота корзины первого уровня (когда второго уровня нет) then: column: value_int # какая из value_* — по типу атрибута из *_attrs_dict per_value: 10 # квота на пару (attr_id, value_int); по умолчанию 10 max_values: 20 # значений второго уровня на значение первого; по умолчанию 20 clients: limit: 2000 sample: stratify_via: # обратная: значения открываются на дочерней таблице - table: clients.clients_attrs join: { core_id: core_id } # колонка дочерней таблицы: колонка этой таблицы where: "attr_id = 4 AND active_flg" column: value_string per_value: 100
stratify[].then— один уровень вложенности (глубже число корзин растёт как произведение, и квота ужмётся в ноль). Значения второго уровня берутсяSELECT DISTINCT <then.column> … WHERE (<base>) AND <column> = V LIMIT max_values + 1— фильтр по первому уровню делает запрос выборочным. Корзина именуетсяattr_id#0/value_int#3; если второго уровня у значения нет, остаётся корзина первого уровня с его квотой.stratify_via[]— значенияcolumnчитаются на дочерней таблице (SELECT DISTINCT … WHERE (<where>) LIMIT 51, обычно по индексуattr_id), а корзины у родителя строятся условиемEXISTS (SELECT 1 FROM <table> _via WHERE _via.<k> = <родитель>.<k> AND (<where>) AND _via.<column> = V)с квотойper_value. Так «сто красных, сто зелёных» из атрибутов попадают в выборкуclients, аclients_attrsподтягивается каскадом.- Квоты обоих видов ужимаются под
limitтак же, как уstratify_by; строковые значения вsample-report.jsonпоказываются только для кодов, для ПД-колонок — номер корзины.
Авто-генерация: с флагом --criteria (или --deep) prepare-config профилирует категориальные колонки и сам предлагает sample.criteria (по корзине на топ-значение). Бизнес-сегменты из кода (Eloquent scopes, методы репозиториев) добавляются через ветку OPENCODE — см. Углублённый анализ.
Замена персональных данных (faker)
Пакет может автоматически обнаруживать и заменять персональные данные при экспорте. Это позволяет безопасно использовать дампы в dev/staging окружениях.
Поддерживаемые паттерны:
| Паттерн | Описание | Пример оригинала | Пример замены |
|---|---|---|---|
fio |
ФИО полностью | Иванов Иван Иванович | Петров Александр Сергеевич |
fio_short |
ФИО сокращённо | Иванов И.И. | Козлов А.В. |
name |
Фамилия Имя | Иванов Иван | Петров Александр |
firstname |
Имя (кросс-корреляция с составной колонкой) | Иван | Александр |
lastname |
Фамилия (кросс-корреляция с составной колонкой) | Иванов | Петров |
patronymic |
Отчество (кросс-корреляция с составной колонкой) | Иванович | Сергеевич |
email |
ivan@company.ru | aleksandr.petrov42@example.com | |
phone |
Телефон | +79161234567 | +79234567890 |
gender |
Пол (12 форматов: male/female, м/ж, муж/жен и др.) | Мужской | Женский |
inn |
ИНН (10 или 12 цифр, контрольные разряды пересчитаны) | 7707083893 | 7712045619 |
ogrn |
ОГРН/ОГРНИП (13 или 15 цифр, контрольный разряд пересчитан) | 1027700132195 | 1024306718432 |
digits |
Любой цифровой идентификатор с сохранением длины и разделителей (КПП, ОКПО, ОКАТО, ОКТМО, СНИЛС) | 770701001 | 418362905 |
birth_date |
Дата рождения (сдвиг ±180 дней, формат исходной строки сохраняется) | 1985-04-17 | 1985-09-02 |
doc_series |
Серия документа | 4509 | 2731 |
doc_number |
Номер документа | 123456 | 804517 |
Секция faker в конфигурации:
faker: public: users: full_name: fio display_name: name first_name: firstname last_name: lastname middle_name: patronymic email: email phone: phone sex: gender employees: fio: fio short_fio: fio_short contact_email: email
Паттерны firstname, lastname и patronymic детектируются через кросс-корреляцию: если в таблице уже найдена составная колонка (fio, fio_short, name), а рядом есть колонка с отдельными именами/фамилиями/отчествами — она будет обнаружена автоматически.
Паттерны inn, ogrn, digits, birth_date, doc_series и doc_number выводятся из значения: одинаковый вход даёт одинаковый выход в любой таблице и в любом прогоне. Иначе один и тот же ИНН превратился бы в clients в одно число, а в leads — в другое, и связь между записями в дампе разошлась бы. Длина, разделители и формат даты сохраняются, поэтому такой faker годится и для числовых колонок (bigint), и для date/timestamp.
Паттерн gender определяется по совпадению имени колонки (gender, sex, пол) и содержимого (допустимые значения: male/female, m/f, м/ж, мужской/женский, муж/жен, мужчина/женщина). Регистр и формат оригинала сохраняются при замене.
Команда prepare-config автоматически анализирует содержимое таблиц и генерирует секцию faker, если в колонках обнаруживаются паттерны ПД (порог совпадения: 80% из 200 случайных строк). Чтобы отключить: --no-faker.
Замена детерминирована — seed основан на хеше значения ФИО (колонка с паттерном fio), если такая колонка есть в конфигурации таблицы. Если колонки fio нет — seed берётся от хеша комбинации всех faker-значений строки. Это гарантирует, что одна и та же персона всегда получает одинаковую замену независимо от таблицы и запуска.
Разделение конфига по схемам (includes)
При большом количестве таблиц конфигурация может стать громоздкой. Команда prepare-config по умолчанию разбивает конфиг на отдельные файлы по схемам. Главный dump_config.yaml лежит в docker/database/, а пер-схемные файлы — в подкаталоге docker/database/dump-settings/:
docker/database/
├── dump_config.yaml # главный файл с includes
└── dump-settings/
├── public.yaml # конфигурация схемы public
├── system.yaml # конфигурация схемы system
└── analytics/ # именованное подключение
└── analytics.yaml
Главный файл (docker/database/dump_config.yaml):
includes: public: ./dump-settings/public.yaml system: ./dump-settings/system.yaml connections: analytics: includes: analytics: ./dump-settings/analytics/analytics.yaml
💡 Пути к include пишутся с префиксом
./— так PhpStorm/IDE распознают их как ссылки на файлы:Ctrl+B/Cmd+B(или Ctrl+клик) на пути прыгает прямо в нужный*.yaml. На резолв путей загрузчиком префикс не влияет (работают оба варианта — с./и без).
Файл схемы (docker/database/dump-settings/public.yaml):
full_export: - users - roles partial_export: clients: limit: 1000 order_by: created_at DESC faker: users: full_name: fio email: email
Чтобы генерировать единый файл без разделения: --no-split.
Несколько подключений
Если нужно работать с несколькими базами данных, добавьте секцию connections:
# Основное подключение full_export: public: - users - roles partial_export: public: posts: limit: 100 # Дополнительные подключения connections: analytics: # имя подключения (как в настройках фреймворка) full_export: analytics: - events - metrics partial_export: analytics: logs: limit: 50 order_by: id DESC
Куда сохраняются дампы:
- Основное подключение:
{data_dir}/dumps/{schema}/{table}.sql - Именованное подключение:
{data_dir}/dumps/{connection}/{schema}/{table}.sql
data_dir — базовый каталог данных (по умолчанию docker/database), от него считается всё, что порождает пакет: главный конфиг ({data_dir}/dump_config.yaml), пер-схемные файлы ({data_dir}/dump-settings), дампы ({data_dir}/dumps), анализ ({data_dir}/analysis) и хуки ({data_dir}/before_exec, {data_dir}/after_exec). Изменить можно ключом data_dir в файле настроек — config/packages/database_dumps.yaml (Symfony) / config/database-dumps.php (Laravel) — или через env DBDUMP_DATA_DIR. В prod всегда используется дефолт docker/database.
Смена дефолта в 1.1.23. Раньше дефолтом был
database, а путь кdump_config.yamlбыл зашит отдельной константой и не следовал заdata_dir. Теперь дефолт —docker/database, и конфиг живёт внутриdata_dirвместе с дампами. Установка, которая полагалась на прежний дефолт (ключdata_dirне задан), после обновления перестанет видеть свои файлы — пропишитеdata_dir: 'database'либо перенесите каталог.
Опция --connection:
# Только основное подключение (по умолчанию) php artisan dbdump:export all # Только указанное подключение php artisan dbdump:export all --connection=analytics # Все подключения сразу php artisan dbdump:export all --connection=all
Настройки (settings)
Секция settings позволяет изменять внутренние параметры пакета:
settings: batch_size: 1000 # строк на INSERT-выражение (по умолчанию: 1000) sample_size: 200 # строк для анализа ПД в PatternDetector (по умолчанию: 200) max_cascade_depth: 10 # макс. глубина cascade_from подзапросов (по умолчанию: 10) sample: per_value: 100 # квота корзины stratify_by для всех таблиц (по умолчанию: 100)
Автогенерация конфигурации
Команда prepare-config смотрит на структуру БД и создаёт или обновляет dump_config.yaml. Обязательный аргумент mode определяет область действия:
# Symfony php bin/console app:dbdump:prepare-config all # Полная регенерация php bin/console app:dbdump:prepare-config schema=billing # Перегенерировать одну схему php bin/console app:dbdump:prepare-config table=public.users # Перегенерировать одну таблицу php bin/console app:dbdump:prepare-config new # Добавить только новые таблицы # Laravel php artisan dbdump:prepare-config all php artisan dbdump:prepare-config schema=billing php artisan dbdump:prepare-config table=public.users php artisan dbdump:prepare-config new
Режимы:
| Режим | Описание |
|---|---|
all |
Полная регенерация конфигурации. Поверх существующего конфига отказывает — нужен --force |
schema=<name> |
Перегенерация одной схемы, мёрж в существующий конфиг |
table=<schema.table> |
Перегенерация одной таблицы, мёрж в существующий конфиг |
new |
Обнаружение и дописывание новых таблиц (не затрагивает существующие) |
Режим all собирает конфиг заново и существующий файл не читает: настроенные вручную sample.criteria,
cascade_from, limit и faker он заменяет машинной догадкой. Поэтому при существующем dump_config.yaml
команда отказывается работать и называет режимы, которые мёржат, а не затирают (new, schema=, table=),
а также check-criteria и validate. Осознанная пересборка с нуля — prepare-config all --force.
Опции:
| Опция | Описание | По умолчанию |
|---|---|---|
--threshold, -t |
Порог строк: таблицы с количеством строк <= порога идут в full_export, больше — в partial_export | 500 |
--force, -f |
Разрешить полную регенерацию поверх существующего конфига (только для режима all) |
— |
--no-cascade |
Пропустить обнаружение FK и генерацию cascade_from |
— |
--no-faker |
Пропустить обнаружение персональных данных | — |
--no-split |
Генерировать единый YAML без разделения по схемам | — |
--criteria |
Авто-генерация sample.criteria из категориальных колонок |
— |
--ai / --no-ai |
Включить/отключить LLM-детекцию ПД (авто: включается, если LLM настроен — env DBDUMP_LLM_URL или файл настроек). --no-ai также подавляет авто-запрос настройки LLM при первом запуске |
авто |
--deep |
Глубокий анализ: профилирование + ИИ + sample.criteria + отчёт docker/database/analysis/REPORT.md |
— |
Как распределяются таблицы:
- Строк <= порога —
full_export - Строк > порога —
partial_export(с limit, автоопределённой сортировкой и шаблономwhere: "1=1"для удобства редактирования) - Пустые таблицы — пропускаются
- Служебные таблицы (migrations, sessions, cache_, telescope_, oauth_, audit_) — пропускаются
Углублённый анализ (--deep / --ai / --criteria)
Анализ с участием ИИ — основной сценарий пакета. Опирается на два источника:
- Прямой LLM (
openai/gpt-oss-120bпо OpenAI-совместимому API) — анализ данных: PII-классификация точнее regex, профилирование, подсказки по выборке. Запросы ограничены по размеру. - OPENCODE (внешний агент) — анализ кода целиком: связи без FK (Eloquent
belongsTo/hasMany, Doctrine-ассоциации, сырые JOIN), карта «колонка → код», ключевые поля и бизнес-сегменты. Пакет готовит вход и инструкции, агент возвращает JSON, который пакет поглощает.
Если LLM не настроен, анализ мягко деградирует на regex-эвристики — пакет остаётся работоспособным. См. также Быстрый запуск.
Настройка LLM (интерактивно)
При первом запуске prepare-config (если настройки LLM ещё не заданы и сессия интерактивна) пакет сам предложит настроить LLM — спросит API URL, Модель и Token (token можно оставить пустым), сохранит несекретное в файл настроек, токен — в .env.local (DBDUMP_LLM_TOKEN), и применит настройки немедленно в этом же запуске. Отказ тоже запоминается, чтобы не спрашивать снова. Авто-запрос подавляется флагом --no-ai и в неинтерактивном режиме (--no-interaction, CI).
Настроить или изменить LLM в любой момент можно явной командой — она дополнительно умеет проверить соединение:
php artisan dbdump:configure-llm # Laravel php bin/console app:dbdump:configure-llm # Symfony
После настройки --ai/--deep и prepare-analysis подхватывают параметры автоматически. Приоритет источников: переменные окружения DBDUMP_LLM_* (если задан URL) перекрывают файл настроек config/packages/database_dumps.yaml (Symfony) / config/database-dumps.php (Laravel); токен всегда берётся из окружения (.env.local → .env) и в файл настроек не пишется.
LLM-детекция ПД и профилирование
# Symfony php bin/console app:dbdump:prepare-config all --deep php bin/console app:dbdump:prepare-config all --ai # только LLM-PII php bin/console app:dbdump:prepare-config all --criteria # только авто sample.criteria # Laravel php artisan dbdump:prepare-config all --deep
--aiиспользует LLM для классификации ПД (regex-результаты подаются как hints; принимаются типы с уверенностью выше порога, маппятся наfio/email/phone/...). При недоступном LLM — тихий fallback на regex + предупреждение.--criteriaпрофилирует категориальные колонки и предлагаетsample.criteria(по корзине на топ-значение).--deepвключает всё перечисленное + пишет отчётdocker/database/analysis/REPORT.md(+ машинныйanalysis_result.json): режим экспорта, предложенные критерии с SQL и обоснованием, ПД (regex vs LLM), профиль колонок.
Переменные окружения LLM:
| Переменная | Назначение | По умолчанию |
|---|---|---|
DBDUMP_LLM_URL |
Базовый URL OpenAI-совместимого API (например https://llm.example.com/v1). Пусто → AI-функции выключены |
— |
DBDUMP_LLM_MODEL |
Имя модели | openai/gpt-oss-120b |
DBDUMP_LLM_TOKEN |
Bearer-токен (опционально) | — |
DBDUMP_LLM_TIMEOUT |
Таймаут запроса, сек | 120 |
DBDUMP_LLM_ENABLED |
true/false; по умолчанию авто (включено при заданном URL) |
авто |
HTTP-запросы выполняются через ext-curl (без guzzle). Данные PII в промптах ограничены примерами значений колонок.
Анализ кода через OPENCODE
Пакет не запускает агента. До 1.1.26
prepare-analysis --runдёргалopencodeчерезexec()из PHP — и это оказалось неверным слоем. Агент сам решает, чем и в каком порядке пользоваться, и умеет вызватьprepare-analysis,validateиcheck-criteriaкуда осмысленнее, чем PHP умеет вызвать агента. За пакетом осталась детерминированная часть: собрать факты (инвентарь схемы, находки валидатора, падающие criteria с ошибкой СУБД) и применить готовый результат (apply-analysis). Решения — эвристики, починка, выбор режима выгрузки — снаружи. В пакете не осталось ни одного вызова внешнего процесса.
Самый простой путь — одной командой (нужен opencode в PATH):
php artisan dbdump:prepare-analysis # Laravel php bin/console app:dbdump:prepare-analysis # Symfony
Команда собирает пакет (инвентарь схемы, пер-схемные инвентари, контракт вывода) и печатает, где он лежит. Прогон агента — снаружи; применение результата — apply-analysis.
Ручной путь (3 шага) — если хотите контролировать прогон агента:
# 1. Подготовить пакет (инвентарь + контракт) и документацию инструмента php bin/console app:dbdump:prepare-analysis # Symfony php bin/console app:dbdump:docs # WORKFLOW.md / COMMANDS.md / FINDINGS.md # 2. Запустить своего агента по чанку на схему, дав ему инвентарь схемы и контракт вывода # (агент читает код проекта, к БД не подключается — все безопасные факты уже в инвентаре) # 3. Применить результат к dump_config.yaml php bin/console app:dbdump:apply-analysis # Symfony php artisan dbdump:apply-analysis # Laravel
Что провижинит prepare-analysis в хост-проект:
docker/database/analysis/schema_inventory.json— полный инвентарь +schema_inventory.<schema>.jsonпо каждой схеме (для прогона по чанку без переполнения контекста 128k), без значений данных (PII агенту не выгружается);docker/database/analysis/dossier.<schema>.json— досье по каждой таблице и колонке: слепок, оценка строк, связи (db_fk+cascade_from), enum'ы из кода с доказательствами, миграции, вьюхи, признаки SCD2/словаря/EAV и пометкиambiguous— именно с ними идут к агенту по коду;docker/database/analysis/decisions.<schema>.json— предложения по конфигу (правила R1…R11): что менять, на что и почему, со ссылками на доказательства;docker/database/analysis/decisions_schema.json— контракт этого файла;docker/database/analysis/output_schema.json— JSON-контракт ответа агента (старый формат);docker/database/analysis/out/— каталог для результата агента.
Решения (decisions.<schema>.json)
Правила читают досье и предлагают правки. Тулза применяет сама только механическое —
записи с auto: true:
| правило | о чём | авто |
|---|---|---|
R1 |
полная выгрузка таблицы крупнее порога → частичная с order_by и разрезами |
нет |
R2 |
словарь, маленькая таблица, таблица с высокой входящей степенью или наполняемая миграцией → выгружать целиком | нет |
R3 |
категориальная колонка без покрытия → корзины (stratify; для EAV — attr_id + вложенный value_*) |
нет |
R4 |
колонка с ПД-именем без faker → маскировать |
да |
R5 |
связь без cascade_from |
да для внешнего ключа БД |
R6 |
SCD2-таблица без разрезов → три критерия: текущая активная, деактивированная, история | нет |
R7 |
таблица в конфиге, которой нет в БД → удалить | да |
R8 |
кандидат в корзины с более чем 50 различными значениями → нужны критерии по группам | нет |
R9 |
case enum'а из кода не встречается в БД (и наоборот) | нет |
R10 |
квота × число корзин больше limit → снизить квоту |
нет |
R11 |
phone/fio на числовой или датной колонке → inn/digits/birth_date |
да |
Значений данных в решениях нет: только имена таблиц, колонок и case'ов enum'ов. id записи
считается от адреса и предложения, поэтому отметка accepted переживает повторный analyze, а
изменившееся предложение придётся принимать заново.
Изменено в 1.1.32. Пакет больше не кладёт
.opencode/agents/dbdump-mapper.md,.opencode/commands/dbdump-map.mdиanalysis/RUN.md: инструкция устаревала отдельно от кода. Её место занялиapp:dbdump:docs(документация из самого инструмента) и политика запуска агента в проекте.
Применение: apply
php bin/console app:dbdump:apply # он же app:dbdump:apply-analysis php bin/console app:dbdump:apply -s clients -s tasks php bin/console app:dbdump:apply --decisions=docker/database/analysis/decisions.clients.json php bin/console app:dbdump:apply --legacy # только старый вывод агента из out/
Записывает решения в dump_config.yaml. Три правила, которые не обойти:
autoприменяется само — маскирование колонки с ПД-именем, связь по внешнему ключу БД, удаление таблицы, которой в базе нет, замена заведомо неподходящего паттерна. Всё, что меняет состав выборки, ждёт отметкиacceptedот агента по коду или от человека.accepted: false— отказ, он сильнееauto.- Существующее значение в YAML побеждает без
override: конфиг правят руками, и затирать ручную настройку молча нельзя.criteriaиcascade_fromпри этом накапливаются — новое ребро или сегмент дописывается, совпадение по имени (или по пареparent+fk_column) пропускается. - Решение из устаревшего конфига не применяется. Если
currentзаписи разошёлся с тем, что сейчас в YAML, значит между анализом и применением конфиг поправили: запись помечаетсяstaleи пропускается. Перезапуститеprepare-analysis.
Предложение проверяется через TableConfig::fromArray() до мутации, поэтому битая запись не
оставляет конфиг полуприменённым. Итог с провенансом (правило, обоснование, доказательства,
статус) — в docker/database/analysis/apply-report.json; изменённый YAML пишется только если
хоть одно решение применилось.
Старый вывод агента (analysis/out/*.json) тот же вызов применяет по-прежнему: cascade_from
из кода и sample.criteria из бизнес-сегментов, пользовательские правки в приоритете, провенанс
в docker/database/analysis/REPORT.md. Кроме того, prepare-analysis показывает его в новом
виде — связи и сегменты как решения с rule: legacy (всегда требуют отметки), а columns[]
как заметки агента в досье: раньше они просто выбрасывались.
Для больших схем дробите прогон по чанку на схему — пер-схемные инвентари для того и пишутся.
Одна команда проверки: check
check прогоняет все проверки и складывает находки в одно пространство кодов. Стадии выбираются
по тому, что доступно, и каждая честно докладывает, почему пропущена:
| стадия | что проверяет | что нужно |
|---|---|---|
static |
конфиг против слепка схемы (коды S/C/L/Q-1…Q-5/F/G/D/H); --fix чинит однозначное |
слепок |
live |
каждый sample.criterion в БД под statement_timeout: Q-7 падает, Q-8 таймаут, Q-6 корзина пуста; Q-9 у таблицы с sample не виден первичный ключ; P-1/P-2 из слепка |
живая БД |
plan |
что и как будет выгружено: режим, where, каскад, корзины |
ничего |
dump |
что реально легло в dumps/: V-1…V-8 |
файлы дампа |
import |
контрольная заливка в scratch-БД: I-1…I-4 |
--import-connection |
php bin/console app:dbdump:check # все доступные стадии php bin/console app:dbdump:check --stage=static --fix # только конфиг, с автоправками php bin/console app:dbdump:check --stage=dump -s persons # дампы одной схемы php bin/console app:dbdump:check --tables-from=docker/database/analysis/dirty.json php bin/console app:dbdump:check --format=json --out=docker/database/analysis/check.json php bin/console app:dbdump:check --stage=import --import-connection=scratch
Опции: --stage (повторяемая; по умолчанию все, кроме plan), -s|--schema, --tables-from
(файл со списком schema.table или dirty.json), --config, --inventory, -c|--connection,
--import-connection, --fail-on=error|warning|note (порог кода возврата 1), --fix,
--format=text|json, --out.
Отчёт — один JSON: {generated_at, run_id, stages:{ran, why_skipped, ms, queries, …}, summary, coverage, findings[]}, у каждой находки есть stage. Узкие команды validate, check-criteria
и verify-dump остаются рабочими: это подмножества check с прежними опциями.
Документация из инструмента: docs
php bin/console app:dbdump:docs # → docker/database/analysis/docs/ php bin/console app:dbdump # рабочий цикл кратко + список команд
docs пишет четыре файла: WORKFLOW.md (рабочий цикл), DOSSIER.md (что лежит в
dossier.<schema>.json и что означают пометки ambiguous), COMMANDS.md (собран из определений
команд — те же опции, что в --help), FINDINGS.md (реестр кодов: стадия, уровень, смысл,
чинится ли --fix, кто решает). Это же кладут в проект для агента, чтобы он не гадал по
устаревшему мануалу.
Паритет мостов Symfony и Laravel
Ядро пакета общее, различаются только консольные мосты. Восемь исходных команд работают в обоих; новые — только Symfony, и Laravel-мост заморожен: новые зависимости в нём nullable, код PHP 7.2-совместимый.
| команда | Symfony | Laravel |
|---|---|---|
prepare-config, prepare-analysis, apply-analysis, validate, check-criteria/repair-configs, export, import, db-init |
да | да |
check (стадии static/live/plan/dump/import) |
да | нет |
checkpoint, --since-checkpoint, --since-migration |
да | нет |
docs, app:dbdump (рабочий цикл) |
да | нет |
verify-dump |
да | нет |
алиас apply, опции -s/--decisions/--legacy у apply-analysis |
да | нет |
Laravel-команда dbdump:apply-analysis применяет только старый вывод агента (analysis/out/*.json);
решения decisions.<schema>.json читает Symfony-мост.
Инкремент: checkpoint и грязный набор
php bin/console app:dbdump:checkpoint # что изменилось с прошлой отметки php bin/console app:dbdump:checkpoint --save # зафиксировать текущее состояние php bin/console app:dbdump:checkpoint --since-migration=Version20250101120000 php bin/console app:dbdump:check --since-checkpoint # проверять только изменившееся php bin/console app:dbdump:check --since-migration=Version20250101120000
Первый прогон — полный, дальше перепроверять всё незачем. Отметка
docker/database/analysis/checkpoint.json хранит самую новую миграцию, время слепка, коммит и
хеши по каждой таблице: настройки, состав колонок, набор кодов, внешние ключи. Времени мало —
конфиг могли поправить и вернуть, а в базе за неделю мог появиться новый status_id без единой
миграции; хеш это ловит, время нет.
Четыре сенсора:
| сенсор | что ловит | почему отдельно |
|---|---|---|
| миграции | версии новее отметки, с их DDL и наполнением | колонка появляется миграцией — основной путь изменений |
| конфиг | хеш настроек таблицы (порядок ключей не считается) | правку YAML руками не увидит никто другой |
| слепок | columns, codes+distinct_count, внешние ключи |
новое значение status_id приходит без миграции |
| git | файлы миграций, изменившиеся после коммита отметки | ловит откаты и правки уже применённых миграций |
Результат — docker/database/analysis/dirty.json с причиной по каждой таблице; он подаётся
дальше как есть (check --tables-from=…/dirty.json), а --since-checkpoint делает то же
одним флагом. Отдельно в отчёте видно таблицы, которые миграция завела, а конфиг не знает
(повод дозапустить prepare-config), и те, что исчезли из конфига после отметки.
Границы честности сенсоров. На схлопнутой истории (clone --depth=1, а в закрытом контуре
это норма) git diff по старому коммиту невозможен: сенсор докладывает, что отключён, и
работают остальные три — молча объявлять всё чистым он не станет. Слепок сравнивается тем же
кодом, что считает хеши для новой отметки, поэтому расхождение формул не превратится в вечно
грязный набор. Отметки нет — грязное всё, и это значит «нужен полный цикл», а не «изменений
не найдено».
--save — отдельный флаг: случайно перезаписать отметку нельзя, а посмотреть грязный набор
можно в любой момент.
Проверка конфига без БД (validate)
validate проверяет dump_config.yaml и пер-схемные dump-settings/*.yaml, не подключаясь
к базе. Схему берёт из замороженного слепка {data_dir}/analysis/schema_inventory.json
(и пер-схемных schema_inventory.<schema>.json), который кладёт prepare-analysis. Поэтому
команда работает в CI, в закрытом контуре и до подъёма стенда — в отличие от check-criteria,
который гоняет каждый criterion по живой БД.
# Symfony php bin/console app:dbdump:validate php bin/console app:dbdump:validate --format=json --out=docker/database/analysis/findings.json php bin/console app:dbdump:validate -s pdl -s tasks --severity=warning php bin/console app:dbdump:validate --fix # Laravel php artisan dbdump:validate php artisan dbdump:validate --format=json --out=docker/database/analysis/findings.json
Опции: --config и --inventory (пути, по умолчанию — {data_dir}/dump_config.yaml и
{data_dir}/analysis/schema_inventory.json), -s|--schema (повторяемая, только эти схемы),
--format=text|json, --out=PATH, --severity=error|warning|note (порог вывода),
--fix (применить механически однозначные правки).
Код возврата — 1, если есть находки уровня error, иначе 0. Этого достаточно, чтобы
скрипт или агент решил «готов конфиг к снятию дампа или нет», не разбирая текст вывода.
Что проверяется
| код | уровень | что нашли |
|---|---|---|
S-1 |
error | YAML не разобрался или файл из includes: пропал |
S-2 |
error | TableConfig отверг настройки таблицы |
S-3 |
error | таблица и в full_export, и в partial_export |
S-4 |
warning | пустая секция / пустая карта faker у таблицы |
C-1 |
warning | таблица есть в слепке, но не выгружается |
C-2 |
warning | таблица есть в конфиге, но её нет в слепке |
C-3 |
warning | схема есть на одной стороне и отсутствует на другой |
L-1…L-7 |
error / warning | несуществующие колонки в order_by, where, cascade_from, sample.criteria, stratify_by, faker, deferred_columns |
Q-1, Q-2 |
error | в критерии алиас таблицы (t1.) или bind-параметр (:name) — дампер такой критерий пропустит |
Q-3 |
error | непригодны ВСЕ критерии таблицы: выборка молча выродится в плоский срез |
Q-4 |
warning | повторяющиеся имена критериев |
Q-5 |
warning | сумма квот больше limit — объединённую выборку молча обрежет |
F-1 |
error / warning | faker-паттерн на нетекстовой колонке: на дате/bytea/uuid INSERT упадёт (error), на числовой — подменит идентификатор (warning) |
F-2 |
error | паттерн вне PatternDetector::ALLOWED_PATTERNS — FakerConfig не примет конфиг |
G-1 |
error | родитель cascade_from не выгружается — ограничение молча отбросится |
G-2 |
error | цикл в cascade_from |
G-3 |
warning | цепочка длиннее settings.max_cascade_depth |
G-4 |
warning | родитель с sample выгружается ПОЗЖЕ ребёнка |
G-5 |
note | родитель с sample выгружается раньше — связность держится на порядке имён |
G-6 |
note | у таблицы и sample, и cascade_from: cascade не применится |
D-1 |
note | справочник *_dict обрезан лимитом или квотами |
H-1 |
note | версионная таблица (date_from/date_to) выбирает только действующие версии |
G-4 заслуживает отдельного слова. Порядок экспорта строит TopologicalSorter по FK из
слепка; в базе без FK-констрейнтов рёбер нет вовсе, и порядок вырождается в алфавитный.
Тогда родитель, чьё имя идёт позже имени ребёнка, попадает в дамп уже после него, реестр
выбранных id (SelectedPkRegistry) пуст, и CascadeWhereResolver откатывается к подзапросу,
который не повторяет sample.criteria — набор строк родителя в дампе и в подзапросе расходится.
Что делает --fix
Только то, где правка однозначна и не меняет состав выборки: снимает faker-маппинг с
нетекстовой колонки (F-1) и с неизвестным паттерном (F-2), убирает маппинг несуществующей
колонки (L-6) и мёртвую запись cascade_from (L-3), переименовывает дубль критерия (Q-4),
удаляет пустую секцию (S-4). Всё остальное остаётся находкой с подсказкой — решение за
человеком.
Правки пишутся в тот файл, откуда пришла схема (dump-settings/<schema>.yaml или общий
конфиг); тронутый файл переписывается целиком, поэтому его форматирование становится
каноническим (записи cascade_from разворачиваются из однострочной формы в блочную).
Файлы, где чинить нечего, не трогаются. После правок содержимое перепроверяется тем же
TableConfig, что и на экспорте: если правка сделала конфиг хуже, файл не записывается.
Проверяется только основное подключение; секция connections: в аудит не входит.
Проверка выгруженных дампов (verify-dump) и отчёт импорта
verify-dump смотрит на то, что реально легло в {data_dir}/dumps/, — validate и
check-criteria туда не заглядывают. К БД не подключается; каждый файл читается один раз
для всех проверок, наружу уходят только счётчики и имена колонок — значений данных в отчёте
нет.
php bin/console app:dbdump:verify-dump php bin/console app:dbdump:verify-dump --format=json --out=docker/database/analysis/verify.json php bin/console app:dbdump:verify-dump -s persons --inventory=docker/database/analysis/schema_inventory.json
Опции: -s|--schema, -c|--connection, --inventory (слепок схемы, по умолчанию
{data_dir}/analysis/schema_inventory.json; без слепка проверки по нему пропускаются с
пометкой в отчёте), --format=text|json, --out=PATH. Код возврата — 1 при ошибках.
| код | уровень | что нашли |
|---|---|---|
V-1 |
error | строки ребёнка ссылаются на родителя, которого нет в выгрузке, — каскад не ограничил выборку; для ребра по FK из БД: импорт с констрейнтом такой дамп не примет |
V-2 |
warning | таблица есть в конфиге, файла дампа нет |
V-3 |
warning | родитель связи не выгружен (или FK ссылается на таблицу вне конфига) — сверять нечем |
V-4 |
error | колонки связи нет в дампе |
V-5 |
warning | покрытие значений: кодов/категорий колонки в дампе меньше, чем в слепке; в подсказке — какие коды отсутствуют (codes из pg_stats — только частые значения, поэтому не error) |
V-7 |
error / warning | колонка без faker, а значения выглядят как e-mail, телефон или ФИО (error); имя колонки говорит о ПД, а значений для проверки по содержимому мало (warning) |
V-8 |
error / warning | строк больше limit или суммы квот sample.criteria (error); full_export расходится со слепком (с допуском 10 %, если размер там — оценка) или дамп пуст при непустой таблице (warning) |
Рёбра для V-1 — объединение cascade_from и внешних ключей из слепка (foreign_keys):
сегодня в базе без констрейнтов второй список пуст, но появись FK — связь проверяется без
правки конфига.
import возвращает отчёт с теми же правилами игры: ошибки в нём дают код возврата 1,
даже если транзакция прошла; --out=PATH пишет отчёт в JSON.
| код | уровень | что нашли |
|---|---|---|
I-1 |
error | таблица пропущена: колонки дампа расходятся со схемой БД (с --ignore-schema-mismatch — warning, таблица залита) |
I-2 |
error | после заливки в таблице не столько строк, сколько в файле дампа |
I-3 |
warning | sequence отстаёт от максимума колонки (PostgreSQL) — первый INSERT приложения упрётся в дубликат ключа |
I-4 |
error | внешний ключ нарушен — в дампе строки без родителя; отложенные констрейнты проверяются SET CONSTRAINTS ALL IMMEDIATE внутри транзакции |
Настройка Symfony
Регистрация бандла
Flex-рецепта у пакета нет, поэтому бандл нужно зарегистрировать вручную. Добавьте в config/bundles.php:
return [ // ... Timbrs\DatabaseDumps\Bridge\Symfony\DatabaseDumpsBundle::class => ['dev' => true, 'test' => true], ];
Регистрация в dev/test соответствует установке через composer require --dev. Если пакет ставится в prod (без --dev), регистрируйте бандл как ['all' => true].
Настройки бандла
Все настройки живут в одном файле config/packages/database_dumps.yaml под корневым ключом
database_dumps:. Файл необязателен — без него работают дефолты.
⚠️ Если бандл зарегистрирован как
['dev' => true, 'test' => true](рекомендованный вариант выше), секцию ОБЯЗАТЕЛЬНО оборачивать вwhen@dev:/when@test:. Файлыconfig/packages/*.yamlгрузятся во всех окружениях, а расширенияdatabase_dumpsв prod нет — голый корневой ключ уронит сборку контейнера:There is no extension able to load the configuration for "database_dumps". Так же устроены штатныеweb_profiler.yamlиdebug.yaml. При регистрации['all' => true]обёртка не нужна.
# Вариант для бандла, зарегистрированного в dev/test. Значения одинаковые, # поэтому блок задан YAML-якорем. when@dev: &database_dumps database_dumps: data_dir: 'docker/database' llm: url: 'https://llm.example.com/v1' when@test: *database_dumps
Полный список ключей (здесь без обёртки, для случая ['all' => true]):
database_dumps: platform: 'postgresql' # или 'mysql', 'oracle' batch_size: 1000 # строк в одном INSERT sample_size: 200 # выборка для детекции ПД и профилирования max_cascade_depth: 10 # глубина каскадных подзапросов sample: per_value: 100 # квота корзины stratify_by по умолчанию # Базовый каталог: от него считаются dump_config.yaml, dump-settings/, dumps/, # analysis/ и хуки before_exec/after_exec. data_dir: 'docker/database' # Путь к главному конфигу выгрузки. По умолчанию — {data_dir}/dump_config.yaml. # config_path: '%kernel.project_dir%/docker/database/dump_config.yaml' llm: url: 'https://llm.example.com/v1' model: 'openai/gpt-oss-120b' timeout: 120 verify_ssl: true # enabled: не задано — auto: включено, если задан url # Бережный доступ к БД (см. раздел ниже). Времена — в миллисекундах, 0 — без ограничения. db: analyze_statement_timeout: 15000 # разведка: prepare-config, prepare-analysis, check-criteria export_statement_timeout: 1800000 # выгрузка: фаза 2 читает таблицу целиком lock_timeout: 2000 # дольше блокировку не ждём — чужую миграцию не подвешиваем idle_in_transaction_session_timeout: 60000 query_budget: 2000 # предохранитель: запросов к БД на один прогон разведки max_scan_rows: 50000 # выше — таблицу целиком не сканируем (COUNT(*), DISTINCT, сортировка) # Служебные таблицы фреймворков — не попадают в конфиг выгрузки. # Список задаётся целиком: доменное имя может совпасть со служебным. service_tables: exact: ['migrations', 'failed_jobs', 'sessions', 'messenger_messages'] prefix: ['horizon_', 'pulse_', 'sanctum_'] segments: ['log', 'logs', 'backup', 'test']
jobsбольше не служебная (1.1.34). Раньше это имя было в жёстком списке, и вместе с очередью Laravel из инвентаря молча пропадала бизнес-таблица с тем же именем. Очередь по-прежнему ловится поfailed_jobs/job_batches; если в вашем проектеjobs— это действительно очередь, добавьте её вservice_tables.exact. Указанный ключ заменяет умолчание целиком, а не дополняет его: перечисляйте полный список.
Токен сюда не кладётся. Он живёт в .env.local (DBDUMP_LLM_TOKEN), поэтому файл
безопасно коммитить. Переменные окружения DBDUMP_* перекрывают значения из этого файла.
Работают и оверрайды по окружениям — config/packages/dev/database_dumps.yaml.
Переезд конфига в 1.1.24. Раньше
data_dir,llmиopencodeлежали отдельным PHP-файломconfig/database-dumps.php, а бандл читал его в обход DI. Теперь это обычный конфиг бандла. При обновлении перенесите значения из старого файла вconfig/packages/database_dumps.yamlпод ключdatabase_dumps:и удалите старый — Symfony-бридж его больше не читает. В Laravel формат не изменился: там по-прежнему публикуемыйconfig/database-dumps.php.
Бережный доступ к БД (db)
Разведка (prepare-config, prepare-analysis, check-criteria) работает и на боевой базе, поэтому
пакет не читает данные там, где хватает статистики планировщика, и ограничивает всё остальное.
В Laravel те же ключи — секция db в config/database-dumps.php (env DBDUMP_DB_*).
- Профили сессии. При первом обращении к подключению в сессию уходят
SET statement_timeout,lock_timeout,idle_in_transaction_session_timeout(PostgreSQL; MySQL/MariaDB —max_execution_time/max_statement_time; Oracle — без таймаутов). Профильanalyze— короткий таймаут и бюджет запросов;exportвключает дампер (долгий таймаут, без бюджета);import— импортёр (без ограничений). КомандаlistБД не открывает:SETприменяется лениво. - Размер таблиц — из
pg_class.reltuples/pg_stat_user_tables.n_live_tup(MySQL —information_schema.tables.table_rows, Oracle —all_tables.num_rows), одним запросом на подключение. ТочныйCOUNT(*)— только если оценка не превышаетmax_scan_rows, либо с--exact-counts(полный проход по каждой таблице — не для боевой БД). Таблица без статистики получает находку P-1 («размер неизвестен, выполнитеANALYZEна стороне БД») и точно не считается никогда — именно она может оказаться самой большой;prepare-configкладёт её вpartial_export. - Профили колонок на PostgreSQL берутся из
pg_stats(null_frac,n_distinct, самые частые значения) — таблица не читается вовсе. Выборка строк делается только для колонок, которых в статистике нет (добавлены после последнегоANALYZE), и никогда не сортирует таблицу целиком:TABLESAMPLE BERNOULLIв пределахmax_scan_rows,SYSTEM— на больших; таблица неизвестного размера — голова. - Права. Колонка без права
SELECT— находка P-2:SELECT *при выгрузке упадёт, а вpg_statsтакой колонки нет, и без этой проверки «нет прав» неотличимо от «нет статистики». - Коды. Инвентарь для агента по-прежнему без значений данных; единственное исключение —
codesколонки: короткие ASCII-идентификаторы (1,-4,RED), не более 50 различных, повторяющиеся, не из колонки с ПД-именем и не дата. Они нужны, чтобы сверить enum из кода с тем, что реально лежит в базе.codes_complete: false—most_common_valsхранит только частые значения. - Бюджет. Профиль
analyzeсчитает запросы; при превышенииquery_budgetпрогон останавливается сQueryBudgetExceededException— сузьте анализ до схемы или поднимите бюджет. - Таймаут критерия.
check-criteriaотличает критерий, упавший поstatement_timeout(префиксtimeout:), от синтаксически неверного: первый нужно не чинить, а индексировать или сужать.
Инвентарь получает row_count_estimated, row_count_source и список warnings (P-1/P-2).
pgbouncer в режиме transaction pooling не сохраняет сессионный
SETмежду транзакциями — задайте таймауты на стороне пула.
Структура каталогов (Symfony)
your-symfony-project/
├── config/
│ └── packages/
│ └── database_dumps.yaml # настройки пакета (data_dir, llm, opencode)
└── docker/
└── database/ # = data_dir (по умолчанию)
├── dump_config.yaml # главный конфиг с includes
├── dump-settings/ # пер-схемные файлы конфига
│ ├── public.yaml
│ └── system.yaml
├── before_exec/ # скрипты до импорта
│ └── 01_prepare.sql
├── dumps/ # SQL-дампы
│ ├── public/
│ │ ├── users.sql
│ │ └── roles.sql
│ └── analytics/ # именованное подключение
│ └── analytics/
│ └── events.sql
└── after_exec/ # скрипты после импорта
└── 01_finalize.sql
Команды Symfony
# Экспорт всех таблиц php bin/console app:dbdump:export all # Экспорт одной таблицы php bin/console app:dbdump:export public.users # Экспорт только из одной схемы php bin/console app:dbdump:export all --schema=public # Экспорт из конкретного подключения php bin/console app:dbdump:export all --connection=analytics php bin/console app:dbdump:export all --connection=all # Импорт всех дампов php bin/console app:dbdump:import # Импорт с опциями php bin/console app:dbdump:import --skip-before --skip-after php bin/console app:dbdump:import --schema=public php bin/console app:dbdump:import --connection=all # Экспорт без каскадной фильтрации и без замены ПД php bin/console app:dbdump:export all --no-cascade --no-faker # Предпросмотр плана экспорта (без реального выполнения) php bin/console app:dbdump:export all --dry-run # Импорт с игнорированием расхождений схемы php bin/console app:dbdump:import --ignore-schema-mismatch # Сгенерировать конфигурацию по структуре БД php bin/console app:dbdump:prepare-config all php bin/console app:dbdump:prepare-config all --threshold=1000 --force php bin/console app:dbdump:prepare-config schema=billing php bin/console app:dbdump:prepare-config table=public.users php bin/console app:dbdump:prepare-config new --no-cascade --no-faker # Углублённый анализ php bin/console app:dbdump:prepare-config all --deep php bin/console app:dbdump:prepare-analysis # собрать пакет для агента php bin/console app:dbdump:prepare-analysis --exact-counts # точный COUNT(*) по каждой таблице (не для боевой БД) # или вручную: prepare-analysis → opencode run → apply-analysis # Проверить конфиг по слепку схемы, без подключения к БД php bin/console app:dbdump:validate php bin/console app:dbdump:validate --format=json --out=docker/database/analysis/findings.json php bin/console app:dbdump:validate --fix # Проверить sample.criteria на живой БД и починить падающие через opencode php bin/console app:dbdump:check-criteria # Проверить, что реально легло в dumps/: связи, покрытие значений, ПД без faker, число строк php bin/console app:dbdump:verify-dump php bin/console app:dbdump:verify-dump --format=json --out=docker/database/analysis/verify.json # Импорт с отчётом (I-1…I-4) в файл php bin/console app:dbdump:import --out=docker/database/analysis/import.json
Настройка Laravel
Регистрация провайдера
Сервис-провайдер подключается автоматически. Если нет — зарегистрируйте в config/app.php:
'providers' => [ // ... Timbrs\DatabaseDumps\Bridge\Laravel\DatabaseDumpsServiceProvider::class, ],
Публикация конфигурации
Чтобы изменить пути, опубликуйте PHP-конфигурацию:
php artisan vendor:publish --tag=database-dumps-config
Появится файл config/database-dumps.php:
return [ // config_path следует за data_dir — конфиг лежит рядом с дампами и анализом 'config_path' => base_path(env('DBDUMP_DATA_DIR', 'docker/database') . '/dump_config.yaml'), 'project_dir' => base_path(), 'data_dir' => env('DBDUMP_DATA_DIR', 'docker/database'), ];
Команды Laravel
# Экспорт всех таблиц php artisan dbdump:export all # Экспорт одной таблицы php artisan dbdump:export public.users # Экспорт только из одной схемы php artisan dbdump:export all --schema=public # Экспорт из конкретного подключения php artisan dbdump:export all --connection=analytics php artisan dbdump:export all --connection=all # Импорт всех дампов php artisan dbdump:import # Импорт с опциями php artisan dbdump:import --skip-before --skip-after php artisan dbdump:import --schema=public php artisan dbdump:import --connection=all # Экспорт без каскадной фильтрации и без замены ПД php artisan dbdump:export all --no-cascade --no-faker # Предпросмотр плана экспорта (без реального выполнения) php artisan dbdump:export all --dry-run # Импорт с игнорированием расхождений схемы php artisan dbdump:import --ignore-schema-mismatch # Сгенерировать конфигурацию по структуре БД php artisan dbdump:prepare-config all php artisan dbdump:prepare-config all --threshold=1000 --force php artisan dbdump:prepare-config schema=billing php artisan dbdump:prepare-config table=public.users php artisan dbdump:prepare-config new --no-cascade --no-faker # Углублённый анализ php artisan dbdump:prepare-config all --deep php artisan dbdump:prepare-analysis # собрать пакет для агента # или вручную: prepare-analysis → opencode run → apply-analysis # Проверить конфиг по слепку схемы, без подключения к БД php artisan dbdump:validate php artisan dbdump:validate --format=json --out=docker/database/analysis/findings.json php artisan dbdump:validate --fix # Проверить sample.criteria на живой БД и починить падающие через opencode php artisan dbdump:check-criteria
Скрипты before/after
Можно выполнять свои SQL-скрипты до и после импорта.
| Каталог | Когда выполняется |
|---|---|
docker/database/before_exec/ |
до импорта дампов |
docker/database/after_exec/ |
после импорта дампов |
Скрипты выполняются в алфавитном порядке. Используйте числовые префиксы для управления очерёдностью:
docker/database/before_exec/
├── 01_disable_triggers.sql
├── 02_prepare_temp.sql
docker/database/after_exec/
├── 01_enable_triggers.sql
├── 02_refresh_views.sql
Чтобы пропустить скрипты, используйте --skip-before и --skip-after:
php artisan dbdump:import --skip-before php artisan dbdump:import --skip-after php artisan dbdump:import --skip-before --skip-after
Поддержка IDE (JSON Schema)
В пакете есть JSON Schema для dump_config.yaml — файл resources/dump_config.schema.json. Он даёт автодополнение и валидацию в PHPStorm и других IDE.
Вариант 1: YAML-комментарий (рекомендуется)
Добавьте в начало dump_config.yaml:
# yaml-language-server: $schema=../vendor/timbrs/database-dumps/resources/dump_config.schema.json
Путь указывается относительно файла: для Symfony — относительно
config/, для Laravel — относительноdocker/database/.
Вариант 2: Настройка PHPStorm вручную
- Откройте Settings > Languages & Frameworks > Schemas and DTDs > JSON Schema Mappings
- Добавьте маппинг:
- Schema file:
vendor/timbrs/database-dumps/resources/dump_config.schema.json - File path pattern:
dump_config.yaml
- Schema file:
Архитектура
Как работает экспорт
Команда → TableConfigResolver → DatabaseDumper → [FK-сортировка + Cycle Breaking] → DataFetcher → [Cascade WHERE] → [Faker] → SqlGenerator → .sql файлы
- TableConfigResolver — читает YAML и собирает список таблиц для экспорта
- DatabaseDumper — управляет процессом экспорта
- TableDependencyResolver — топологическая сортировка таблиц по FK (родители экспортируются первыми). При обнаружении циклов — автоматический разрыв через nullable-рёбра
- DataFetcher — получает данные из БД через
ConnectionRegistry - CascadeWhereResolver — генерирует WHERE-подзапросы из
cascade_fromдля связности данных - RussianFaker — заменяет персональные данные (ФИО, email, телефон, пол) на сгенерированные
- SqlGenerator — генерирует SQL: TRUNCATE + INSERT (с NULL для разорванных FK) + сброс счётчиков + UPDATE (восстановление FK)
- Результат сохраняется в
docker/database/dumps/{schema}/{table}.sql
Как работает импорт
Команда → DatabaseImporter → ProductionGuard → TransactionManager → [FK-сортировка + Cycle Breaking] → [SchemaValidator] → ScriptExecutor → SqlParser → выполнение
- ProductionGuard — проверяет, что мы не на продакшене
- TransactionManager — оборачивает всё в транзакцию
- TableDependencyResolver — топологическая сортировка файлов по FK (родители импортируются первыми), с автоматическим разрывом циклов
- SchemaValidator — сравнивает столбцы дампа со столбцами в БД, предупреждает о расхождениях
- ScriptExecutor — выполняет скрипты из
before_exec/ - SqlParser / StatementSplitter — разбирает .sql файлы на отдельные выражения
- Выражения выполняются в БД
- ScriptExecutor — выполняет скрипты из
after_exec/
Различия платформ
Пакет сам генерирует правильный SQL в зависимости от СУБД:
| PostgreSQL | MySQL | Oracle (12c+) | |
|---|---|---|---|
| Имена таблиц | "table" (двойные кавычки) |
`table` (обратные кавычки) |
"TABLE" (двойные кавычки, UPPERCASE) |
| TRUNCATE | TRUNCATE ... CASCADE |
SET FOREIGN_KEY_CHECKS=0 |
DELETE FROM (FK-safe) |
| Счётчики | setval() / pg_get_serial_sequence() |
ALTER TABLE ... AUTO_INCREMENT |
Комментарий-заглушка (используйте after_exec/) |
| LIMIT | LIMIT N |
LIMIT N |
FETCH FIRST N ROWS ONLY |
| INSERT | Батч 1000 строк | Батч 1000 строк | По одной строке (нет multi-row INSERT) |
| Случайное число | RANDOM() |
RAND() |
DBMS_RANDOM.VALUE |
Платформа определяется автоматически по подключению к БД.
Oracle: используется
DELETE FROMвместоTRUNCATE TABLE, т.к. Oracle TRUNCATE не поддерживает CASCADE и блокируется FK constraints. Сброс sequences требует PL/SQL — используйте скрипты вdocker/database/after_exec/.
Структура исходного кода
src/
├── Adapter/ # Адаптеры подключений к БД
│ ├── DoctrineDbalAdapter.php # Doctrine DBAL
│ ├── LaravelDatabaseAdapter.php # Laravel DB
│ └── PdoAdapter.php # Универсальный PDO (Oracle и др.)
├── Bridge/ # Интеграции с фреймворками
│ ├── Laravel/
│ │ ├── Command/ # Artisan-команды
│ │ ├── DatabaseDumpsServiceProvider.php
│ │ └── LaravelLogger.php
│ └── Symfony/
│ ├── Command/ # Console-команды
│ ├── DependencyInjection/
│ ├── ConnectionRegistryFactory.php
│ ├── ConsoleLogger.php
│ └── DatabaseDumpsBundle.php
├── Config/ # Классы конфигурации
│ ├── DumpConfig.php # Общие настройки дампов
│ ├── EnvironmentConfig.php # Определение окружения
│ ├── FakerConfig.php # Настройки замены ПД
│ └── TableConfig.php # Настройки экспорта таблицы
├── Contract/ # Интерфейсы
├── Exception/ # Исключения
├── Platform/ # Поддержка SQL-диалектов
│ ├── MySqlPlatform.php
│ ├── OraclePlatform.php
│ ├── PostgresPlatform.php
│ └── PlatformFactory.php
├── Service/
│ ├── ConfigGenerator/ # Автогенерация конфигурации
│ │ ├── ConfigGenerator.php # Генератор dump_config.yaml
│ │ ├── ConfigSplitter.php # Разделение на per-schema файлы
│ │ └── ForeignKeyInspector.php # Инспекция FK: pg_catalog (PG), information_schema (MySQL)
│ ├── ConnectionRegistry.php # Реестр подключений
│ ├── Dumper/ # Экспорт дампов
│ │ ├── CascadeWhereResolver.php # Рекурсивная резолюция cascade WHERE
│ │ ├── DatabaseDumper.php # Основной экспортёр
│ │ └── DataFetcher.php # Загрузка данных из таблицы
│ ├── Faker/ # Замена персональных данных
│ │ ├── PatternDetector.php # Автодетекция паттернов ПД
│ │ └── RussianFaker.php # Генератор русских ФИО/email/телефонов
│ ├── Generator/ # Генерация SQL
│ │ ├── DeferredUpdateGenerator.php # UPDATE для восстановления разорванных FK
│ │ └── ...
│ ├── Graph/ # Граф FK-зависимостей
│ │ ├── SortResult.php # DTO результата сортировки с deferred-рёбрами
│ │ ├── TableDependencyResolver.php # FK-граф + топологическая сортировка
│ │ └── TopologicalSorter.php # Kahn (BFS) + Tarjan (SCC) + Cycle Breaking
│ ├── Importer/ # Импорт дампов
│ │ ├── SchemaValidator.php # Валидация столбцов дампа vs БД
│ │ ├── ValidationResult.php # DTO результата валидации
│ │ └── ...
│ ├── Parser/ # Разбор SQL
│ └── Security/ # Защита от продакшена
└── Util/
├── FileSystemHelper.php
└── YamlConfigLoader.php
Безопасность
Пакет не позволяет случайно импортировать дампы на продакшен. Импорт заблокирован, когда переменная окружения APP_ENV равна prod или predprod.
Тестирование
# Все тесты composer test # Тесты с покрытием кода composer test-coverage # Статический анализ (PHPStan level 8) composer phpstan # Исправление стиля кода composer cs-fix
Полный цикл на живой БД
tests/Integration/FullCycleTest.php прогоняет цепочку целиком: сид → слепок → досье →
решения → apply → check (static/live/plan) → export → check --stage=dump. Юнит-тесты
проверяют каждое звено с моками, а расходятся звенья как раз на стыках — на форме слепка, на том,
что решение адресует таблицу так же, как её называет конфиг, на порядке выгрузки. Без живой базы
такой стык не проверить.
Без DBDUMP_TEST_DSN тест пропускается — в закрытом контуре базы в CI нет:
DBDUMP_TEST_DSN='pgsql:host=127.0.0.1;port=5432;dbname=app' DBDUMP_TEST_USER=app DBDUMP_TEST_PASSWORD=app vendor/bin/phpunit tests/Integration
Тест работает в своей схеме (dbdump_it), которую сам создаёт и сносит в tearDown — чужие
таблицы не трогает. Подойдёт любой Postgres, в том числе поднятый локальным стендом хост-проекта
(docker-compose.local.yml, сервис postgres).
Локальная разработка
Чтобы подключить пакет из локальной папки (без Packagist), добавьте в composer.json вашего проекта:
{
"repositories": [
{
"type": "path",
"url": "../database-dumps"
}
],
"require": {
"timbrs/database-dumps": "*"
}
}
Затем выполните composer update timbrs/database-dumps — Composer создаст симлинк на локальный пакет.
Требования
Обязательные:
- PHP ^7.1 | ^8.0
symfony/yaml^4.4 | ^5.4 | ^6.0 | ^7.0symfony/finder^4.4 | ^5.4 | ^6.0 | ^7.0
Нижняя планка — 7.1: пакет ставится в том числе в проекты, которые с неё не сдвинулись. На
практике нужен 7.1.3 — столько требует Symfony 4.4, самая старая поддерживаемая ветка
symfony/yaml и symfony/finder.
Совместимость проверяется не на глаз, а сниффами:
composer compat
(PHPCompatibility по src/ с testVersion 7.1-). Это единственный способ поймать, например,
array_key_first() или флаг JSON_INVALID_UTF8_SUBSTITUTE — оба появились после 7.1 и оба уже
пробирались в код. Тесты и статический анализ такого не видят: они гоняются на современном PHP.
Опциональные (зависят от фреймворка):
| Зависимость | Для чего нужна |
|---|---|
doctrine/dbal ^2.13 | ^3.0 | ^4.0 |
Адаптер Doctrine DBAL (Symfony) |
symfony/console ^4.4 | ^5.4 | ^6.0 | ^7.0 |
Консольные команды Symfony |
symfony/http-kernel ^5.4 | ^6.0 | ^7.0 |
Регистрация бандла Symfony |
illuminate/support ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 |
Сервис-провайдер Laravel |
illuminate/console ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 |
Artisan-команды Laravel |
illuminate/database ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 |
Адаптер БД Laravel |
Лицензия
MIT License. Подробнее в файле LICENSE.
English
PHP package for exporting and importing database dumps as SQL. Supports PostgreSQL, MySQL, and Oracle (12c+). Works with Symfony, Laravel, and any PHP project.
Table of Contents
- Description
- Features
- Installation
- Quick Start
- Configuration
- Symfony Setup
- Laravel Setup
- Before/After Scripts
- IDE Support (JSON Schema)
- Architecture
- Security
- Testing
- Local Development
- Requirements
- License
Description
Database Dumps is a PHP package that helps developers create and deploy database dumps for local and test environments.
What problem does it solve? On a project with a large production database, developers need up-to-date test data — but copying the entire database is slow, and using real customer data in dev environments is not acceptable. This package lets you configure export rules once — which tables to grab in full, which partially — and forget about manual dump preparation.
How does it work? You describe export rules in a YAML file. The package then:
- generates SQL dumps from production (or staging) database, respecting FK dependencies between tables
- replaces personal data (Russian names, emails, phone numbers) with generated values, making dumps safe to use
- deploys dumps into the target database with a single command, with built-in protection against accidental runs on production
Where is it useful? In any PHP project using Symfony or Laravel (or standalone), where you need to:
- quickly set up a test database for developers
- share dumps across teams without leaking personal data
- keep seed data in the repository and update it from a real database
- work with multiple database connections simultaneously
Features
- No framework lock-in — works with Symfony, Laravel, and any PHP project
- PostgreSQL, MySQL & Oracle — automatically generates the right SQL for each database
- Multiple connections — export/import from several databases at once
- Batched INSERTs — automatically groups rows (1000 per statement)
- Rollback on errors — import runs inside a transaction
- Production guard — import is blocked when
APP_ENV=prod - Before/After scripts — run custom SQL before and after import
- Flexible config — YAML file with full and partial export rules
- Sequence reset — automatic sequence / auto-increment reset after import
- Auto-generate config —
prepare-configcommand creates YAML from DB structure - FK-aware ordering — automatic topological sorting of tables during export and import (parents first)
- Cascade dependencies —
cascade_fromgenerates WHERE subqueries to keep data consistent across FK relations - Personal data masking (faker) — automatic detection and replacement of PII (Russian names, email, phone, gender) during export
- Config splitting — automatic splitting of configuration into per-schema files
- Smart Cycle Breaking — automatic resolution of circular FK dependencies (two-phase INSERT NULL → UPDATE) instead of falling back to alphabetical order
- Self-referential FK — support for self-referencing tables (category trees, org structures, threaded comments)
- Schema validation on import — warnings when dump columns don't match the DB,
--ignore-schema-mismatchoption - Config settings —
settings:section for batch_size, sample_size, max_cascade_depth - Dry-run export —
--dry-runoption to preview the export plan without executing
Installation
composer require --dev timbrs/database-dumps
Quick Start
Symfony
- Register the bundle manually in
config/bundles.php(there is no Flex recipe):
Timbrs\DatabaseDumps\Bridge\Symfony\DatabaseDumpsBundle::class => ['dev' => true, 'test' => true],
- Create
docker/database/dump_config.yaml(default path; override viaconfig_path):
full_export: public: - users - roles partial_export: public: clients: limit: 1000 order_by: created_at DESC
- Export dumps:
php bin/console app:dbdump:export all
- Import dumps:
php bin/console app:dbdump:import
Laravel
-
The service provider is discovered automatically. The file
docker/database/dump_config.yamlis created on first run. -
Edit
docker/database/dump_config.yaml(same format as Symfony). -
Export dumps:
php artisan dbdump:export all
- Import dumps:
php artisan dbdump:import
Configuration
Export is configured via a YAML file with two sections: full_export (all rows) and partial_export (with limits).
Full Export
Exports all rows from listed tables:
full_export: public: # schema name - users # tables — all rows - roles system: - settings
Partial Export
Exports a limited number of rows with filtering:
partial_export: public: clients: limit: 1000 # max rows order_by: created_at DESC # sorting where: "is_active = true" # WHERE condition orders: limit: 5000 order_by: id DESC
Available options:
| Option | Description |
|---|---|
limit |
Max rows |
order_by |
Sorting (must end with ASC or DESC) |
where |
WHERE condition |
cascade_from |
Cascade filtering by FK parent (see below) |
Cascade Dependencies (cascade_from)
When partially exporting related tables, data can become inconsistent: a child table may reference rows that didn't make it into the parent's dump. The cascade_from option solves this by automatically generating a WHERE subquery that limits the selection to only those rows whose FK parent is present in the dump.
partial_export: public: users: limit: 500 order_by: id DESC orders: limit: 1000 order_by: created_at DESC cascade_from: - parent: public.users fk_column: user_id parent_column: id order_items: limit: 5000 order_by: id DESC cascade_from: - parent: public.orders fk_column: order_id parent_column: id
In this example:
ordersexports only orders whoseuser_idexists among exportedusersorder_itemsexports only items belonging to exportedorders- Subqueries are nested:
order_items->orders->users(up to 10 levels deep)
The prepare-config command automatically detects FK dependencies and generates cascade_from. To disable: --no-cascade.
Personal Data Masking (faker)
The package can automatically detect and replace personal data during export. This allows safe use of dumps in dev/staging environments.
Supported patterns:
| Pattern | Description | Original example | Replacement example |
|---|---|---|---|
fio |
Full Russian name | Иванов Иван Иванович | Петров Александр Сергеевич |
fio_short |
Short Russian name | Иванов И.И. | Козлов А.В. |
name |
First and last name | Иванов Иван | Петров Александр |
firstname |
First name (cross-correlated with composite column) | Иван | Александр |
lastname |
Last name (cross-correlated with composite column) | Иванов | Петров |
patronymic |
Patronymic (cross-correlated with composite column) | Иванович | Сергеевич |
email |
Email address | ivan@company.ru | aleksandr.petrov42@example.com |
phone |
Phone number | +79161234567 | +79234567890 |
gender |
Gender (12 formats: male/female, m/f, м/ж, etc.) | Мужской | Женский |
inn |
Russian tax number (10 or 12 digits, checksum recomputed) | 7707083893 | 7712045619 |
ogrn |
Russian state registration number (13 or 15 digits) | 1027700132195 | 1024306718432 |
digits |
Any numeric identifier, length and separators preserved (KPP, OKPO, OKATO, OKTMO, SNILS) | 770701001 | 418362905 |
birth_date |
Birth date (shifted by ±180 days, original format preserved) | 1985-04-17 | 1985-09-02 |
doc_series |
Document series | 4509 | 2731 |
doc_number |
Document number | 123456 | 804517 |
The faker section in configuration:
faker: public: users: full_name: fio display_name: name first_name: firstname last_name: lastname middle_name: patronymic email: email phone: phone sex: gender employees: fio: fio short_fio: fio_short contact_email: email
The inn, ogrn, digits, birth_date, doc_series and doc_number patterns are value-seeded: the same input always produces the same output, in every table and every run. Otherwise one tax number would map to different values in clients and in leads, breaking the link between records in the dump. Length, separators and date format are preserved, so these patterns also fit numeric (bigint) and date/timestamp columns.
The firstname, lastname, and patronymic patterns are detected via cross-correlation: if a composite column (fio, fio_short, name) is already found in the table and there's an adjacent column with individual first names/last names/patronymics — it will be detected automatically.
The gender pattern is detected by matching both the column name (gender, sex, пол) and its contents (valid values: male/female, m/f, м/ж, мужской/женский, муж/жен, мужчина/женщина). The original value's case and format are preserved during replacement.
The prepare-config command automatically analyzes table contents and generates the faker section when PII patterns are detected in columns (threshold: 80% match from 200 random rows). To disable: --no-faker.
Replacement is deterministic — the seed is based on the hash of the FIO value (column with fio pattern), if such a column exists in the table's faker config. If there is no fio column, the seed is computed from the hash of all faker column values in the row. This ensures the same person always gets the same replacement regardless of the table or run.
Config Splitting by Schema (includes)
When dealing with many tables, the configuration can become unwieldy. The prepare-config command splits config into per-schema files by default. The main dump_config.yaml lives in docker/database/, and per-schema files go into the docker/database/dump-settings/ subdirectory:
docker/database/
├── dump_config.yaml # main file with includes
└── dump-settings/
├── public.yaml # public schema config
├── system.yaml # system schema config
└── analytics/ # named connection
└── analytics.yaml
Main file (docker/database/dump_config.yaml):
includes: public: ./dump-settings/public.yaml system: ./dump-settings/system.yaml connections: analytics: includes: analytics: ./dump-settings/analytics/analytics.yaml
💡 Include paths are written with a
./prefix so PhpStorm/IDEs treat them as file references:Ctrl+B/Cmd+B(or Ctrl+click) on a path jumps straight to the target*.yaml. The prefix does not affect path resolution (both./-prefixed and plain relative paths load fine).
Schema file (docker/database/dump-settings/public.yaml):
full_export: - users - roles partial_export: clients: limit: 1000 order_by: created_at DESC faker: users: full_name: fio email: email
To generate a single file without splitting: --no-split.
Multiple Connections
To work with several databases, add a connections section:
# Main connection full_export: public: - users - roles partial_export: public: posts: limit: 100 # Additional connections connections: analytics: # connection name (as in framework config) full_export: analytics: - events - metrics partial_export: analytics: logs: limit: 50 order_by: id DESC
Where dumps are saved:
- Main connection:
{data_dir}/dumps/{schema}/{table}.sql - Named connection:
{data_dir}/dumps/{connection}/{schema}/{table}.sql
data_dir is the base data directory (default docker/database). Everything the package
produces is resolved from it: the main config ({data_dir}/dump_config.yaml), per-schema files
({data_dir}/dump-settings), dumps ({data_dir}/dumps), analysis ({data_dir}/analysis) and
hooks ({data_dir}/before_exec, {data_dir}/after_exec). Change it in
the data_dir key of the settings file — config/packages/database_dumps.yaml (Symfony) /
config/database-dumps.php (Laravel) — or via the DBDUMP_DATA_DIR env var. In prod the default docker/database is always used.
Default changed in 1.1.23. It used to be
database, and the path todump_config.yamlwas a separate hardcoded constant that did not followdata_dir. The default is nowdocker/database, and the config lives insidedata_dirnext to the dumps. An installation that relied on the old default (nodata_dirkey set) will stop seeing its files after the upgrade — setdata_dir: 'database'or move the directory.
The --connection option:
# Main connection only (default) php artisan dbdump:export all # Specific connection only php artisan dbdump:export all --connection=analytics # All connections at once php artisan dbdump:export all --connection=all
Settings
The settings section allows you to customize internal package parameters:
settings: batch_size: 1000 # rows per INSERT statement (default: 1000) sample_size: 200 # rows for PII analysis in PatternDetector (default: 200) max_cascade_depth: 10 # max depth for cascade_from subqueries (default: 10) sample: per_value: 100 # default per-bucket quota for stratify_by (default: 100)
Auto-generate Configuration
The prepare-config command looks at your DB structure and creates or updates dump_config.yaml. The required mode argument defines the scope:
# Symfony php bin/console app:dbdump:prepare-config all # Full regeneration php bin/console app:dbdump:prepare-config schema=billing # Regenerate one schema php bin/console app:dbdump:prepare-config table=public.users # Regenerate one table php bin/console app:dbdump:prepare-config new # Add only new tables # Laravel php artisan dbdump:prepare-config all php artisan dbdump:prepare-config schema=billing php artisan dbdump:prepare-config table=public.users php artisan dbdump:prepare-config new
Modes:
| Mode | Description |
|---|---|
all |
Full config regeneration. Refuses over an existing config — --force required |
schema=<name> |
Regenerate one schema, merge into existing config |
table=<schema.table> |
Regenerate one table, merge into existing config |
new |
Detect and append new tables only (doesn't touch existing entries) |
all mode rebuilds the config from scratch and never reads the existing file: hand-tuned
sample.criteria, cascade_from, limit and faker are replaced by machine guesses. That is why
the command refuses to run over an existing dump_config.yaml and points at the modes that merge
instead of overwriting (new, schema=, table=), plus check-criteria and validate.
A deliberate rebuild from scratch is prepare-config all --force.
Options:
| Option | Description | Default |
|---|---|---|
--threshold, -t |
Row threshold: tables with rows <= threshold go to full_export, more — to partial_export | 500 |
--force, -f |
Allow full regeneration over an existing config (only for all mode) |
— |
--no-cascade |
Skip FK detection and cascade_from generation |
— |
--no-faker |
Skip personal data detection | — |
--no-split |
Generate a single YAML without splitting by schema | — |
How tables are sorted:
- Rows <= threshold —
full_export - Rows > threshold —
partial_export(with limit, auto-detected sorting andwhere: "1=1"template for easy customization) - Empty tables — skipped
- Service tables (migrations, sessions, cache_, telescope_, oauth_, audit_) — skipped
Symfony Setup
Bundle Registration
The package ships no Flex recipe, so the bundle must be registered manually. Add it to config/bundles.php:
return [ // ... Timbrs\DatabaseDumps\Bridge\Symfony\DatabaseDumpsBundle::class => ['dev' => true, 'test' => true], ];
Registering under dev/test matches installation via composer require --dev. If the package is installed for prod (without --dev), register the bundle as ['all' => true].
Bundle Settings
All settings live in a single config/packages/database_dumps.yaml under the database_dumps:
root key. The file is optional — defaults apply without it.
⚠️ If the bundle is registered as
['dev' => true, 'test' => true](the recommended setup above), the section MUST be wrapped inwhen@dev:/when@test:. Files underconfig/packages/*.yamlare loaded in every environment, and there is nodatabase_dumpsextension in prod — a bare root key breaks the container build:There is no extension able to load the configuration for "database_dumps". The built-inweb_profiler.yamlanddebug.yamlwork the same way. With['all' => true]no wrapper is needed.
# For a bundle registered in dev/test. The values are identical, # so the block is shared through a YAML anchor. when@dev: &database_dumps database_dumps: data_dir: 'docker/database' llm: url: 'https://llm.example.com/v1' when@test: *database_dumps
Full key list (shown without the wrapper, i.e. for ['all' => true]):
database_dumps: platform: 'postgresql' # or 'mysql', 'oracle' batch_size: 1000 # rows per INSERT sample_size: 200 # sample for PII detection and profiling max_cascade_depth: 10 # cascade subquery depth # Base directory: dump_config.yaml, dump-settings/, dumps/, analysis/ # and the before_exec/after_exec hooks are all resolved from it. data_dir: 'docker/database' # Path to the main dump config. Defaults to {data_dir}/dump_config.yaml. # config_path: '%kernel.project_dir%/docker/database/dump_config.yaml' llm: url: 'https://llm.example.com/v1' model: 'openai/gpt-oss-120b' timeout: 120 verify_ssl: true # enabled: unset — auto: on when url is set # Gentle database access: per-profile session timeouts (analyze / export / import), # a query budget for discovery and the row threshold above which a table is never # scanned in full (COUNT(*), DISTINCT, sorting). Milliseconds; 0 — unlimited. db: analyze_statement_timeout: 15000 export_statement_timeout: 1800000 lock_timeout: 2000 idle_in_transaction_session_timeout: 60000 query_budget: 2000 max_scan_rows: 50000
Row counts come from planner statistics (pg_class.reltuples, information_schema.tables.table_rows,
all_tables.num_rows); an exact COUNT(*) runs only when the estimate fits in max_scan_rows or with
prepare-analysis --exact-counts. On PostgreSQL column profiles are read from pg_stats and the table
itself is not touched; row samples use TABLESAMPLE, never ORDER BY RANDOM(). Tables without
statistics are reported as P-1, columns without SELECT privilege as P-2. The only data values
that reach the inventory are codes — short ASCII identifiers of low-cardinality columns after a PII gate.
The token does not go here. It lives in .env.local (DBDUMP_LLM_TOKEN), so the file is safe
to commit. DBDUMP_* environment variables override values from this file. Per-environment
overrides work too — config/packages/dev/database_dumps.yaml.
Config moved in 1.1.24.
data_dir,llmandopencodeused to live in a separate PHP file,config/database-dumps.php, which the bundle read bypassing DI. It is now an ordinary bundle config. When upgrading, move the values from the old file intoconfig/packages/database_dumps.yamlunder thedatabase_dumps:key and delete the old one — the Symfony bridge no longer reads it. Laravel is unchanged: still the publishedconfig/database-dumps.php.
Directory Structure (Symfony)
your-symfony-project/
├── config/
│ └── packages/
│ └── database_dumps.yaml # package settings (data_dir, llm, opencode)
└── docker/
└── database/ # = data_dir (default)
├── dump_config.yaml # main config with includes
├── dump-settings/ # per-schema config files
│ ├── public.yaml
│ └── system.yaml
├── before_exec/ # pre-import scripts
│ └── 01_prepare.sql
├── dumps/ # SQL dumps
│ ├── public/
│ │ ├── users.sql
│ │ └── roles.sql
│ └── analytics/ # named connection
│ └── analytics/
│ └── events.sql
└── after_exec/ # post-import scripts
└── 01_finalize.sql
Symfony Commands
# Export all tables php bin/console app:dbdump:export all # Export one table php bin/console app:dbdump:export public.users # Export from one schema only php bin/console app:dbdump:export all --schema=public # Export from a specific connection php bin/console app:dbdump:export all --connection=analytics php bin/console app:dbdump:export all --connection=all # Import all dumps php bin/console app:dbdump:import # Import with options php bin/console app:dbdump:import --skip-before --skip-after php bin/console app:dbdump:import --schema=public php bin/console app:dbdump:import --connection=all # Export without cascade filtering and without PII replacement php bin/console app:dbdump:export all --no-cascade --no-faker # Preview export plan (no actual export) php bin/console app:dbdump:export all --dry-run # Import ignoring schema mismatches php bin/console app:dbdump:import --ignore-schema-mismatch # Generate config from DB structure php bin/console app:dbdump:prepare-config all php bin/console app:dbdump:prepare-config all --threshold=1000 --force php bin/console app:dbdump:prepare-config schema=billing php bin/console app:dbdump:prepare-config table=public.users php bin/console app:dbdump:prepare-config new --no-cascade --no-faker
Verifying exported dumps (verify-dump) and the import report
verify-dump inspects what actually landed in {data_dir}/dumps/ — neither validate nor
check-criteria looks there. It never connects to the database: every dump file is read once
for all checks, and only counters and column names leave the file — no data values appear in
the report.
php bin/console app:dbdump:verify-dump php bin/console app:dbdump:verify-dump --format=json --out=docker/database/analysis/verify.json php bin/console app:dbdump:verify-dump -s persons --inventory=docker/database/analysis/schema_inventory.json
Options: -s|--schema, -c|--connection, --inventory (schema inventory, default
{data_dir}/analysis/schema_inventory.json; without it inventory-based checks are skipped and
the report says so), --format=text|json, --out=PATH. Exit code is 1 on errors.
| code | level | finding |
|---|---|---|
V-1 |
error | child rows reference a parent that is not in the export — the cascade did not restrict the selection; for an edge coming from a DB foreign key the import would reject such a dump |
V-2 |
warning | table is configured, dump file is missing |
V-3 |
warning | the parent of a relation was not exported (or a FK points to a table outside the config) — nothing to compare with |
V-4 |
error | relation column is missing from the dump |
V-5 |
warning | value coverage: fewer codes/categories in the dump than in the inventory; the suggestion names the missing codes (codes from pg_stats are only the frequent values, hence not an error) |
V-7 |
error / warning | a column without faker whose values look like e-mails, phones or full names (error); a PII-named column with too few values to judge by content (warning) |
V-8 |
error / warning | more rows than limit or the sum of sample.criteria quotas (error); full_export disagrees with the inventory (10 % tolerance when the size there is an estimate) or the dump is empty while the table is not (warning) |
Edges for V-1 are the union of cascade_from and the inventory foreign_keys: in a
constraint-less database the second list is empty today, but once FKs appear the relation is
checked without touching the config.
import returns a report with the same rules: errors in it produce exit code 1 even when the
transaction succeeded; --out=PATH writes the report as JSON.
| code | level | finding |
|---|---|---|
I-1 |
error | table skipped: dump columns differ from the DB schema (--ignore-schema-mismatch downgrades it to a warning and loads the table) |
I-2 |
error | after loading, the table holds a different number of rows than the dump file |
I-3 |
warning | a sequence lags behind the column maximum (PostgreSQL) — the application's first INSERT will hit a duplicate key |
I-4 |
error | foreign key violated — rows without a parent; deferred constraints are forced with SET CONSTRAINTS ALL IMMEDIATE inside the transaction |
Laravel Setup
Provider Registration
The service provider is discovered automatically. If not, register it in config/app.php:
'providers' => [ // ... Timbrs\DatabaseDumps\Bridge\Laravel\DatabaseDumpsServiceProvider::class, ],
Publishing Configuration
To customize paths, publish the PHP config:
php artisan vendor:publish --tag=database-dumps-config
This creates config/database-dumps.php:
return [ // config_path следует за data_dir — конфиг лежит рядом с дампами и анализом 'config_path' => base_path(env('DBDUMP_DATA_DIR', 'docker/database') . '/dump_config.yaml'), 'project_dir' => base_path(), 'data_dir' => env('DBDUMP_DATA_DIR', 'docker/database'), ];
Laravel Commands
# Export all tables php artisan dbdump:export all # Export one table php artisan dbdump:export public.users # Export from one schema only php artisan dbdump:export all --schema=public # Export from a specific connection php artisan dbdump:export all --connection=analytics php artisan dbdump:export all --connection=all # Import all dumps php artisan dbdump:import # Import with options php artisan dbdump:import --skip-before --skip-after php artisan dbdump:import --schema=public php artisan dbdump:import --connection=all # Export without cascade filtering and without PII replacement php artisan dbdump:export all --no-cascade --no-faker # Preview export plan (no actual export) php artisan dbdump:export all --dry-run # Import ignoring schema mismatches php artisan dbdump:import --ignore-schema-mismatch # Generate config from DB structure php artisan dbdump:prepare-config all php artisan dbdump:prepare-config all --threshold=1000 --force php artisan dbdump:prepare-config schema=billing php artisan dbdump:prepare-config table=public.users php artisan dbdump:prepare-config new --no-cascade --no-faker
Before/After Scripts
You can run custom SQL scripts before and after import.
| Directory | When it runs |
|---|---|
docker/database/before_exec/ |
before importing dumps |
docker/database/after_exec/ |
after importing dumps |
Scripts run in alphabetical order. Use numeric prefixes to control the order:
docker/database/before_exec/
├── 01_disable_triggers.sql
├── 02_prepare_temp.sql
docker/database/after_exec/
├── 01_enable_triggers.sql
├── 02_refresh_views.sql
To skip scripts, use --skip-before and --skip-after:
php artisan dbdump:import --skip-before php artisan dbdump:import --skip-after php artisan dbdump:import --skip-before --skip-after
IDE Support (JSON Schema)
The package includes a JSON Schema for dump_config.yaml at resources/dump_config.schema.json. It provides autocompletion and validation in PHPStorm and other IDEs.
Option 1: YAML comment (recommended)
Add to the top of your dump_config.yaml:
# yaml-language-server: $schema=../vendor/timbrs/database-dumps/resources/dump_config.schema.json
The path is relative to the file: for Symfony — relative to
config/, for Laravel — relative todocker/database/.
Option 2: PHPStorm manual setup
- Open Settings > Languages & Frameworks > Schemas and DTDs > JSON Schema Mappings
- Add a mapping:
- Schema file:
vendor/timbrs/database-dumps/resources/dump_config.schema.json - File path pattern:
dump_config.yaml
- Schema file:
Architecture
How Export Works
Command → TableConfigResolver → DatabaseDumper → [FK sorting + Cycle Breaking] → DataFetcher → [Cascade WHERE] → [Faker] → SqlGenerator → .sql files
- TableConfigResolver — reads YAML and builds a list of tables to export
- DatabaseDumper — manages the export process
- TableDependencyResolver — topological sorting of tables by FK (parents are exported first). Circular dependencies are automatically broken via nullable edges
- DataFetcher — fetches data from the DB via
ConnectionRegistry - CascadeWhereResolver — generates WHERE subqueries from
cascade_fromfor data consistency - RussianFaker — replaces personal data (names, email, phone, gender) with generated values
- SqlGenerator — generates SQL: TRUNCATE + INSERT (with NULL for broken FKs) + counter reset + UPDATE (FK restoration)
- Result is saved to
docker/database/dumps/{schema}/{table}.sql
How Import Works
Command → DatabaseImporter → ProductionGuard → TransactionManager → [FK sorting + Cycle Breaking] → [SchemaValidator] → ScriptExecutor → SqlParser → execution
- ProductionGuard — checks we're not on production
- TransactionManager — wraps everything in a transaction
- TableDependencyResolver — topological sorting of files by FK (parents are imported first), with automatic cycle breaking
- SchemaValidator — compares dump columns against DB columns, warns about mismatches
- ScriptExecutor — runs scripts from
before_exec/ - SqlParser / StatementSplitter — splits .sql files into individual statements
- Statements are executed against the DB
- ScriptExecutor — runs scripts from
after_exec/
Platform Differences
The package generates the right SQL depending on the database:
| PostgreSQL | MySQL | Oracle (12c+) | |
|---|---|---|---|
| Table names | "table" (double quotes) |
`table` (backticks) |
"TABLE" (double quotes, UPPERCASE) |
| TRUNCATE | TRUNCATE ... CASCADE |
SET FOREIGN_KEY_CHECKS=0 |
DELETE FROM (FK-safe) |
| Counters | setval() / pg_get_serial_sequence() |
ALTER TABLE ... AUTO_INCREMENT |
Stub comment (use after_exec/) |
| LIMIT | LIMIT N |
LIMIT N |
FETCH FIRST N ROWS ONLY |
| INSERT | Batch 1000 rows | Batch 1000 rows | One row per INSERT (no multi-row INSERT) |
| Random function | RANDOM() |
RAND() |
DBMS_RANDOM.VALUE |
The platform is detected automatically from the DB connection.
Oracle:
DELETE FROMis used instead ofTRUNCATE TABLEbecause Oracle TRUNCATE doesn't support CASCADE and is blocked by FK constraints. Sequence reset requires PL/SQL — use scripts indocker/database/after_exec/.
Source Directory Structure
src/
├── Adapter/ # DB connection adapters
│ ├── DoctrineDbalAdapter.php # Doctrine DBAL
│ ├── LaravelDatabaseAdapter.php # Laravel DB
│ └── PdoAdapter.php # Universal PDO (Oracle, etc.)
├── Bridge/ # Framework integrations
│ ├── Laravel/
│ │ ├── Command/ # Artisan commands
│ │ ├── DatabaseDumpsServiceProvider.php
│ │ └── LaravelLogger.php
│ └── Symfony/
│ ├── Command/ # Console commands
│ ├── DependencyInjection/
│ ├── ConnectionRegistryFactory.php
│ ├── ConsoleLogger.php
│ └── DatabaseDumpsBundle.php
├── Config/ # Configuration classes
│ ├── DumpConfig.php # Overall dump settings
│ ├── EnvironmentConfig.php # Environment detection
│ ├── FakerConfig.php # PII masking settings
│ └── TableConfig.php # Per-table export settings
├── Contract/ # Interfaces
├── Exception/ # Exceptions
├── Platform/ # SQL dialect support
│ ├── MySqlPlatform.php
│ ├── OraclePlatform.php
│ ├── PostgresPlatform.php
│ └── PlatformFactory.php
├── Service/
│ ├── ConfigGenerator/ # Config auto-generation
│ │ ├── ConfigGenerator.php # dump_config.yaml generator
│ │ ├── ConfigSplitter.php # Splitting into per-schema files
│ │ └── ForeignKeyInspector.php # FK inspection: pg_catalog (PG), information_schema (MySQL)
│ ├── ConnectionRegistry.php # Connection registry
│ ├── Dumper/ # Dump export
│ │ ├── CascadeWhereResolver.php # Recursive cascade WHERE resolution
│ │ ├── DatabaseDumper.php # Main exporter
│ │ └── DataFetcher.php # Table data loading
│ ├── Faker/ # Personal data masking
│ │ ├── PatternDetector.php # Automatic PII pattern detection
│ │ └── RussianFaker.php # Russian names/email/phone generator
│ ├── Generator/ # SQL generation
│ │ ├── DeferredUpdateGenerator.php # UPDATE for restoring broken FK edges
│ │ └── ...
│ ├── Graph/ # FK dependency graph
│ │ ├── SortResult.php # DTO for sort result with deferred edges
│ │ ├── TableDependencyResolver.php # FK graph + topological sorting
│ │ └── TopologicalSorter.php # Kahn (BFS) + Tarjan (SCC) + Cycle Breaking
│ ├── Importer/ # Dump import
│ │ ├── SchemaValidator.php # Dump columns vs DB schema validation
│ │ ├── ValidationResult.php # Validation result DTO
│ │ └── ...
│ ├── Parser/ # SQL parsing
│ └── Security/ # Production guard
└── Util/
├── FileSystemHelper.php
└── YamlConfigLoader.php
Security
The package prevents accidental imports on production. Import is blocked when the APP_ENV environment variable is prod or predprod.
Testing
# All tests composer test # Tests with code coverage composer test-coverage # Static analysis (PHPStan level 8) composer phpstan # Code style fix composer cs-fix
Local Development
To use the package from a local folder (without Packagist), add to your project's composer.json:
{
"repositories": [
{
"type": "path",
"url": "../database-dumps"
}
],
"require": {
"timbrs/database-dumps": "*"
}
}
Then run composer update timbrs/database-dumps — Composer will create a symlink to the local package.
Requirements
Required:
- PHP ^7.2 | ^8.0
symfony/yaml^4.4 | ^5.4 | ^6.0 | ^7.0symfony/finder^4.4 | ^5.4 | ^6.0 | ^7.0
Optional (depends on framework):
| Dependency | What it's for |
|---|---|
doctrine/dbal ^2.13 | ^3.0 | ^4.0 |
Doctrine DBAL adapter (Symfony) |
symfony/console ^4.4 | ^5.4 | ^6.0 | ^7.0 |
Symfony console commands |
symfony/http-kernel ^5.4 | ^6.0 | ^7.0 |
Symfony bundle registration |
illuminate/support ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 |
Laravel service provider |
illuminate/console ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 |
Laravel artisan commands |
illuminate/database ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 |
Laravel DB adapter |
License
MIT License. See LICENSE for details.
Developed by Timur Bayan (Timbrs).