Bicycle/System/Classes/Controller.php
Egor Isaev 4e41390544 dev
2026-06-26 12:52:41 +03:00

99 lines
3.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @package Bicycle
* @author Egor Isaev
* @description Controller.php
* @copyright (c) 03/06/2026
*/
namespace System\Classes;
use System\Classes\HTTP\HTTPException;
use System\Classes\HTTP\Request as HTTPRequest;
/**
* Веб-контроллер: рендеринг layout + content, авто-проверка CSRF на небезопасных
* методах и JSON-ответы. Контроллеры приложения наследуют его.
*/
class Controller extends BaseController
{
/** @var string Имя layout-шаблона в System/view/views */
protected string $_layout = 'layout';
/** @var bool Проверять ли CSRF-токен на небезопасных методах */
protected bool $_csrf_protection = true;
/**
* Проверяет CSRF-токен на POST/PUT/PATCH/DELETE.
* Отключается флагом $_csrf_protection (например, для API/вебхуков).
*
* @return void
* @throws HTTPException 403, если токен не прошёл
*/
protected function before(): void
{
if (!$this->_csrf_protection) {
return;
}
$request = Request::$current;
$method = $request?->method() ?? HTTPRequest::GET;
$unsafe = [HTTPRequest::POST, HTTPRequest::PUT, HTTPRequest::PATCH, HTTPRequest::DELETE];
if (in_array($method, $unsafe, true) && !CSRF::validate($request->post(CSRF::$key))) {
throw HTTPException::factory(403);
}
}
/**
* Рендерит шаблон контента внутри layout.
* Если $dir пуст — определяется автоматически из имени класса
* (App\Controller\FooController → view/Foo).
*
* @param string $template Имя шаблона контента без расширения
* @param array $data Данные, передаваемые в шаблон
* @param string $dir Каталог шаблона относительно view/ (опционально)
* @return string Готовый HTML
* @throws MyException
*/
protected function render(string $template, array $data = [], string $dir = ''): string
{
if ($dir === '') {
$class = substr(get_class($this), strlen('App\\Controller\\')); // [Admin\]FooController
$dir = 'view/' . str_replace('\\', '/', substr($class, 0, -10)); // view/[Admin/]Foo
}
return (new View($this->_layout, 'view/views', [
'content' => (new View($template, $dir, $data))->render(),
] + $this->layoutData()))->render();
}
/**
* Дополнительные данные, передаваемые в layout помимо 'content'
* (например меню). Переопределяется наследниками. По умолчанию пусто.
*
* @return array
*/
protected function layoutData(): array
{
return [];
}
/**
* Формирует JSON-ответ: ставит статус и Content-Type, кодирует данные.
*
* @param mixed $data Данные ответа
* @param int $status HTTP-статус
* @return string JSON
*/
protected function json(mixed $data, int $status = 200): string
{
http_response_code($status);
if (!headers_sent()) {
header('Content-Type: application/json; charset=utf-8');
}
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}
}