Представлення
HTML у Concept Core рендериться через Twig або Plates.
Шаблони — файли .twig або .php у каталозі views; у контролері відповідь збирається через
ViewResponseFactoryInterface::create() (див. Відповіді → HTML).
Низькорівневий рендер — ViewInterface::render() (сервіси, error handler).
Шляхи до views налаштовуються в config/view.php і через
ComponentInterface::viewPaths() / viewContexts() компонентів.
Простори імен Twig
Шаблон з контролера вказується як @namespace/шлях/без_розширення.
Namespace реєструється в конфігурації:
// config/view.php
'paths' => [
'frontend' => '/resources/views/frontend',
'dashboard' => '/resources/views/dashboard',
],
// AuthAdminComponent::viewPaths()
public function viewPaths(): array
{
return [
'auth-admin' => $this->pathManager->toRelative($this->componentDir . '/Views'),
];
}
Файл src/Components/AuthAdmin/Views/users/list.twig → @auth-admin/users/list.
Каталог resources/views (PathName::VIEWS у paths.php) також додано
як fallback без namespace — рідко потрібен, якщо всі шаблони в namespaced paths.
Рендер з контролера
return $this->viewResponse->create('@frontend/index');
return $this->viewResponse->create('@cabinet/index', [
'user' => $user,
'latestProject' => $latestProject,
]);
Другий аргумент — змінні лише для цього запиту. Вони зливаються з глобальним контекстом
(errors, old, flashes, csrf_token) з
ShareViewDataMiddleware.
Layouts і partials
Типова структура:
resources/views/frontend/
├── layouts/base.twig
├── partials/_navbar.twig
├── partials/_flash.twig
└── index.twig
src/Components/UserCabinet/Views/
├── layouts/cabinet.twig
├── layouts/auth.twig
└── index.twig
{# index.twig #}
{% extends '@frontend/layouts/base.twig' %}
{% block title %}Головна{% endblock %}
{% block body %}
<h1>Контент</h1>
{% endblock %}
{# Підключення фрагмента #}
{% include '@frontend/partials/_flash.twig' %}
Компонентні сторінки часто наслідують спільний layout іншого namespace
(наприклад адмін: @auth-admin/users/list.twig → @dashboard/layouts/base.twig).
URL у шаблонах
У skeleton функції uri(), url(), base_url() реєструє
AppExtension
(config/view.php → view.extensions).
<a href="{{ uri('architecture') }}">Architecture</a>
<a href="{{ url('cabinet.login') }}">Login</a>
<a href="{{ uri('admin.user.edit', { id: user.id }) }}">Edit</a>
<link rel="canonical" href="{{ base_url() }}">
uri()— відносний шлях за іменем маршруту (UrlGeneratorInterface::uri())url()— повний URL (UrlGeneratorInterface::url())base_url()— база застосунку (UrlGeneratorInterface::base())
Детальніше про імена маршрутів — маршрутизація.
Глобальні змінні та flash
| Змінна | Джерело |
|---|---|
errors | Помилки валідації (flash) |
old | Попередній ввід після failed validation |
flashes | Усі flash-повідомлення (success, error…) |
csrf_token | Токен для форм |
route_namespace | Контекст UI за префіксом URI — детально |
<input name="email" value="{{ old.email|default('') }}"
class="input {{ errors.email is defined and errors.email ? 'is-invalid' : '' }}">
{% if errors.email is defined and errors.email is not empty %}
<div class="field-error" role="alert">
{{ errors.email is iterable ? errors.email|join(', ') : errors.email }}
</div>
{% endif %}
Flash повідомлення:
{% if flashes is defined and flashes|length > 0 %}
{% for type, messages in flashes %}
{% for message in messages %}
<div class="alert alert-{{ type == 'error' ? 'danger' : type }}">{{ message }}</div>
{% endfor %}
{% endfor %}
{% endif %}
Детальніше про валідацію — Запити та валідація.
route_namespace
route_namespace — не те саме, що Twig-namespace шаблону
(@cabinet/index). Це логічний контекст інтерфейсу: за поточним URL
визначається, в якому «оформленні» працює сторінка (публічний сайт, адмінка, кабінет).
Змінна доступна у всіх шаблонах як глобальна: її додає
ContextExtension через ViewContextResolver.
Навіщо
У одному застосунку кілька зон з різними layout і стилями. При 404/500 або в спільних partial потрібно знати, який набір views підключати, без перевірки шляху в кожному Twig-файлі.
route_namespace відповідає: префікс URI → ім’я контексту з конфігурації.
Як визначається
- У
view.contexts(застосунок + компоненти) задають мапу префікс URI → контекст. -
ViewContextResolverбере шлях запиту, перебирає префікси від найдовшого (щоб/admin/usersзбігся з/admin, а не з/). ContextExtension::getGlobals()повертаєroute_namespaceу Twig.
// config/view.php
'contexts' => [
'/' => 'frontend',
],
// AuthAdminComponent::viewContexts()
public function viewContexts(): array
{
return ['/admin' => 'dashboard'];
},
// SettingsManagerComponent::viewContexts()
public function viewContexts(): array
{
return ['/admin' => 'dashboard'];
},
| URL | route_namespace |
|---|---|
/, /architecture | frontend |
/admin/users | dashboard |
/cabinet/profile | cabinet |
Приклад: сторінки помилок
Можна використати один диспетчер у resources/views/errors/ який підключає 404/500
відповідного контексту:
{# resources/views/errors/404.twig #}
{% include '@' ~ (route_namespace|default('frontend')) ~ '/errors/404.twig' %}
/unknown-page→frontend→@frontend/errors/404.twig(публічний layout)/admin/missing→dashboard→@dashboard/errors/404.twig(layout адмінки)
Публічна 404 наслідує @frontend/layouts/base.twig;
адмінська — @dashboard/layouts/error.twig.
Без route_namespace довелося б дублювати умови «якщо /admin — один шаблон, інакше інший».
Інше використання в Twig
{# Різний partial залежно від зони #}
{% if route_namespace == 'dashboard' %}
{% include '@dashboard/partials/_sidebar.twig' %}
{% else %}
{% include '@frontend/partials/_navbar.twig' %}
{% endif %}
{# Динамічний include (ignore missing — якщо partial відсутній) #}
{% include '@' ~ route_namespace ~ '/partials/_breadcrumbs.twig' ignore missing %}
Важливий нюанс
Значення в contexts (dashboard, frontend, cabinet)
має відповідати Twig-namespace, де реально лежать потрібні шаблони.
У AuthAdmin контекст названо dashboard, а не auth-admin: сторінки помилок
і спільний layout адмінки — у @dashboard/, а контент CRUD — у @auth-admin/.
Для /cabinet контекст cabinet, але окремих
Views/errors/ у компоненті UserCabinet немає. Якщо потрібна своя 404 у кабінеті —
додайте @cabinet/errors/404.twig або змініть диспетчер із fallback на
frontend.
Конфігурація config/view.php
return [
'view' => [
'extensions' => [
AppExtension::class, // has_component()
ContextExtension::class, // route_namespace global
UrlExtension::class, // uri(), url(), base_url()
],
'paths' => [ /* namespace => шлях від кореня проєкту */ ],
'contexts' => [
'/' => 'frontend', // URI prefix => ім'я контексту для route_namespace
],
'cache_dir' => 'views', // storage/cache/views у production
],
];
- extensions — класи
Twig\Extension\AbstractExtension, резолвяться з DI-контейнера - paths — Twig namespaces для застосунку
- contexts — префікс URI →
route_namespace(детально) - cache_dir — підкаталог у
storage/cache; приapp.debugкеш Twig вимкнено
Views у компонентах
Компонент оголошує views через методи viewPaths(), viewContexts()
і опційно viewExtensions(). Під час ComponentsServiceProvider::boot()
дані додаються до реєстрів і підхоплюються Twig при HTTP-запиті (не в CLI).
// AuthAdminComponent — власне розширення + шаблони
public function viewExtensions(): array
{
return [TwigExtension::class];
}
public function viewPaths(): array
{
return [
'auth-admin' => $this->pathManager->toRelative($this->componentDir . '/Views'),
];
}
public function viewContexts(): array
{
return ['/admin' => 'dashboard'];
}
У Twig: {{ auth().check() }} (якщо підключено розширення компонента),
{{ has_component('UserCabinet') }} — з AppExtension.
Власне Twig-розширення
class MyExtension extends AbstractExtension
{
public function getFunctions(): array
{
return [
new TwigFunction('format_money', [$this, 'formatMoney']),
];
}
public function getFilters(): array
{
return [
new TwigFilter('excerpt', [$this, 'excerpt']),
];
}
}
Зареєструйте клас у view.extensions (застосунок) або в
viewExtensions() компонента. У шаблоні: {{ price|format_money }}, {{ format_money(price) }}.
Прямий виклик ViewInterface
/** @var ViewInterface $view */
$view = $container->get(ViewInterface::class);
$html = $view->render('@frontend/errors/404', ['exception' => $e]);
Розширення .twig додається автоматично, якщо не вказано.
Телеметрія: TelemetryEvent::TPL_RENDERED у TwigView::render()
(Телеметрія).
У debug — DebugExtension і Twig Profiler.
Сторінки помилок
Диспетчер resources/views/errors/{код}.twig через
route_namespace підключає
@frontend/errors/... або @dashboard/errors/....
У production error handler рендерить той самий шлях; якщо файлу немає — fallback на 500.
Додаткові fallback-файли PHP — resources/views/errors/fallback/
(PathName::ERRORS_FALLBACK_VIEWS).
Статика в шаблонах
CSS/JS зазвичай лежать у public/ і підключаються абсолютними шляхами
(/assets/css/frontend.css, /vendor/bootstrap/...).
Компоненти можуть публікувати assets через assets()
(копіювання в public/components/... командою component:publish-assets).
Логіку (запити до БД, права доступу) тримайте в контролерах і сервісах. У Twig — відображення, умови, цикли та допоміжні фільтри.