Реальный код Universal Engine, а не псевдокод: страница, маршрут, работа с universal table, идемпотентный upsert, свой скин и подключение эффектов. Всё то, из чего собран этот сайт.
Полноценная страница в одном файле: выбор скина, заголовок, header/mainmenu/footer. Инфраструктура живёт в ядре, а не в странице.
<?php
declare(strict_types=1);
// Один вызов задаёт скин - дальше вся страница рисуется его контрактом.
Sogerien::Page()->set_templates([TemplateBlog::class]);
// Единый экранировщик вывода на страницу.
function h(mixed $v): string
{
return htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
$name = trim((string)($_GET['name'] ?? 'мир'));
Sogerien::Page()->title = 'Hello - Sogerien';
Sogerien::Page()->header();
Sogerien::Page()->mainmenu();
?>
<section class="container py-5">
<h1>Привет, <?= h($name) ?></h1>
<p>Вся страница - это один файл page/hello.php. Инфраструктуры в нём ноль.</p>
</section>
<?php
Sogerien::Page()->footer();
Тонкий front controller: карта «URL-сегмент -> файл страницы», один foreach и финальный template().
<?php
declare(strict_types=1);
// ... выше: require Sogerien.php, загрузка config, DbController()->connect() ...
$routes = Sogerien::Routes();
// Карта маршрутов: сегмент URL -> файл страницы в /page. '' - это корень сайта.
$map = [
'' => '/page/home.php',
'hello' => '/page/hello.php',
'examples' => '/page/examples.php',
'docs' => '/page/docs.php',
];
foreach ($map as $url => $templatePath) {
$routes->add_template($url, $templatePath);
}
$routes->template(); // выбирает страницу по текущему URL и исполняет её
Sogerien::markDone(); // помечаем запрос завершённым
SELECT по природному ключу (entity, item_key) с именованными params и разбором JSON-ответа sql_request.
<?php
declare(strict_types=1);
$dbAlias = 'front';
// sql_request ВСЕГДА возвращает JSON-строку, а не массив - её декодируем.
$json = Sogerien::DbController()->sql_request($dbAlias, [
'sql' => "SELECT item_key, title, data
FROM sogerien
WHERE entity = :entity AND status = :status
ORDER BY updated_at DESC
LIMIT :limit",
'params' => [
':entity' => 'landing_feature',
':status' => 'published',
':limit' => 20,
],
]);
$res = json_decode($json, true);
$rows = (is_array($res) && ($res['result'] ?? false) === true)
? ($res['rows'] ?? [])
: []; // result=false -> причина в $res['error']['message']
foreach ($rows as $row) {
// jsonb-колонка может прийти массивом или строкой - нормализуем перед доступом.
$data = is_array($row['data'] ?? null)
? $row['data']
: (array)json_decode((string)($row['data'] ?? '[]'), true);
$title = (string)($row['title'] ?? '');
$icon = (string)($data['icon'] ?? '');
}
INSERT ... ON CONFLICT (entity, item_key) DO UPDATE: повторный вызов не плодит дубли, а обновляет запись и updated_at.
-- Идемпотентная запись в universal table sogerien.
-- Природный уникальный ключ таблицы - пара (entity, item_key).
INSERT INTO sogerien (entity, item_key, title, data, status)
VALUES (:entity, :item_key, :title, CAST(:data AS jsonb), :status)
ON CONFLICT (entity, item_key) DO UPDATE
SET title = EXCLUDED.title,
data = EXCLUDED.data,
status = EXCLUDED.status,
updated_at = now()
RETURNING id;
Duck-typed контракт скина: head-CSS/JS, класс и атрибуты <body>, хуки открытия/закрытия. Эффекты подмешиваются из Affects.
<?php
declare(strict_types=1);
/**
* Скин лендинга sogerien.top. Лёгкий холст: свой CSS/JS страницы плюс
* эффекты из Affects. Контракт duck-typed - Template зовёт эти методы сам.
*/
final class TemplateLanding
{
use SogerienClassHelp;
public function __construct(private Template $template)
{
}
/**
* @return array<int,string>
*/
public function get_head_css_urls(): array
{
$domain = (string)Sogerien::InputRequest()->sogerien_domain . '/sogerien';
return array_merge([
$domain . '/page/css/landing/landing.css',
], Affects::get_head_css_urls($domain));
}
/**
* @return array<int,array{src:string,defer:bool}>
*/
public function get_head_js_urls(): array
{
$domain = (string)Sogerien::InputRequest()->sogerien_domain . '/sogerien';
return array_merge([
['src' => $domain . '/page/js/landing/landing.js', 'defer' => true],
], Affects::get_head_js_urls($domain));
}
public function get_body_class(): string
{
return 'sg-landing';
}
/**
* @return array<string,string>
*/
public function get_body_attributes(): array
{
return ['data-theme' => 'dark'];
}
public function render_body_open(): void
{
}
public function render_body_close(): void
{
}
}
Сторонний эффект (AOS) добавляется одной строкой в css- и js-массивы Affects и появляется на всех страницах через скин.
<?php
declare(strict_types=1);
/**
* Единственная точка подключения сторонних библиотек и эффектов.
* Ни один <link>/<script> не пишется в разметку страницы - только сюда.
*/
final class Affects
{
use SogerienClassHelp;
/**
* @return array<int,string>
*/
public static function get_head_css_urls(string $domain): array
{
return [
$domain . '/page/effects/aos/aos.css',
$domain . '/page/effects/proxymint-background-kit/proxymint-background-kit.css',
];
}
/**
* @return array<int,array{src:string,defer:bool}>
*/
public static function get_head_js_urls(string $domain): array
{
return [
['src' => $domain . '/page/effects/aos/aos.js', 'defer' => true],
['src' => $domain . '/page/effects/aos/aos.init.js', 'defer' => true],
];
}
}