Search by

timbrs / database-dumps

backbista

Framework-agnostic PHP package for managing database dumps with SQL exports/imports, supporting PostgreSQL, MySQL and Oracle

Package info

github.com/timbrs/php-database-dumps

pkg:composer/timbrs/database-dumps

Statistics

Installs: 80

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.49 2026-09-29 07:00 UTC

README

Tests PHP Version License

Русский | English

Русский язык

PHP-пакет для экспорта и импорта дампов баз данных в SQL. Поддерживает PostgreSQL, MySQL и Oracle (12c+). Работает с Symfony, Laravel и любым PHP-проектом.

Оглавление

Описание

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 ещё не заданы, команда спросит:

  1. Настроить LLM сейчас? — да/нет (по умолчанию да);
  2. API URL — базовый адрес OpenAI-совместимого API (например https://gpt.example.com/v1);
  3. Модель — имя модели (по умолчанию openai/gpt-oss-120b);
  4. 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

  1. Зарегистрируйте бандл вручную в config/bundles.php (Flex-рецепта нет):
Timbrs\DatabaseDumps\Bridge\Symfony\DatabaseDumpsBundle::class => ['dev' => true, 'test' => true],
  1. Создайте файл docker/database/dump_config.yaml (путь по умолчанию; переопределяется ключом config_path):
full_export:
  public:
    - users
    - roles

partial_export:
  public:
    clients:
      limit: 1000
      order_by: created_at DESC
  1. Экспортируйте дампы:
php bin/console app:dbdump:export all
  1. Импортируйте дампы:
php bin/console app:dbdump:import

Laravel

  1. Сервис-провайдер подключается автоматически. Файл docker/database/dump_config.yaml создаётся при первом запуске.

  2. Отредактируйте docker/database/dump_config.yaml (формат тот же, что и для Symfony).

  3. Экспортируйте дампы:

php artisan dbdump:export all
  1. Импортируйте дампы:
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 есть среди экспортированных users
  • order_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 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 вручную

  1. Откройте Settings > Languages & Frameworks > Schemas and DTDs > JSON Schema Mappings
  2. Добавьте маппинг:
    • Schema file: vendor/timbrs/database-dumps/resources/dump_config.schema.json
    • File path pattern: dump_config.yaml

Архитектура

Как работает экспорт

Команда → TableConfigResolver → DatabaseDumper → [FK-сортировка + Cycle Breaking] → DataFetcher → [Cascade WHERE] → [Faker] → SqlGenerator → .sql файлы
  1. TableConfigResolver — читает YAML и собирает список таблиц для экспорта
  2. DatabaseDumper — управляет процессом экспорта
  3. TableDependencyResolver — топологическая сортировка таблиц по FK (родители экспортируются первыми). При обнаружении циклов — автоматический разрыв через nullable-рёбра
  4. DataFetcher — получает данные из БД через ConnectionRegistry
  5. CascadeWhereResolver — генерирует WHERE-подзапросы из cascade_from для связности данных
  6. RussianFaker — заменяет персональные данные (ФИО, email, телефон, пол) на сгенерированные
  7. SqlGenerator — генерирует SQL: TRUNCATE + INSERT (с NULL для разорванных FK) + сброс счётчиков + UPDATE (восстановление FK)
  8. Результат сохраняется в docker/database/dumps/{schema}/{table}.sql

Как работает импорт

Команда → DatabaseImporter → ProductionGuard → TransactionManager → [FK-сортировка + Cycle Breaking] → [SchemaValidator] → ScriptExecutor → SqlParser → выполнение
  1. ProductionGuard — проверяет, что мы не на продакшене
  2. TransactionManager — оборачивает всё в транзакцию
  3. TableDependencyResolver — топологическая сортировка файлов по FK (родители импортируются первыми), с автоматическим разрывом циклов
  4. SchemaValidator — сравнивает столбцы дампа со столбцами в БД, предупреждает о расхождениях
  5. ScriptExecutor — выполняет скрипты из before_exec/
  6. SqlParser / StatementSplitter — разбирает .sql файлы на отдельные выражения
  7. Выражения выполняются в БД
  8. 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.0
  • symfony/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

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-config command creates YAML from DB structure
  • FK-aware ordering — automatic topological sorting of tables during export and import (parents first)
  • Cascade dependencies — cascade_from generates 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-mismatch option
  • Config settings — settings: section for batch_size, sample_size, max_cascade_depth
  • Dry-run export — --dry-run option to preview the export plan without executing

Installation

composer require --dev timbrs/database-dumps

Quick Start

Symfony

  1. Register the bundle manually in config/bundles.php (there is no Flex recipe):
Timbrs\DatabaseDumps\Bridge\Symfony\DatabaseDumpsBundle::class => ['dev' => true, 'test' => true],
  1. Create docker/database/dump_config.yaml (default path; override via config_path):
full_export:
  public:
    - users
    - roles

partial_export:
  public:
    clients:
      limit: 1000
      order_by: created_at DESC
  1. Export dumps:
php bin/console app:dbdump:export all
  1. Import dumps:
php bin/console app:dbdump:import

Laravel

  1. The service provider is discovered automatically. The file docker/database/dump_config.yaml is created on first run.

  2. Edit docker/database/dump_config.yaml (same format as Symfony).

  3. Export dumps:

php artisan dbdump:export all
  1. 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:

  • orders exports only orders whose user_id exists among exported users
  • order_items exports only items belonging to exported orders
  • 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 to dump_config.yaml was a separate hardcoded constant that did not follow data_dir. The default is now docker/database, and the config lives inside data_dir next to the dumps. An installation that relied on the old default (no data_dir key set) will stop seeing its files after the upgrade — set data_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 and where: "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 in when@dev: / when@test:. Files under config/packages/*.yaml are loaded in every environment, and there is no database_dumps extension in prod — a bare root key breaks the container build: There is no extension able to load the configuration for "database_dumps". The built-in web_profiler.yaml and debug.yaml work 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, llm and opencode used 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 into config/packages/database_dumps.yaml under the database_dumps: key and delete the old one — the Symfony bridge no longer reads it. Laravel is unchanged: still the published config/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 to docker/database/.

Option 2: PHPStorm manual setup

  1. Open Settings > Languages & Frameworks > Schemas and DTDs > JSON Schema Mappings
  2. Add a mapping:
    • Schema file: vendor/timbrs/database-dumps/resources/dump_config.schema.json
    • File path pattern: dump_config.yaml

Architecture

How Export Works

Command → TableConfigResolver → DatabaseDumper → [FK sorting + Cycle Breaking] → DataFetcher → [Cascade WHERE] → [Faker] → SqlGenerator → .sql files
  1. TableConfigResolver — reads YAML and builds a list of tables to export
  2. DatabaseDumper — manages the export process
  3. TableDependencyResolver — topological sorting of tables by FK (parents are exported first). Circular dependencies are automatically broken via nullable edges
  4. DataFetcher — fetches data from the DB via ConnectionRegistry
  5. CascadeWhereResolver — generates WHERE subqueries from cascade_from for data consistency
  6. RussianFaker — replaces personal data (names, email, phone, gender) with generated values
  7. SqlGenerator — generates SQL: TRUNCATE + INSERT (with NULL for broken FKs) + counter reset + UPDATE (FK restoration)
  8. 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
  1. ProductionGuard — checks we're not on production
  2. TransactionManager — wraps everything in a transaction
  3. TableDependencyResolver — topological sorting of files by FK (parents are imported first), with automatic cycle breaking
  4. SchemaValidator — compares dump columns against DB columns, warns about mismatches
  5. ScriptExecutor — runs scripts from before_exec/
  6. SqlParser / StatementSplitter — splits .sql files into individual statements
  7. Statements are executed against the DB
  8. 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 FROM is used instead of TRUNCATE TABLE because Oracle TRUNCATE doesn't support CASCADE and is blocked by FK constraints. Sequence reset requires PL/SQL — use scripts in docker/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.0
  • symfony/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).