Skip to content

Фаза 0 — WidgetRegistry и области

Статус: спека фазы 0. Виджет — отдельная сущность (Q4), не частный случай блока. Здесь — реестр виджетов, области, условия показа и — критично — отдельная инвалидация кеша областей. Параллель BlockRegistry, но с другим жизненным циклом. Опирается на глоссарий → виджет, модель данных, движок полей.

Виджет ≠ блок: почему отдельная сущность

Решение Q4 зафиксировало разные жизненные циклы и места хранения:

БлокВиджет
Где живётв потоке контента страницыв области (сайдбар/футер/шапка)
Хранениеprops в JSONB ревизии страницысвоя таблица cms_widgets, привязка к области
Охватодна конкретная страницасквозной — на многих страницах разом
Кто редактируетконтент-менеджер в drag&drop страницыконтент-менеджер в разделе «Области»
Инвалидация кешапостраничная (меняется страница)по области (меняется на всех страницах)

Слияние в одну сущность заставило бы тащить «область» в каждый блок и ломало бы инвалидацию: правка виджета в футере затрагивает все страницы, а не одну — это принципиально другая модель сброса кеша.

Определение типа виджета

Как BlockType — объект-описание в открытом реестре. Использует тот же движок полей:

php
WidgetType::make('contacts')
    ->label('Контакты')
    ->areas(['footer', 'sidebar'])          // где допустим
    ->fields(fn () => [ /* Field-декларации движка полей */ ])
    ->view('widgets.contacts')              // резолвится каскадом тем
    ->schemaVersion(1)
    ->interactive(false)                    // React-остров, как у блока
    ->cacheScope('area');                   // область как единица кеша (см. ниже)

Регистрация — в boot() провайдера модуля/темы/проекта (открытость, как у BlockRegistry):

php
WidgetRegistry::register(ContactsWidget::describe());

Области (areas)

Область — именованное место в макете, куда тема выводит виджеты. Базовый набор ядра — header, sidebar, footer; тема объявляет свои через контракт темы.

blade
{{-- в layout темы --}}
<x-cms-area name="footer" />   {{-- рендерит все виджеты области footer по условиям показа --}}

Тема декларирует доступные области в манифесте (контракт темы); ядро валидирует, что виджет размещают в области, которую тема поддерживает.

Хранение и условия показа

cms_widgets
  id, type,                       # тип из WidgetRegistry
  area,                           # footer / sidebar / header / …
  props (jsonb),                  # значения полей (движок полей)
  sort_order,
  conditions (jsonb),             # ГДЕ показывать (см. ниже)
  is_active, _v,
  locale / city_id (nullable),    # заготовка мультигорода/локали (Q6)
  created_at, updated_at

Условия показа (conditions) — где виджет виден, аналог видимости виджетов WordPress, но декларативно:

jsonc
{
  "include": { "page_type": ["catalog", "service"], "url": ["/about*"] },
  "exclude": { "url": ["/checkout*"] }
}

Резолвер условий на рендере области решает, какие виджеты показать на текущей странице. Условия — данные, не код (не даём контент-менеджеру писать логику).

Инвалидация кеша областей — ключевое отличие

Виджет сквозной → его правка меняет все страницы, где видна область. Поэтому кеш областей инвалидируется отдельно от постраничного:

  • каждая область — свой тег кеша (area:footer, area:sidebar);
  • правка виджета (Filament afterSave) → Cache::tags(['area:footer'])->flush(), не сброс конкретной страницы и не Cache::flush() всего (filament spec);
  • страница в page-cache хранит области как отдельные кешируемые фрагменты (edge-side-includes- подобно) или помечена тегами всех областей, что на ней есть — сброс тега области инвалидирует затронутые страницы;
  • событие WidgetSaved (ось событий) → слушатели (CDN-инвалидация, прогрев) знают, что изменилась область, а не страница.

Это то, что было бы невозможно чисто, будь виджет частным случаем блока.

Рендер

php
// рендер области
$widgets = WidgetRepository::forArea('footer', context: $pageContext)   // с учётом conditions
    ->visible();                                                         // is_active + условия
foreach ($widgets as $w) {
    $type  = WidgetRegistry::get($w->type);
    $props = WidgetMigrator::upgrade($type, $w);        // до актуальной _v (как у блока)
    echo view($type->resolveView(), ['props' => $props, 'widget' => $w]);
}
  • resolveView() — каскад тем (контракт темы): проект → тема → parent → fallback ядра;
  • неизвестный тип (модуль выключен) → fallback-заглушка, не падение (деградация);
  • вывод — только ; интерактивный виджет — server-HTML + React-остров.

Версионирование

_v виджета и data-миграция props — та же механика, что у блока (контракт блока). Тип виджета объявляет миграции; применение — лениво при рендере или батчем cms:widgets:migrate.

Стыки с жизненным циклом модуля

  • виджет модуля исчезает из областей, когда модуль выключен (сценарий B из жизненного цикла): область просто не рендерит виджет, страница цела;
  • при disable/uninstall модуля — обратная проверка: «виджет модуля используется в N областях» (аналог cms_block_usage);
  • перенос сайта: виджеты входят в дамп + переносимый демо-контент темы (модель данных).

Контрактные тесты виджета (cms-testing)

  1. demo-props валидны против схемы (движок полей);
  2. рендер demo не бросает исключений, нет {!! !!} вне whitelist;
  3. цепочка _v замкнута (1→…→current);
  4. виджет размещается только в объявленных areas;
  5. правка виджета инвалидирует тег области, не всю страницу и не весь кеш;
  6. условия показа (conditions) резолвятся корректно (include/exclude).

Чеклист фазы 0 (WidgetRegistry)

  • [ ] WidgetType + открытый WidgetRegistry (регистрация модулем/темой/проектом).
  • [ ] Области: <x-cms-area>, декларация областей темой, валидация размещения.
  • [ ] Таблица cms_widgets (props jsonb, conditions jsonb, area, _v, locale/city).
  • [ ] Условия показа (include/exclude) — данные, не код.
  • [ ] Отдельная инвалидация кеша по тегу области (area:*), не постраничная.
  • [ ] Версионирование _v + data-миграция (как у блока).
  • [ ] Fallback-рендер при неизвестном типе / выключенном модуле.
  • [ ] Контрактные тесты виджета.

Связи

Внутренняя база знаний студии