Перейти до основного вмісту

Як тема перекриває twig-шаблон модуля

Модуль везе свою вітринну розмітку у власному пакеті: app/Modules/<Studly>/Views/<назва>.twig. Правити цей файл — очевидний і неправильний хід: це копія пакета, тож наступний реліз модуля або нічний cms9:auto-update або перезапише правку, або взагалі відмовиться оновлювати модуль і повідомить local_changes (див. Локальні правки в модулі).

Тема — єдине місце на сайті, якого не чіпає ні пакет модуля, ні збірка ядра. Тож розмітка, яку сайт хоче змінити, належить саме туди:

themes/<тема>/modules/<identif>/<шлях усередині Views>.twig

Якщо цей файл є, вітрина рендерить його замість вью модуля. Більше не змінюється нічого: модуль оновлюється як звичайно, а cms9:module-drift лишається чистим, бо всередині app/Modules/ ніхто нічого не чіпав.

  • <тема> — активна тема (settings.site_template);
  • <identif> — кодовий ідентифікатор модуля (share, buy_one_click, payment_method_2checkout), а не назва теки в StudlyCase;
  • <шлях усередині Views> — шлях точно такий, як під Views/ модуля, разом із підтеками.

Приклад: модуль share рендерить app/Modules/Share/Views/share.twig. Тема base перекриває його файлом:

themes/base/modules/share/share.twig

Повна заміна вью​

Скопіюйте вью модуля за шляхом вище і правте копію:

mkdir -p themes/base/modules/share
cp app/Modules/Share/Views/share.twig themes/base/modules/share/share.twig

Перекриття підхоплюється на наступному запиті — реєструвати його ніде не треба і кеш чистити теж (див. Кеш).

Змінити один блок замість копії файла: @module​

Повна копія заморожує розмітку модуля на день копіювання: кожна пізніша правка в модулі на цьому сайті невидима, а команда дрейфу може лише сказати, що оригінал поїхав, — злити зміни за вас вона не вміє. Коли треба змінити лише фрагмент, розширюйте оригінал, а не копіюйте його.

Оригінальне вью завжди доступне під twig-простором імен @module:

{% extends '@module/share/share.twig' %}

{% block share_label %}
<span class="my-label">Поділитися сторінкою</span>
{% endblock %}

@module/<identif>/<шлях>.twig завжди веде у файл самого модуля і ніколи назад у тему — тож файл може безпечно розширювати саме те вью, яке перекриває: рекурсії немає за побудовою.

{% include %} працює так само — це варіант на випадок, коли вью модуля не оголошує жодного {% block %} (більшість наразі не оголошує):

<div class="my-wrapper">
{% include '@module/share/share.twig' %}
</div>

Блоки — кращий контракт: якщо у вью модуля ядра потрібен блок, додайте його в сам модуль, а не копіюйте файл до себе.

На що перекриття має право вказувати​

Перекриття замкнене в теці активної теми. Резолвер приймає лише шлях, який лишається під themes/<тема>/modules/, і відмовляє всьому іншому: сегментам .., абсолютним шляхам, порожнім і здвоєним роздільникам, symlink-у з ціллю поза темою, NUL-байту в назві та будь-якому розширенню, крім .twig. Відмова — не помилка: рендериться вью самого модуля, наче перекриття й не було.

Що не переживає зміну теми​

Перекриття належать одній темі. Зміните settings.site_template — і всі перекриття попередньої теми перестають діяти, повертаються вью модулів. Копія теми (або передача її іншому сайту) несе перекриття з собою, бо це звичайні файли всередині теки теми.

Теми не входять у core-zip, тож перекриття невидиме і для cms9:core-drift, і для збірки ядра. Тримайте його в репозиторії самої теми, як і будь-який інший її файл.

Виняток — unishop. Цю тему закріплює гейт HTML-парності CrawlSweep, тож перекриття для неї свідомо вимкнені: файли під themes/unishop/modules/ команда дрейфу перелічує, але рендер їх не бере. Те саме обмеження вже діє для BaseWidget::themeOverride.

Коли перекриття зламане​

Синтаксична помилка Twig або виняток усередині перекриття не гасить сторінку. Рендерер відкочується на вью модуля і пише один рядок у writable/logs/:

ERROR - 2026-09-24 05:57:38 --> theme module override share/share.twig failed: Unclosed "(" in "@__sfovr/share/share.twig" at line 2.

Рівень саме error, а не warning, і це навмисно: у проді Config\Logger::$threshold = 4, тож warning до лога не дійшов би — сторінка мовчки рендерила б не те, що задумав сайт.

Дрейф: cms9:theme-overrides​

Перекриття — це фотографія вью модуля в один момент. Модуль оновлюється, і в оригіналі може з'явитись поле, клас чи цілий блок, про які перекриття не знає, — при цьому нічого голосно не ламається, сайт просто тихо малює торішню розмітку.

php spark cms9:theme-overrides # активна тема
php spark cms9:theme-overrides base # названа тема
php spark cms9:theme-overrides base --module share # один модуль
php spark cms9:theme-overrides base --json # машинний вивід
php spark cms9:theme-overrides base --write-hash # підписати поточний стан

Обидві форми опції працюють — --module share і --module=share.

Команда лише читає, окрім --write-hash. Вона звіряє маркер sha256 у першому рядку перекриття з поточним вью модуля:

{# cms9-override-of: sha256=782c403f0f71ef880492815406c151becb7a95bdf1b0880f9f46b7fee15bb4c8 #}

Маркер лежить у самому файлі, а не в окремому індексі, бо файли перекриттів копіюють, перейменовують і комітять поодинці — зовнішній індекс від цього розходиться мовчки. Twig-коментар не потрапляє у вивід, а перекриття без маркера так само рендериться.

Стани:

СтанЩо означаєКод виходу
okмаркер збігається з поточним вью модуля0
unpinnedмаркера немає — дрейф для цього файла не відстежується0
driftвью модуля змінилось після підпису перекриття1
orphanмодуля або перекритого вью вже немає1

Тож CI може просто запустити команду: 0 — усі підписані перекриття ще збігаються, 1 — хтось має подивитись. Помилка вжитку дає 7 (EXIT_USER_INPUT) і з дрейфом не плутається.

Порядок дій після оновлення модуля:

php spark cms9:theme-overrides base # drift → які вью поїхали
php spark cms9:module-drift share --diff # що саме змінилось в оригіналі
# перенести зміну в перекриття, далі:
php spark cms9:theme-overrides base --module share --write-hash

Кеш​

Руками чистити нічого не треба. Кеш скомпільованого twig ключується шляхом знайденого файла, а перекриття рендериться під іншою назвою шаблона, ніж вью модуля, — тож поява, зникнення чи правка перекриття дають інший скомпільований шаблон уже на наступному запиті. Правку на місці покриває auto_reload, який звіряє filemtime.

Кешується натомість готовий HTML: з увімкненим LiteSpeed-кешем гостьова сторінка віддає попередню розмітку, доки кеш не почистять. Чистьте штатно — консоллю кеша в адмінці (Optimizer), яка шле X-LiteSpeed-Purge: *. Файли кеша руками не видаляйте. Щоб перевірити зміну, нічого не чистячи, запитайте сторінку з унікальним query-рядком (?nc=1) — він обходить гостьовий кеш.

Адмінка​

Перекриття діють лише на вітрині. Шаблони адмінки йдуть іншим рендерером і під це не підпадають.