210 lines
7.2 KiB
PHP
210 lines
7.2 KiB
PHP
<?php
|
||
/**
|
||
* @package Bicycle
|
||
* @author Egor Isaev
|
||
* @description View.php
|
||
* @copyright (c) 03/06/2026
|
||
*/
|
||
|
||
namespace System\Classes;
|
||
|
||
use Throwable;
|
||
|
||
/**
|
||
* Рендеринг .html-шаблонов через ob_start + extract + include.
|
||
*/
|
||
class View
|
||
{
|
||
/**
|
||
* @var string Полный путь к найденному файлу шаблона
|
||
*/
|
||
public string $_file;
|
||
|
||
/**
|
||
* @var array Данные, доступные внутри шаблона
|
||
*/
|
||
public array $_data = [];
|
||
|
||
/**
|
||
* @var array<int,string> Зарегистрированные пути CSS-файлов — общие для всех View за
|
||
* один запрос (content рендерится раньше layout, см. Controller::render())
|
||
*/
|
||
private static array $_styles = [];
|
||
|
||
/**
|
||
* @var array<int,array{src:string,type:string}> Зарегистрированные JS-файлы
|
||
*/
|
||
private static array $_scripts = [];
|
||
|
||
/**
|
||
* @var string|null Путь к зарегистрированному manifest-файлу
|
||
*/
|
||
private static ?string $_manifest = null;
|
||
|
||
|
||
/**
|
||
* @param string|null $file Имя файла шаблона без расширения
|
||
* @param string $dir Каталог шаблона относительно базовых путей
|
||
* @param array|null $data Данные для шаблона
|
||
|
||
*/
|
||
public function __construct(?string $file = null, string $dir = '', ?array $data = null)
|
||
{
|
||
if ($file !== null) {
|
||
$this->setFilename($file, $dir);
|
||
}
|
||
|
||
if ($data !== null) {
|
||
// Добавить значения к текущим данным
|
||
$this->_data = $data + $this->_data;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Устанавливает файл шаблона, проверяя его существование.
|
||
*
|
||
* @param string $file Имя файла шаблона без расширения
|
||
* @param string $dir Каталог шаблона (обязателен)
|
||
* @return View
|
||
*/
|
||
public function setFilename(string $file, string $dir = ''): self
|
||
{
|
||
if ($dir === '') {
|
||
throw new MyException("Параметр dir не передан в View::setFilename для файла '$file'");
|
||
}
|
||
|
||
if (($path = Core::findFile($dir, $file, 'html')) === false) {
|
||
throw new MyException("При запросе view файл $dir/$file не найден");
|
||
}
|
||
|
||
// Store the file path locally
|
||
$this->_file = $path;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Выполняет шаблон и возвращает результат как строку.
|
||
*
|
||
* @return string Отрендеренный HTML
|
||
* @throws MyException Если файл не установлен или ошибка при рендеринге
|
||
*/
|
||
public function render(): string
|
||
{
|
||
if (empty($this->_file)) {
|
||
throw new MyException('Нужно установить файл для использования во view перед преобразованием');
|
||
}
|
||
|
||
ob_start();
|
||
extract($this->_data, EXTR_SKIP);
|
||
|
||
try {
|
||
include $this->_file;
|
||
|
||
return ob_get_clean();
|
||
} catch (Throwable $e) {
|
||
ob_end_clean();
|
||
|
||
throw new MyException(
|
||
"Ошибка при render файла: {$e->getFile()} на строке {$e->getLine()} - {$e->getMessage()}", null, 0, $e
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Регистрирует CSS-файл из App/media/css — вызывается прямо из шаблона:
|
||
* <?php $this->setStyle('about.css'); ?>. Выводится позже через getStyles()
|
||
* (обычно из layout.html, после того как content уже отрендерен).
|
||
*
|
||
* @param string $file Имя файла с расширением, например 'about.css'
|
||
* @return void
|
||
*/
|
||
public function setStyle(string $file): void
|
||
{
|
||
$name = pathinfo($file, PATHINFO_FILENAME);
|
||
|
||
if (Core::findFile('media/css', $name, 'css') === false) {
|
||
echo '<div class="alert alert-danger" role="alert">Файл \'' .
|
||
htmlspecialchars($file, ENT_QUOTES, 'UTF-8') . '\' в media/css не найден</div>';
|
||
|
||
return;
|
||
}
|
||
|
||
self::$_styles[] = "/App/media/css/$name.css";
|
||
}
|
||
|
||
/**
|
||
* Регистрирует JS-файл из App/media/js — вызывается прямо из шаблона:
|
||
* <?php $this->setScript('about.js'); ?>. Выводится позже через getScripts().
|
||
*
|
||
* @param string $file Имя файла с расширением, например 'about.js'
|
||
* @param string $type Атрибут type у <script>
|
||
* @return void
|
||
*/
|
||
public function setScript(string $file, string $type = 'text/javascript'): void
|
||
{
|
||
$name = pathinfo($file, PATHINFO_FILENAME);
|
||
|
||
if (Core::findFile('media/js', $name, 'js') === false) {
|
||
echo '<div class="alert alert-danger" role="alert">Файл \'' .
|
||
htmlspecialchars($file, ENT_QUOTES, 'UTF-8') . '\' в media/js не найден</div>';
|
||
|
||
return;
|
||
}
|
||
|
||
self::$_scripts[] = ['src' => "/App/media/js/$name.js", 'type' => $type];
|
||
}
|
||
|
||
/**
|
||
* Регистрирует manifest (PWA) из App/media — <?php $this->setManifest('manifest.json'); ?>.
|
||
*
|
||
* @param string $file Имя файла, например 'manifest.json' или 'manifest.webmanifest'
|
||
* @return void
|
||
*/
|
||
public function setManifest(string $file): void
|
||
{
|
||
$name = pathinfo($file, PATHINFO_FILENAME);
|
||
$ext = pathinfo($file, PATHINFO_EXTENSION) ?: 'json';
|
||
|
||
if (Core::findFile('media', $name, $ext) === false) {
|
||
echo '<div class="alert alert-danger" role="alert">Файл \'' .
|
||
htmlspecialchars($file, ENT_QUOTES, 'UTF-8') . '\' в media не найден</div>';
|
||
|
||
return;
|
||
}
|
||
|
||
self::$_manifest = "/App/media/$name.$ext";
|
||
}
|
||
|
||
/**
|
||
* Выводит <link> для manifest и всех зарегистрированных стилей — вызывать в <head> layout.html:
|
||
* <?php $this->getStyles(); ?>.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function getStyles(): void
|
||
{
|
||
if (self::$_manifest !== null) {
|
||
echo '<link rel="manifest" href="' . htmlspecialchars(self::$_manifest, ENT_QUOTES, 'UTF-8') . "\">\n";
|
||
}
|
||
|
||
foreach (self::$_styles as $href) {
|
||
echo '<link rel="stylesheet" href="' . htmlspecialchars($href, ENT_QUOTES, 'UTF-8') . "\">\n";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Выводит <script> для всех зарегистрированных скриптов — вызывать перед </body> layout.html:
|
||
* <?php $this->getScripts(); ?>.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function getScripts(): void
|
||
{
|
||
foreach (self::$_scripts as $script) {
|
||
echo '<script type="' . htmlspecialchars($script['type'], ENT_QUOTES, 'UTF-8') . '" src="' .
|
||
htmlspecialchars($script['src'], ENT_QUOTES, 'UTF-8') . "\"></script>\n";
|
||
}
|
||
}
|
||
}
|