Коротко: если вы делаете «корпоративный» проект с множеством сайтов, долгой поддержкой, необходимостью аккуратно инкапсулировать повторно используемые куски и иметь мощный ORM/миграции — чаще всего я рекомендую Symfony. Если вам важна скорость разработки, лёгкость и вы уже хорошо знаете Yii — Yii3 тоже жизнеспособен, но экосистема/инструменты для построения большого модульного монорепо у Symfony более зрелые.
Ниже — подробная мотивация и практические рекомендации по структуре проекта, БД и переиспользованию общих частей (подходы, которые минимизируют копипаст).
1) Почему чаще выбирают Symfony для такого сценария
- Сильная модульная архитектура (bundles, компоненты), стандартная экосистема (Composer, Flex), удобная DI, зрелая интеграция с Doctrine ORM и миграциями.
- Doctrine даёт гибкие механизмы — кастомная NamingStrategy / event listeners / несколько entity‑manager’ов — что удобно для сценариев с префиксами/разделением таблиц.
- Легко делать переиспользуемые composer‑пакеты (shared entities, сервисы, UI компоненты), которые подключаются в разные сайты/ядра.
- Хорошие практики для монорепо / мультикернельных приложений (несколько kernel’ов/конфигов в одном коде).
2) Когда стоит рассмотреть Yii3
- Быстрая разработка, меньше бойлерплейта, если ваша команда уже знает Yii.
- В Yii удобно работать с tablePrefix и AR ({{%table}}), простая система модулей.
- Но экосистема Yii3 ещё моложе, и в больших мультисайтовых системах вам придётся самостоятельно выстраивать некоторые архитектурные шаблоны, которые для Symfony уже «из коробки».
3) Рекомендации по архитектуре кода (независимо от фреймворка)
- Монорепозиторий с внутренними пакетами (recommended):
- Папка /packages/common — общие сущности (entities/models), сервисы, репозитории, shared migrations.
- /packages/features — переиспользуемые модули/бандлы (категории, авторизация, i18n и т. п.).
- /sites/site-a, /sites/site-b — приложение/конфиг/темы/специфичные миграции для каждого сайта.
- Управление зависимостями через Composer (path repositories в dev, private packages в prod).
- Каждый пакет имеет свои миграции и namespace миграций. При деплое запускаете общие миграции, затем миграции сайтов.
4) Организация БД — варианты и рекомендации
Вариант A — одна база, префиксы таблиц для site‑specific таблиц (как вы описали)
- Общие таблицы (languages, users, settings) создаются «без префикса» (или в отдельной схеме), чтобы все сайты могли ссылаться на них прямо.
- Для каждой «сайтовой» таблицы используйте префикс site1_, site2_. Это удобно если FKs между site‑tables и shared‑tables нужны: site1_products -> languages (unprefixed).
- Минусы: много префиксов в именах, возможные коллизии имён (нужно соглашение).
Тонкий момент: ORM должен поддерживать селективное префиксирование (чтобы общие сущности не получили префикс, а site‑специфичные — получили). Это решается конфигурацией ORM (см. примеры ниже).
Вариант B — Postgres schemas (предпочтительный, если БД — PostgreSQL)
- Общие таблицы в схеме common, сайт‑специфичные в схемах site1, site2.
- Плюсы: чистые имена таблиц, поддержка FKs между схемами, более очевидное разделение.
- Минусы: если вы используете MySQL, это не вариант; некоторые инструменты/ORM настройки потребуют доработки, но Doctrine поддерживает схемы.
Вариант C — отдельные базы — вы отметили, что не хотите (и правда: FK/JOIN между БД в MySQL ограничены), поэтому обычно не рекомендую.
5) Как реализовать префиксы/селективное префиксирование в практике
Symfony + Doctrine: рекомендуемый подход
- Общие сущности: namespace App\Common\Entity (таблицы без префикса).
- Site‑специфичные сущности: namespace App\SiteA\Entity (таблицы без префикса в annotations), но при загрузке метаданных префиксировать их имена динамически.
- Реализуйте подписчик на событие loadClassMetadata (или собственную NamingStrategy), который для entity из заданных пространств имён добавляет префикс:
- Если entity в namespace SiteA → $meta->table['name'] = $prefixSiteA . $meta->table['name'];
- Если в namespace Common → не трогать.
- Зарегистрируйте этот listener как сервис и пометьте его тегом doctrine.event_listener (event = loadClassMetadata).
- Миграции: настройте DoctrineMigrations так, чтобы при выполнении миграций для siteA префикс применялся (механика зависит от того, как вы генерируете миграции — чаще генерируете миграции отдельными наборами для common и для каждого сайта).
Пример-идеи (psuedo‑код listener):
public function loadClassMetadata(LoadClassMetadataEventArgs $args) {
$meta = $args->getClassMetadata();
if (strpos($meta->name, 'App\\SiteA\\')===0) {
$meta->setPrimaryTable(['name' => $this->prefix.'_'.$meta->getTableName()]);
foreach ($meta->associationMappings as & $m) { /* поправить joinColumns имя */ }
}
}
Yii3 вариант
- Используйте два подключения к БД:
- db_common (без tablePrefix)
- db_site (с tablePrefix = 'site1_')
- В общих AR-классах переопределите getDb() → возвращать db_common.
- В site‑специфичных AR используйте обычный db (db_site) и в tableName() используйте '{{%table}}' — тогда автоматически получаете префикс.
- Миграции: выполняйте общие миграции против db_common и site‑миграции против db_site.
6) Миграции и поддержка целостности
- Общие таблицы: миграции в пакете common. Деплой: сначала запускаете common migrations, затем site migrations.
- Site migrations: создают префикс‑таблицы и FKs на общие таблицы без префикса.
- Автоматизация: CI/CD скрипты, которые знают порядок миграций.
- Тесты и стейджинг: разворачивайте копию БД с префиксами как в проде, чтобы тестировать миграции.
7) Как минимизировать копипаст общих компонентов
- Вынесите: entities, DTO, репозитории, бизнес‑сервисы, общий UI (twig/шаблоны), форм‑поля, валидаторы — в общие пакеты.
- Используйте интерфейсы + DI, чтобы сайты могли подменять реализацию, не копируя код.
- Храните shared templates/partials отдельно и подключайте их в тему сайта (overrides возможны).
- Общие миграции и seed'ы — в общем пакете.
- Используйте feature toggles / конфиги per site вместо дублирования кода.
8) Практический план внедрения (пример)
- Выбираете фреймворк (Symfony если нет жёсткой зависимости на Yii).
- Создаёте монорепо:
- /packages/common
- /packages/bundles (categories, products, i18n)
- /sites/site1, /sites/site2
- В common: общие entities (Languages), общие миграции.
- Для Symfony: реализуете Doctrine listener для префиксов; для Yii — два подключения (common и site).
- Настройка CI: порядок миграций → deploy.
- Документируете соглашения по неймингу таблиц и namespace’ам.
9) Резюме и рекомендации
- Если проект большой и долгосрочный — выбирайте Symfony. Он даёт больше «готовых» инструментов для управления сложной мультисайтовой архитектурой и повторного использования кода.
- Если команда сильнее в Yii и проект не будет экстремально сложным — можно делать и на Yii3, но планируйте структуру с общими пакетами и несколькими DB‑подключениями заранее.
- По БД: если можно использовать PostgreSQL — рассмотрите схемы (clean и удобнее). Если MySQL — выбранный вами подход с префиксами рабочий; реализуйте префиксинг на стороне ORM (Doctrine listener / Yii tablePrefix) и держите общие таблицы без префикса.
- Организация кода: общие пакеты + per‑site приложения + CI/migrations — это ключ к минимизации копипаста.
Если хотите, могу:
- подготовить пример реализации Doctrine loadClassMetadata listener для селективного префиксирования (реальный PHP‑код и конфигурация в services.yaml), или
- показать пример структуры монорепо и composer.json для общих пакетов + сайтов,
- или показать пример организации подключений и миграций для Yii3.
Что предпочитаете — пример кода для Symfony (Doctrine listener) или для Yii3 (две DB‑конфигурации + AR)?