evgip / w3a-core
Ядро w3a
Requires
- php: ^8.1
- ext-json: *
- ext-mbstring: *
- ext-pdo: *
Requires (Dev)
- phpunit/phpunit: ^11.5
README
Высокопроизводительное, модульное и строго типизированное ядро PHP-фреймворка. Реализует принципы чистой архитектуры и современные паттерны разработки, обеспечивая предсказуемость, тестируемость и отличный Developer Experience.
✨ Ключевые особенности
- 🎯 Строгая типизация HTTP-ответов: Контроллеры возвращают явные объекты
ViewResponse,RedirectResponseилиJsonResponse. Никаких скрытыхvoidили побочных эффектов. - 📨 Единый центр сообщений (MessageBag): Централизованное управление flash-сообщениями и сохранением данных форм (
old_input) без прямого манипулирования сессией в контроллерах. - 🛠 Встроенные Коллекции (Collections): Мощный fluent-интерфейс для работы с массивами данных (
collect()->map()->filter()->pluck()), избавляющий от громоздкихarray_*функций. - 📄 Пагинация из коробки (Paginator): Расчёт страниц, offset для SQL и видимый диапазон номеров — общий для всех проектов.
- ✅ Декларативная валидация: Встроенный класс
Validatorс поддержкой правил (required,email,unique,min,regexи др.) и автоматической обработкой ошибок черезvalidateRequest(). - 🧩 Чистая архитектура (DIP): Ядро не содержит жестких ссылок на классы приложения (
\App\...). Зависимости явно передаются через DI-контейнер и интерфейсы (Contracts). - 🛡️ Безопасность из коробки: Встроенная защита от CSRF, XSS (CSP-nonce), Rate Limiting и Firewall (бан IP).
- 🗄 Готовые БД-реализации контрактов:
DatabaseRateLimitStorage,DatabaseAuditStorage,DatabaseBannedIpRepository— приложению достаточно зарегистрировать их вAppServiceProvider, писать свои классы не нужно. - ⚡ Ленивая загрузка конфигов: Файлы конфигурации читаются с диска только при первом обращении к их ключам через dot-нотацию.
- 💾 Безопасный файловый кэш:
FileCacheиспользуетserialize()+flock(LOCK_SH)вместоrequire— нет исполнения произвольного PHP, нет race condition при конкурентной записи. Атомарная запись через tmp +rename(), шардирование каталога (2-символьные подпапки) для масштабируемости. - 🔐 Ленивая сессия:
Sessionстартует только при первом обращении к$_SESSION. Анонимные GET-запросы не создают session-файлы и не шлютPHPSESSIDcookie ботам и гостям — экономия ресурсов на 90%+ трафика публичного сайта.
📋 Требования
- PHP 8.1+ (используются union types, constructor property promotion, match expressions)
- Расширения:
pdo,mbstring,json - База данных: MySQL / MariaDB (или другая, поддерживаемая PDO)
📦 Установка
Для продакшена (через Packagist)
composer require evgip/w3a-core
Для локальной разработки (через symlink)
Добавьте в composer.json вашего основного проекта:
{
"repositories": [
{
"type": "path",
"url": "./w3a-core"
}
],
"require": {
"evgip/w3a-core": "@dev"
}
}
Затем выполните composer update evgip/w3a-core. Composer создаст символическую ссылку, и все изменения в ядре будут применяться мгновенно.
🚀 Быстрый старт
Точка входа (public/index.php) максимально чиста и явно объявляет зависимости:
<?php declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; // 1. Загрузка переменных окружения \W3a\Core\Foundation\Env::load(dirname(__DIR__) . '/.env'); // 2. Инициализация приложения с явной регистрацией провайдеров $app = new \W3a\Core\Foundation\Application(dirname(__DIR__), [ \W3a\Core\Foundation\CoreServiceProvider::class, // Сервисы ядра \App\AppServiceProvider::class, // Сервисы вашего приложения ]); $app->bootstrap()->run();
💡 Современный стиль кода (Примеры)
1. Контроллеры с явными ответами и MessageBag
Забудьте о $this->session()->flash() и скрытых редиректах в контроллерах приложения:
public function store(): RedirectResponse { $data = $this->request->getParams(); // Автоматическая валидация с редиректом и сохранением old_input при ошибке $validation = $this->validateRequest([ 'email' => 'required|email|unique:users,email', 'password' => 'required|min:6', ]); if ($validation !== true) { return $validation; // Возвращаем RedirectResponse } try { $this->userService->create($data); MessageBag::flashMessage('success', 'Пользователь успешно создан!'); return $this->redirect('/users'); } catch (\Throwable $e) { MessageBag::flashMessage('error', 'Ошибка при создании пользователя.'); return $this->redirectBack(); } }
2. Использование Коллекций (Collections)
Замена громоздких array_map и array_filter на читаемый fluent-интерфейс:
// Получаем уникальные ID активных историй из массива комментариев $storyIds = collect($comments) ->reject(fn($c) => !empty($c['deleted_at'])) ->pluck('story_id') ->unique() ->values() ->toArray();
3. Пагинация (Paginator)
use W3a\Core\Support\Paginator; $total = $itemModel->countList($filter); $pager = new Paginator($total, 15, (int)$request->query('page', 1)); $items = $itemModel->getList(15, $pager->offset(), $filter); $this->render('list', [ 'items' => $items, 'pager' => $pager->toArray(), // currentPage, lastPage, total, range, hasPrev, hasNext ]);
📂 Структура приложения
your-app/
├── app/
│ ├── Config/ # Конфигурация (app.php, database.php, ...)
│ ├── Lang/ # Файлы локализации (ru.php, en.php)
│ ├── Modules/ # Бизнес-модули (Users, Stories, Admin, ...)
│ └── AppServiceProvider.php # Связывание интерфейсов ядра с реализациями
├── storage/
│ ├── cache/ # Кэш маршрутов, представлений и данных (шардированный)
│ │ └── data/
│ │ └── a1/app_*.cache # 256 подпапок (00..ff) по первым 2 символам MD5
│ ├── sessions/ # Файлы сессий PHP (создаются только для авторизованных)
│ └── logs/ # Логи приложения и PHP-ошибок
├── public/
│ └── index.php # Точка входа (Front Controller)
└── composer.json
🔌 Ключевые интерфейсы (Contracts)
Ядро работает через инверсию зависимостей. Интерфейсы, зависящие от бизнес-логики, регистрируются в AppServiceProvider:
| Интерфейс | Назначение | Готовая реализация в ядре |
|---|---|---|
Contracts\RateLimitStorageInterface |
Хранилище лимитов запросов (Rate Limiter) | Security\DatabaseRateLimitStorage |
Contracts\UserIdProviderInterface |
Получение ID текущего авторизованного пользователя | — |
Contracts\AuditStorageInterface |
Хранилище журнала аудита действий | Audit\DatabaseAuditStorage |
Contracts\BannedIpRepositoryInterface |
Проверка заблокированных IP-адресов (Firewall) | Security\DatabaseBannedIpRepository |
Contracts\ErrorHandlerInterface |
Обработка и рендеринг страниц ошибок (404, 500) | Errors\DefaultErrorHandler (fallback) |
Пример регистрации
<?php // app/AppServiceProvider.php namespace App; use W3a\Core\Foundation\Container; use W3a\Core\Database\Database; use W3a\Core\Contracts\RateLimitStorageInterface; use W3a\Core\Contracts\AuditStorageInterface; use W3a\Core\Contracts\BannedIpRepositoryInterface; use W3a\Core\Contracts\ErrorHandlerInterface; class AppServiceProvider { public function register(Container $container): void { // Готовые БД-реализации (таблицы создаются миграциями из database/migrations/) $container->singleton(RateLimitStorageInterface::class, fn($c) => new \W3a\Core\Security\DatabaseRateLimitStorage($c->get(Database::class)) ); $container->singleton(AuditStorageInterface::class, fn($c) => new \W3a\Core\Audit\DatabaseAuditStorage($c->get(Database::class)) ); $container->singleton(BannedIpRepositoryInterface::class, fn($c) => new \W3a\Core\Security\DatabaseBannedIpRepository($c->get(Database::class)) ); // Ошибки: свой ErrorHandler со своим layout; DefaultErrorHandler — fallback $container->singleton(ErrorHandlerInterface::class, fn($c) => new \App\Modules\Errors\Services\ErrorHandler($c) ); // Регистрация групп middleware $router = $container->get(\W3a\Core\Http\Router::class); $router->addMiddlewareGroup('auth', [ \App\Modules\Users\Middleware\AuthMiddleware::class, \App\Modules\Users\Middleware\BanCheckMiddleware::class, ]); } }
⚙️ Конфигурация
Конфиги поддерживают dot-нотацию (config('database.host')) и загружаются лениво (файл database.php не будет прочитан, если вы обращаетесь только к config('app.name')).
// app/Config/app.php return [ 'name' => 'my-app', 'env' => \W3a\Core\Foundation\Env::get('APP_ENV', 'development'), 'lang' => \W3a\Core\Foundation\Env::get('APP_LANG', 'ru'), 'log_path' => dirname(__DIR__, 2) . '/storage/logs/app.log', ];
🧩 Основные компоненты ядра
| Компонент | Неймспейс | Назначение |
|---|---|---|
Application |
W3a\Core\Foundation |
Оркестратор: управление жизненным циклом (bootstrap) |
Container |
W3a\Core\Foundation |
DI-контейнер (поддержка singleton, bind, авто-резолв через рефлексию) |
ExceptionHandler |
W3a\Core\Exceptions |
Централизованная обработка ошибок |
Router |
W3a\Core\Http |
Маршрутизация с поддержкой middleware-групп и индексацией по префиксу |
MessageBag |
W3a\Core\Support |
Управление flash-сообщениями и данными форм (old_input) |
Collection |
W3a\Core\Support |
Fluent-интерфейс для трансформации массивов данных |
Paginator |
W3a\Core\Support |
Расчёт пагинации: страницы, offset, диапазон |
PhpArrayFile |
W3a\Core\Support |
Атомарная запись/чтение PHP-массивов (единый кэш-слой) |
Validator |
W3a\Core\Support |
Декларативная валидация входных данных с поддержкой БД (unique, exists) |
Session |
W3a\Core\Http |
Ленивое управление PHP-сессиями, flash-сообщения, защита от Session Fixation |
FileCache |
W3a\Core\Cache |
Файловый кэш с serialize, flock и шардированием каталога |
Audit |
W3a\Core\Support |
Журнал аудита действий с ленивым чтением сессии |
DatabaseRateLimitStorage |
W3a\Core\Security |
БД-реализация RateLimitStorageInterface (таблица rate_limits) |
DatabaseBannedIpRepository |
W3a\Core\Security |
БД-реализация BannedIpRepositoryInterface (таблица banned_ips) |
DatabaseAuditStorage |
W3a\Core\Audit |
БД-реализация AuditStorageInterface (таблица audit_logs) |
DefaultErrorHandler |
W3a\Core\Errors |
Fallback-обработчик ошибок (минимальный HTML) |
🧰 Хелперы
Ядро предоставляет глобальные функции (загружаются автоматически):
| Функция | Назначение |
|---|---|
config($key, $default) |
Значение конфигурации (dot-нотация) |
env($key, $default) |
Переменная окружения из .env |
container($abstract) |
Сервис из DI-контейнера |
route($name, $params) |
URL по имени маршрута |
redirect($url, $code) |
Редирект |
abort($code, $message) |
Прервать выполнение с HTTP-ошибкой (бросает исключение ядра) |
e($value) |
HTML-экранирование (защита от XSS) |
__($key, $replace) |
Перевод строки |
dt($datetime, $format) |
Форматирование даты из БД / timestamp |
old($key, $default) |
Старое значение поля после ошибки валидации |
csrf_field() |
Скрытое поле с CSRF-токеном |
csp_nonce() |
Nonce для Content Security Policy |
collect($items) |
Создать коллекцию |
💾 Работа с кэшем
FileCache безопасен для публичных сайтов: данные сериализуются через serialize() (нет исполнения PHP), а каталог шардируется по первым 2 символам MD5-хеша ключа, что исключает деградацию ФС при десятках тысяч ключей.
$cache = $container->get(\W3a\Core\Cache\FileCache::class); // Запись (TTL в секундах, 0 = бессрочно) $cache->set('user:123:profile', $userData, 3600); // Чтение (null если нет или истёк) $user = $cache->get('user:123:profile'); // Проверка и удаление if ($cache->has('user:123:profile')) { $cache->delete('user:123:profile'); } // Полная очистка $cache->clear();
⚠️ Миграция со старых версий: при обновлении необходимо очистить
storage/cache/data/*— старые.phpкэш-файлы несовместимы с новым форматом.cache.
🔐 Работа с сессиями
Session стартует лениво — только при первом вызове get()/set()/flash(). Это значит:
- Анонимные GET-запросы (главная, статьи, RSS, JSON-API) не создают session-файлы
- Боты и гости не получают cookie
PHPSESSID - Авторизованные действия (login, POST-формы, CSRF) стартуют сессию автоматически
$session = $container->get(\W3a\Core\Http\Session::class); // Для гостей: сессия НЕ стартует $session->isStarted(); // false, пока нет обращения к данным // Первое обращение автоматически запускает сессию $session->set('user_id', 123); $session->isStarted(); // true // Flash-сообщения (доступны только на следующем запросе) $session->flash('success', 'Профиль сохранён'); // Защита от Session Fixation — вызывать при входе/выходе $session->regenerate(); // Полное уничтожение $session->destroy();
📄 Лицензия
Распространяется под лицензией MIT.