This commit is contained in:
Egor Isaev 2026-06-26 12:52:41 +03:00
parent b6a8133170
commit 4e41390544
3 changed files with 28 additions and 5 deletions

View File

@ -85,6 +85,8 @@ PhpStorm может показывать предупреждение «Namespac
| `System\Classes\Session` | Синглтон сессии: `instance(string $name)`, `get/set/delete/destroy/regenerate` | | `System\Classes\Session` | Синглтон сессии: `instance(string $name)`, `get/set/delete/destroy/regenerate` |
| `System\Classes\Config` | Статический конфиг: lazy-load `App/config/config.php` + merge `config.local.php` | | `System\Classes\Config` | Статический конфиг: lazy-load `App/config/config.php` + merge `config.local.php` |
| `System\Classes\Log` | Файловый логгер: уровни, аудит запросов, лог ошибок; `info()/error()`, маскировка | | `System\Classes\Log` | Файловый логгер: уровни, аудит запросов, лог ошибок; `info()/error()`, маскировка |
| `System\Classes\LogReader` | Интерфейс чтения логов (`read()`, `dates()`); абстракция над хранилищем |
| `System\Classes\FileLogReader` | Чтение/парсинг логов из файлов; реализация `LogReader` (позже возможен `DbLogReader`) |
| `System\Classes\Validation` | Валидация входных данных по декларативным правилам; `factory()`, `rule()`, `check()`, `errors()` | | `System\Classes\Validation` | Валидация входных данных по декларативным правилам; `factory()`, `rule()`, `check()`, `errors()` |
| `System\Classes\CSRF` | Защита форм от CSRF; токен в сессии: `token()`, `validate()`, `field()` | | `System\Classes\CSRF` | Защита форм от CSRF; токен в сессии: `token()`, `validate()`, `field()` |
| `System\Classes\HTTP` | `redirect()`, `requestHeaders()` | | `System\Classes\HTTP` | `redirect()`, `requestHeaders()` |
@ -270,6 +272,12 @@ Log::debug(...); Log::warning(...);
Каталог логов в `.gitignore` (`/App/logs/`). Каталог логов в `.gitignore` (`/App/logs/`).
**Просмотр логов:** страница `/admin/logs` (`App\Controller\Admin\LogsController`) с фильтрами
(дата, канал, уровень, текст). Источник — через интерфейс `LogReader` (реализация
`FileLogReader` парсит файлы; позже подменяется на `DbLogReader`). Каталог берётся из
`Log::directory()`. Вывод сообщений в шаблоне `App/view/Admin/Logs/logs.html` экранируется
(`htmlspecialchars`) — в логах есть пользовательский ввод (XSS-вектор).
### HTTPException (System\Classes\HTTP\HTTPException) ### HTTPException (System\Classes\HTTP\HTTPException)
```php ```php
@ -309,10 +317,12 @@ $response = HTTPException::factory(403)->getResponse();
Расстановка имён — как в проекте eoffice_v3: Расстановка имён — как в проекте eoffice_v3:
- `System\Classes\BaseController` (abstract) — голое ядро: жизненный цикл `executeAction()` (`before()` → экшен → `after()`), хуки по умолчанию пустые. Нейтрально к вебу/API. - `System\Classes\BaseController` (abstract) — голое ядро: жизненный цикл `executeAction()` (`before()` → экшен → `after()`), хуки по умолчанию пустые. Нейтрально к вебу/API.
- `System\Classes\Controller extends BaseController` — веб: `render()` (layout + content), авто-CSRF в `before()`, `json($data, $status)` для JSON-ответов. **Контроллеры приложения наследуют его.** - `System\Classes\Controller extends BaseController` — веб: `render()` (layout + content), авто-CSRF в `before()`, `json($data, $status)` для JSON-ответов, хук `layoutData()` (доп. данные в layout помимо `content`). **Контроллеры приложения наследуют его.**
API-контроллер делается не отдельным классом, а флагом: `extends Controller` + `$_csrf_protection = false` + ответы через `json()` (так же, как в eoffice_v3). API-контроллер делается не отдельным классом, а флагом: `extends Controller` + `$_csrf_protection = false` + ответы через `json()` (так же, как в eoffice_v3).
**Админка** (`App/Controller/Admin/`): базовый `App\Controller\Admin\AdminController extends Controller` задаёт layout `admin` (боковое меню, `System/view/views/admin.html`, Bootstrap из `/vendor/`) и наполняет `layoutData()` пунктами меню (`menu()` — пока статичный список, задел под БД). `before()` — заготовка под проверку прав (логина пока нет, **админка открыта**). Конкретные страницы наследуют `AdminController`.
`Request::execute()` вызывает `executeAction({action}Action)`, поэтому `before()/after()` работают для любого контроллера прозрачно. Авторизацию добавлять в `before()` конкретных контроллеров (логина пока нет). `Request::execute()` вызывает `executeAction({action}Action)`, поэтому `before()/after()` работают для любого контроллера прозрачно. Авторизацию добавлять в `before()` конкретных контроллеров (логина пока нет).
### Environment ### Environment
@ -349,8 +359,9 @@ PHPUnit 11 в Docker-контейнере `bicycle`. Bootstrap: `tests/bootstrap
| `tests/Unit/CSRFTest.php` | `token()` стабильность/формат, `validate()`, `field()`, нет токена в сессии | | `tests/Unit/CSRFTest.php` | `token()` стабильность/формат, `validate()`, `field()`, нет токена в сессии |
| `tests/Unit/ControllerTest.php` | `executeAction()`, порядок `before/action/after`, no-op хуки | | `tests/Unit/ControllerTest.php` | `executeAction()`, порядок `before/action/after`, no-op хуки |
| `tests/Unit/LogTest.php` | уровни/порог, каналы (action/error), формат, append, `strtr`, маскировка, `requestInfo()` | | `tests/Unit/LogTest.php` | уровни/порог, каналы (action/error), формат, append, `strtr`, маскировка, `requestInfo()` |
| `tests/Unit/FileLogReaderTest.php` | парсинг, фильтры (level/q/channel), newest-first, missing file, `dates()` |
**154 теста, 253 assertion — все проходят.** **162 теста, 270 assertion — все проходят.**
### Frontend dependencies (через Composer) ### Frontend dependencies (через Composer)

View File

@ -64,8 +64,19 @@ class Controller extends BaseController
} }
return (new View($this->_layout, 'view/views', [ return (new View($this->_layout, 'view/views', [
'content' => (new View($template, $dir, $data))->render() 'content' => (new View($template, $dir, $data))->render(),
]))->render(); ] + $this->layoutData()))->render();
}
/**
* Дополнительные данные, передаваемые в layout помимо 'content'
* (например меню). Переопределяется наследниками. По умолчанию пусто.
*
* @return array
*/
protected function layoutData(): array
{
return [];
} }
/** /**

View File

@ -152,10 +152,11 @@ class Log
/** /**
* Каталог логов (из свойства, конфига или дефолт APPPATH/logs). * Каталог логов (из свойства, конфига или дефолт APPPATH/logs).
* Публичный переиспользуется читалками логов (FileLogReader).
* *
* @return string * @return string
*/ */
private static function directory(): string public static function directory(): string
{ {
return self::$directory ?? Config::get('log', 'path') ?? APPPATH . '/logs'; return self::$directory ?? Config::get('log', 'path') ?? APPPATH . '/logs';
} }