MyBlog - это блоговая платформа на Yii2 Basic с тремя интерфейсами в одном репозитории:
- публичный сайт для читателей
- административная панель и личный кабинет для авторов и администраторов
- версионированный JSON API для интеграций
С архитектурной точки зрения это модульный монолит: один код, одна база данных, общий набор доменных моделей и отдельный консольный воркер для доставки webhook-ов. Это не SPA и не набор микросервисов.
| Аспект | Описание |
|---|---|
| Архитектура | Модульный монолит на Yii2 Basic |
| Интерфейс | Серверный рендеринг Yii Views + AssetBundle, без отдельного Node.js-пайплайна сборки |
| Основные модули | Публичный веб-интерфейс, admin, api/v1, консольные команды |
| Хранилище | MySQL/MariaDB, схема развивается через миграции |
| Интеграции | OpenAPI/Swagger, входящие и исходящие webhook-и |
| Качество | Модульные, функциональные и приемочные тесты на Codeception/PHPUnit |
| Рекомендуемый режим запуска | Docker Compose с app, db и selenium |
- Публичные страницы: главная лента, страница статьи, страница категории, страницы "О проекте" и "Контакты".
- Регистрация и вход на
/auth/signupи/auth/login. - Подтверждение email: новый пользователь создаётся неактивным до перехода по ссылке из письма.
- Личный кабинет на
/cabinet/profileдля смены профиля, пароля и аватара. - Админ-панель на
/adminдля управления статьями, категориями, тегами, комментариями, пользователями и сообщениями обратной связи. - Централизованные двуязычные контент-блоки (
RU/EN) для публичных страниц, хедера и футера с CRUD в админке. - Комментарии и лайки статей для авторизованных пользователей.
- Загрузка изображений для статей и аватаров.
- Локализация интерфейса на английский и русский языки.
- JSON API на
/api/v1с Bearer-аутентификацией. - Входящая точка приёма webhook-ов с HMAC-проверкой и идемпотентностью.
- Исходящая outbox-очередь webhook-ов с повторными попытками и консольным диспетчером.
Проект построен как одно Yii2-приложение с явными внутренними границами:
- публичный HTTP-слой в
controllers/иviews/ - модуль админки и личного кабинета в
modules/admin - API-модуль в
modules/api/modules/v1 - общие доменные модели в
models/ - сервисы общего назначения в
components/ - консольные точки входа в
commands/
Такой подход даёт один артефакт развёртывания, одну схему БД, общий контур аутентификации и при этом сохраняет читаемые границы между подсистемами.
Браузер / API-клиент
-> web/index.php
-> контроллер или модульный контроллер
-> модель ActiveRecord и/или сервис
-> MySQL
-> HTML или JSON-ответ
Публикация статьи / создание комментария / создание сообщения обратной связи
-> событие модели
-> WebhookEventPublisher
-> таблица outbox `webhook_delivery`
-> yii webhook/dispatch
-> внешний эндпоинт
Внешний провайдер
-> POST /api/v1/webhooks/incoming/{source}
-> HMAC + проверка идемпотентности
-> лог `webhook_incoming_delivery`
| Слой | Ответственность | Основные директории |
|---|---|---|
| HTTP-контроллеры | Координация запроса и ответа, проверка доступа, редиректы, рендеринг | controllers/, modules/admin/controllers/, modules/api/modules/v1/controllers/ |
| Доменные модели | Валидация, связи, сохранение, часть бизнес-логики | models/ |
| Сервисы | Токены, webhook outbox, публикация доменных событий | components/security/, components/webhook/ |
| Представление | Макеты, представления, partial-шаблоны и почтовые шаблоны | views/, modules/admin/views/, mail/ |
| Фронтенд-ассеты | CSS/JS bundles и статическая тема | assets/, web/css/, web/public/ |
| Инфраструктура | Конфиги, миграции, Docker, консольные команды | config/, migrations/, docker/, commands/ |
| Тестовый контур | Наборы тестов, фикстуры и инициализация тестовой среды | tests/ |
Фронтенд здесь серверный. Отдельной исполняемой SPA-части и npm-сборки нет.
views/layouts/main.phpрендерит публичную часть.views/layouts/admin.phpрендерит админку и личный кабинет.assets/PublicAsset.phpподключает публичную тему изweb/public/*.assets/AppAsset.phpподключает административные стили изweb/css/*.
Это важно для поддержки проекта: маршрутизация, формы, авторизация и рендеринг живут внутри Yii, а не размазаны между несколькими приложениями.
assets/ Yii asset bundles для публичного и административного интерфейса
commands/ Консольные команды, включая воркер webhook-ов
components/ Повторно используемые сервисы и компоненты инициализации, включая ContentManager
config/ Конфиги web, console, test, db и params
controllers/ Контроллеры публичной части и аутентификации
mail/ Шаблоны писем
migrations/ Эволюция схемы базы данных
models/ ActiveRecord модели, формы и модели поиска, включая ContentBlock
modules/admin/ Админ-панель и личный кабинет пользователя
modules/api/ API-модуль с версией `v1`
tests/ Модульные, функциональные и приемочные тесты, а также фикстуры
views/ Layouts и views публичной части
web/ Корень веб-приложения, uploads, Swagger UI, спецификация OpenAPI и статические ассеты
docker/ Docker-образ и стартовый скрипт
| Сущность | Назначение |
|---|---|
User |
Идентичность пользователя, вход, флаг администратора, состояние подтверждения email, аватар |
Article |
Основная сущность контента: автор, статус, категория, теги, изображение, просмотры |
Category |
Категория статей, может иметь владельца |
Tag |
Теги статей, поддерживают владение и создание прямо из формы |
ArticleTag |
Связь многие-ко-многим между статьями и тегами |
Comment |
Комментарии пользователей к статьям |
ArticleLike |
Уникальная связка лайка между пользователем и статьёй |
ContactMessage |
Сохранённые сообщения с формы /contact |
ContentBlock |
Двуязычные (RU/EN) редактируемые текстовые блоки для хедера, футера и статических секций страниц |
UserAccessToken |
Хешированные API-токены с ротацией и историей отзыва |
WebhookEndpoint |
Конфигурация подписчика исходящих webhook-ов |
WebhookDelivery |
Элемент очереди и журнал доставки исходящего webhook-а |
WebhookIncomingDelivery |
Аудит-лог входящих webhook-ов |
Публичные маршруты живут в корневом слое контроллеров:
SiteControllerотвечает за главную, статью, категорию, страницу контактов и переключение языка.AuthControllerотвечает за вход, регистрацию, выход и подтверждение email.
Модуль admin обслуживает две роли:
- администраторов с полным доступом к пользователям, сообщениям обратной связи и всему контенту
- обычных авторизованных пользователей с доступом только к собственным сущностям
Админский контент-менеджмент расположен на /admin/content-block:
- только администратор может создавать, редактировать и удалять контент-блоки
- каждый блок хранит
value_enиvalue_ru, а вывод выбирается по текущему языку интерфейса - при пустом значении на активном языке используется fallback на второй язык или дефолт из шаблона
Доступ контролируется через modules/admin/controllers/AdminController:
assertAdmin()защищает разделы только для администраторов, например users и feedbackassertOwnerOrAdmin()не даёт не-админам редактировать чужие статьи, теги, категории, комментарии и профили
API версионирован и расположен в modules/api/modules/v1.
- Все API-контроллеры наследуются от
ApiController. - Ответы всегда JSON через
ContentNegotiator. - CORS управляется через
API_ALLOWED_ORIGINS. - Для защищённых эндпоинтов используется Bearer-аутентификация.
Это позволяет добавить v2 рядом, не ломая поведение v1.
Безопасность здесь реализована не одной настройкой, а через набор механизмов на разных слоях.
- Для web-форм включён CSRF.
COOKIE_VALIDATION_KEYобязателен и должен быть уникальным для каждого окружения.- Identity и CSRF cookies помечены как
httpOnlyиsameSite=Lax. - Logout и destructive actions переведены на POST-only, где это уместно.
- Переключение языка валидирует язык и редиректит только на относительные URL.
- Пароли хранятся в виде хеша через хелперы безопасности Yii.
- Новые пользователи могут создаваться в статусе inactive и обязаны подтвердить email.
User::findIdentity()возвращает только активных пользователей.- Права администратора основаны на флаге
isAdmin. - Проверки владения защищают пользовательский контент от редактирования чужими пользователями.
- API-токены выдаются через
AccessTokenService. - В базе хранится только
token_hash, а не сырой токен. - У токенов есть TTL, rotation, logout и logout-all.
- Использование токена обновляет
last_used_at. - Эндпоинт входа ограничен по частоте запросов по email и IP через кэш.
- Временный резервный режим через
user.access_tokenсуществует, но по умолчанию выключен.
- Загружаемые изображения валидируются по расширению, MIME type и размеру.
- Ограничение размера учитывает и конфиг приложения, и PHP
upload_max_filesize/post_max_size. - Имена файлов рандомизируются перед сохранением.
- Загруженные файлы складываются в
web/uploads/. - HTML в шаблонах экранируется через хелперы Yii там, где это требуется.
- Для входящих webhook-ов обязателен настроенный источник и его секрет.
- Подпись проверяется через
HMAC-SHA256. Idempotency-Keyобязателен для защиты от дублей.- Чувствительные входящие заголовки редактируются перед логированием.
- Исходящие webhook-и подписываются через
X-Webhook-Signature.
Свежие миграции усиливают надёжность схемы:
- ограничения
NOT NULLиUNIQUEдля ключевых полей пользователя - индексы под частые запросы к статьям, комментариям, тегам и webhook-таблицам
- внешние ключи для владения и целостности связей
- отдельная таблица для хешированных API-токенов
Этот репозиторий поддерживаем, потому что границы системы видны в коде, а не только в договорённостях.
- Изменения схемы отслеживаются в
migrations/; это канонический источник правды по БД. - Логика фильтрации и поиска в админских грид-таблицах вынесена в
*Searchмодели. - Чувствительная к безопасности логика централизована в сервисах, а не размазана по контроллерам.
- Контракты API и сервисов задокументированы PHPDoc-блоками и аннотациями форм массивов в ключевых контроллерах и сервисах.
- Версионирование API сделано явно через вложенность модулей.
- У тестовой среды есть отдельный конфиг и отдельное подключение к тестовой БД.
- Запуск в Docker воспроизводим и включает миграции, подготовку тестовой БД и опциональную загрузку фикстур.
- Отсутствует отдельная цепочка фронтенд-сборки, которую пришлось бы синхронизировать с PHP-частью.
Для статических фрагментов интерфейса внедрён централизованный слой контент-блоков:
- таблица БД
content_blockхранит ключ блока, секцию, описание и значенияvalue_en/value_ru - компонент
app\components\ContentManagerдаёт единый доступ в шаблонах черезYii::$app->contentManager->text(...) - публичные шаблоны (
views/layouts/main.php,views/site/*,views/auth/*,views/partials/sidebar.php) используют эти блоки для управляемого текста - админский интерфейс
/admin/content-blockподдерживает полный CRUD и мгновенно сбрасывает in-request кэш после сохранения/удаления - шаблон всегда имеет безопасный fallback на текст по умолчанию, если блок или перевод отсутствует
В проекте есть два уровня документации:
- PHPDoc в контроллерах, сервисах и ключевых моделях объясняет ответственность класса, инварианты и форму возвращаемых данных
web/openapi/swagger.jsonфиксирует внешний HTTP-контракт API v1 для Swagger UI, интеграций и регрессионных проверок
Текущая структура оставляет понятные точки расширения.
- создать соседний модуль в
modules/api/modules/ - зарегистрировать маршруты в
config/web.php - оставить
v1нетронутым для обратной совместимости
- создать ActiveRecord и миграцию
- при необходимости добавить
Searchмодель для списка - создать контроллер и CRUD-представления внутри
modules/admin - переиспользовать
AdminControllerи его проверки роли администратора и владельца
- публиковать его из модели или сервиса через
WebhookEventPublisher - дать эндпоинтам возможность подписаться через
WebhookEndpoint::events - доставлять через существующий outbox-воркер
- определить новый секрет источника в
config/params.phpчерез переменные окружения - переиспользовать
/api/v1/webhooks/incoming/{source} - при необходимости добавить последующую обработку после логирования
- расширить
LanguageSelector - добавить файлы переводов в
messages/ - обновить partial-шаблоны переключателя языка
Проект выглядит собранно, но его важно описывать без маркетингового шума.
- Это не Clean Architecture и не DDD. Заметная часть бизнес-логики по-прежнему живёт в ActiveRecord моделях и местами в контроллерах.
- Авторизация намеренно простая:
isAdminплюс проверки владения, а не полноценная RBAC-матрица. - Фоновая доставка реализована через опрос таблиц БД и консольную команду, а не через отдельный брокер очередей.
- Кэш по умолчанию файловый и локальный; для горизонтального масштабирования его нужно заменить на общий backend.
- Входящие webhook-и сейчас валидируются и логируются; бизнес-обработка после приёма остаётся следующей точкой расширения.
- PHP
>= 8.1 - Composer 2
- MySQL или MariaDB
pdo_mysqlgdилиimagickдля captcha
Docker-образ из репозитория использует:
- PHP 8.3 + Apache
- MySQL 8.4
- Selenium standalone Chrome для приемочных тестов
Сам Yii-проект не читает .env автоматически на этапе bootstrap.
.env.exampleнужен в первую очередь дляdocker compose- для локального PHP/OSPanel переменные должны приходить из shell, веб-сервера или настроек ОС
- если переменные окружения не заданы,
config/db.phpиспользует встроенные значения по умолчанию
| Переменная | Назначение |
|---|---|
COOKIE_VALIDATION_KEY |
Секрет для валидации запросов и cookies |
DB_DSN, DB_HOST, DB_PORT, DB_NAME |
Подключение к основной БД |
DB_USERNAME, DB_PASSWORD |
Учётные данные основной БД |
DB_TEST_DSN, DB_TEST_HOST, DB_TEST_PORT, DB_TEST_NAME |
Подключение к тестовой БД |
YII_ENV, YII_DEBUG |
Режим окружения |
AUTO_MIGRATE |
Автозапуск миграций основной БД в Docker |
AUTO_MIGRATE_TEST_DB |
Автозапуск миграций тестовой БД в Docker |
AUTO_LOAD_FIXTURES, FIXTURES_LIST |
Автозагрузка фикстур при пустой базе |
WEBHOOK_INCOMING_GENERIC_SECRET |
Секрет для проверки входящих webhook-ов |
WEBHOOK_OUTGOING_MAX_ATTEMPTS |
Количество повторных попыток для исходящих webhook-ов |
API_ALLOWED_ORIGINS |
Список разрешённых источников для CORS |
API_ACCESS_TOKEN_TTL |
Время жизни API-токена |
API_REVOKE_EXISTING_TOKENS_ON_LOGIN |
Отзывать ли старые активные токены при входе |
API_LEGACY_ACCESS_TOKEN_FALLBACK |
Временный режим совместимости со старой схемой токенов |
composer install
php yii migrate --interactive=0
php yii serve --port=8080После этого приложение будет доступно на http://localhost:8080.
Важно:
- корень сайта у реального веб-сервера должен указывать на
web/ runtime/,web/assets/иweb/uploads/должны быть доступны на запись- в dev-режиме mailer пишет письма в файлы, а не отправляет их наружу
Copy-Item .env.example .env
docker compose up -d --build
docker compose logs -f appПосле старта приложение доступно на http://localhost:8080, если APP_PORT не изменён.
Стартовый скрипт контейнера уже умеет:
- проверять наличие
pdo_mysql - ждать готовности базы данных
- ставить зависимости Composer, если нужно
- запускать миграции основной БД
- запускать миграции тестовой БД
- очищать кэш схемы
- при необходимости загружать фикстуры в пустые базы
По умолчанию Docker-окружение может поднять не только схему, но и полноценный демо-набор данных для разработки, ручной проверки и тестов.
Базовый префикс: /api/v1
- спецификация OpenAPI лежит в
web/openapi/swagger.json - исходный JSON-файл после запуска доступен по
/openapi/swagger.json - Swagger UI после запуска доступен по
/docs/ swagger.jsonподдерживается вручную как контрактный артефакт и проверяется unit-тестомtests/unit/docs/OpenApiSpecTest.php- для API-эндпоинтов дополнительно поддерживаются PHPDoc-описания в
modules/api/modules/v1/controllers/
GET /api/v1/healthPOST /api/v1/auth/loginPOST /api/v1/auth/rotatePOST /api/v1/auth/logoutPOST /api/v1/auth/logout-allGET /api/v1/meGET /api/v1/categoriesGET /api/v1/articlesGET /api/v1/articles/{id}GET /api/v1/articles/{id}/commentsPOST /api/v1/articles/{id}/commentsPOST /api/v1/articles/{id}/likePOST /api/v1/webhooks/incoming/{source}
Исходящая доставка реализована по паттерну outbox.
- события ставятся в очередь при публикации статьи, создании комментария и создании сообщения обратной связи
- элементы очереди хранятся в
webhook_delivery - диспетчер доставки - консольная команда
php yii webhook/dispatch - повторные попытки используют экспоненциальную задержку
- подписчики настраиваются в
webhook_endpoint
Полезные команды:
php yii webhook/list-endpoints
php yii webhook/create-endpoint "crm" "https://example.com/webhook" "secret" "article.published,comment.created"
php yii webhook/dispatch 50Входящий эндпоинт сейчас работает как защищённая точка приёма и аудит-лог.
- валидирует секрет источника
- валидирует
HMAC-SHA256подпись - требует
Idempotency-Key - сохраняет и успешные, и отклонённые доставки в
webhook_incoming_delivery
migrations/- каноническая история схемыmyblog.sql- устаревший SQL-дамп и справочный снимок, но не основной способ управления схемой- тестовые фикстуры лежат в
tests/fixtures/иtests/_data/ - тестовая инициализация использует отдельную базу из
config/test_db.php
Текущий набор фикстур делает проект визуально и функционально пригодным сразу после старта пустой базы:
- 10 пользователей, из которых 1 администратор и 9 обычных авторов
- 10 опубликованных статей с категориями, тегами, просмотрами и обложками
- 20 комментариев с разными авторами и статусами модерации
- 18 лайков на статьях, чтобы можно было проверить UI и API без ручного наполнения
- аватарки пользователей и изображения постов, уже сохранённые в
web/uploads/какfixture-avatar-*иfixture-post-*
- администратор:
admin@example.com/admin12345 - обычный пользователь:
user@example.com/user12345 - остальные демо-пользователи: email из фикстур
tests/_data/user.php, парольdemo12345
Если включены AUTO_LOAD_FIXTURES=1 и непустой FIXTURES_LIST, стартовый контейнер загружает связанные фикстуры в пустую базу.
Типовой список по умолчанию:
User,Category,Tag,Article,ArticleTag,Comment,ArticleLike
Этого достаточно, чтобы после docker compose up получить готовую демо-среду с контентом, пользователями, комментариями и лайками без ручного сидирования.
Никогда не запускайте тестовые наборы на production- или development-данных.
Тестовый стек построен на Codeception с PHPUnit-проверками.
tests/unitпроверяет мелкие единицы: модели, виджеты, вспомогательные классыtests/functionalпроверяет веб-сценарии, разграничение владения в админке, API, подтверждение email и webhook-сценарииtests/acceptanceпроверяет браузерные сценарии через Selenium WebDriver
tests/unit/models/UserTest.phptests/unit/components/LanguageSelectorTest.phptests/unit/components/ContentManagerTest.phptests/unit/docs/OpenApiSpecTest.phptests/functional/ApiMvpCest.phptests/functional/AdminAccessOwnershipCest.phptests/functional/ContentBlockCrudCest.phptests/functional/SignupEmailConfirmationCest.phptests/functional/WebhookFlowCest.phptests/acceptance/LoginCest.php
Отдельно важно:
tests/unit/docs/OpenApiSpecTest.phpвалидируетswagger.json, обязательные пути и отсутствие битых$reftests/functional/ApiMvpCest.phpпроверяет не только happy path API, но и ротацию/отзыв токенов, а также429 Too Many Requestsдля login rate limit
Для локального прогона с хостовой машины должны быть запущены Docker-сервисы app, db и selenium, потому что тестовая БД и браузерный драйвер поднимаются через docker compose.
Команды ниже рассчитаны на PowerShell и используют %TEMP%, чтобы локальный PHP не писал в закрытые каталоги tests/_output, runtime и системный session.save_path OSPanel:
$env:DB_HOST='127.0.0.1'
$env:DB_PORT='3307'
$env:DB_USERNAME='myblog'
$env:DB_PASSWORD='myblog_password'
$env:DB_NAME='myblog'
$env:DB_TEST_NAME='myblog_test'
$env:DB_TEST_DSN='mysql:host=127.0.0.1;port=3307;dbname=myblog_test'
$env:DB_DSN=$env:DB_TEST_DSN
$env:TEST_RUNTIME_PATH="$env:TEMP\myblog-test-runtime"
$env:TEST_ASSET_BASE_PATH="$env:TEMP\myblog-test-assets"
$env:TEST_MAIL_PATH="$env:TEMP\myblog-test-mail"
$env:TEST_SESSION_SAVE_PATH="$env:TEMP\myblog-test-sessions"
$env:UPLOAD_STORAGE_PATH="$env:TEMP\myblog-test-uploads"
php -d session.save_path="$env:TEMP\myblog-test-sessions" vendor/bin/codecept run unit -o "paths: output: $env:TEMP/myblog-codecept-output/unit"
php -d session.save_path="$env:TEMP\myblog-test-sessions" vendor/bin/codecept run functional -o "paths: output: $env:TEMP/myblog-codecept-output/functional"
php vendor/bin/codecept run acceptance --env host -o "paths: output: $env:TEMP/myblog-codecept-output/acceptance"Полный локальный прогон одним вызовом:
php -d session.save_path="$env:TEMP\myblog-test-sessions" vendor/bin/codecept run --env host -o "paths: output: $env:TEMP/myblog-codecept-output/local-full"docker compose exec --user=www-data app php vendor/bin/codecept run unit
docker compose exec --user=www-data app php vendor/bin/codecept run functional
docker compose exec --user=www-data app php vendor/bin/codecept run acceptanceПрогон всех наборов одной командой:
docker compose exec --user=www-data app php vendor/bin/codecept runЕсли запускать тесты с хоста без Docker-сервисов, большинство тестов упадёт по инфраструктурным причинам:
- нет подключения к
myblog_test(MySQL) - нет подключения к Selenium WebDriver
Важно для приемочных тестов:
- сервис
seleniumдолжен быть запущен - контейнер
appрезолвит сайт какhttp://web.local/ - локальный запуск с хоста использует профиль
tests/_envs/host.yml, который направляет Codeception в Selenium по127.0.0.1:4444, но оставляет URL приложения контейнернымhttp://web.local/ - тестовая точка входа находится в
web/index-test.php
Ниже превью экранов. Нажмите на изображение, чтобы открыть полный размер в новой вкладке.
Этот репозиторий не является open-source проектом в смысле MIT, BSD или Apache.
- Проект лицензирован как
proprietary. - Автор и правообладатель:
Rinat Sarmuldin <ura07srr@gmail.com>. - Исходный код, документация, структура БД, фикстуры, дизайн и медиа проекта защищены авторским правом.
- Копирование, публикация, переработка, перепродажа, ребрендинг и выдача проекта за собственную работу без письменного разрешения правообладателя запрещены.
- Даже при согласованном использовании должны сохраняться уведомления об авторстве и файл лицензии.
Файлы с юридическими условиями:
LICENSE.md- основная лицензия проектаNOTICE.md- явное уведомление об авторстве и правовом статусе репозитория
Важно: сторонние зависимости и ассеты внутри проекта сохраняют собственные лицензии. Это относится к Yii2, Bootstrap, jQuery, Font Awesome, Swagger UI и другим внешним компонентам.