_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() ]))->render(); } /** * Формирует 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); } }