Skip to content

Поля

Поле — типизированный элемент данных блока. Поля описывают, что редактор контента сможет заполнить в админке, и какие значения попадут в Blade-шаблон при рендере.

Где живут поля

Структура полей (тип, имя, key, опции) хранится в таблице block_fields. Конкретные значения, заполненные на странице — в таблице page_blocks.data (JSON). При рендере значения проходят через BlockDataResolver, который подставляет модели вместо ID и оборачивает результат в FieldsBag.

Колонки block_fields

ПолеНазначение
keyИдентификатор поля (snake_case) — используется в $fields['key']
nameОтображаемое название в админке
typeТип поля (один из перечня ниже)
default_valueЗначение по умолчанию (хранится как строка, приводится к типу через FieldValueCaster)
dataJSON-конфиг тип-специфичных опций (список для select, и т.п.)
hintПодсказка для контент-менеджера
parent_idДля вложенности (поле внутри array-repeater'а)
block_tab_id, block_section_idПривязка к табу и секции в UI
orderПорядок

Зарезервированные ключи

Эти имена нельзя использовать как key поля — они конфликтуют с переменными в шаблоне блока или системными именами Blade:

id, type, block, page, data, fields, global, actions,
request, slot, attributes, errors

Защита проверяется на уровне BlockField::RESERVED_KEYS — попытка создать поле с таким ключом будет отклонена валидатором.

Доступ к значению в Blade

В шаблоне блока значения доступны через переменную $fields, обёрнутую в Templite\Cms\Support\FieldsBag:

blade
{{ $fields['title'] }}

Поведение FieldsBag

  • Поддерживает ArrayAccess, IteratorAggregate, Countable — можно использовать как массив.
  • Для несуществующего ключа возвращает не null, а пустой FieldsBag([]). Поэтому:
    • {{ $fields['missing'] }} → пустая строка (через __toString).
    • @foreach ($fields['missing'] as $item) → 0 итераций без ошибки.
    • count($fields['missing']) → 0.
  • Вложенные массивы (например, элементы repeater'а или структура поля link) автоматически оборачиваются в FieldsBag при доступе.

Значения по умолчанию

Если поле пустое и default_value не задан, BlockDataResolver::getDefaultForType() подставляет типизированный дефолт. Шаблон всегда получает корректный тип данных:

ТипДефолт
array[]
checkboxfalse
number0
link['url' => '', 'text' => '', 'target' => '_self']
img, file, page, user, category, product, product_optionnull
text, textfield, editor, tiptap, html, color, date, datetime, select, radio''

Если default_value задан в БД — он приводится к типу через Templite\Cms\Support\FieldValueCaster::cast().


Справочник типов

Реальный список — BlockField::FIELD_TYPES. Восемнадцать базовых типов плюс три типа от модуля шопа.

text — Однострочный текст

  • Назначение: короткие строки (заголовки, имена, telephone).
  • Хранение: строка в page_blocks.data.
  • В шаблоне: строка.
blade
<h2>{{ $fields['title'] }}</h2>

textfield — Многострочный текст

  • Назначение: описания, адреса, длинные тексты без форматирования.
  • Хранение: строка с переводами строк.
  • В шаблоне: строка. Для сохранения переносов — nl2br():
blade
{!! nl2br(e($fields['description'])) !!}

number — Число

  • Назначение: цены, количество, индексы.
  • Хранение: строка (приводится к int/float).
  • В шаблоне: число (или строка с числом).
blade
{{ number_format($fields['price'], 0, '.', ' ') }}

img — Изображение

  • Хранение: ID файла в таблице files.
  • В шаблоне: модель Templite\Cms\Models\File (или null).
  • Резолвер BlockDataResolver загружает модель файла за один SQL-запрос на всю страницу (eager-load — нет N+1).
blade
<x-cms::image :file="$fields['hero']" />
<a href="{{ $fields['hero']?->url }}">Прямой URL</a>

file — Файл

  • Идентично img по структуре хранения и резолва.
  • Назначение: PDF, DOCX, ZIP — любые файлы, не предназначенные для рендера через <img>.
blade
@if ($fields['document'])
    <a href="{{ $fields['document']->url }}" download>
        Скачать {{ $fields['document']->name }}
    </a>
@endif

editor — Форматированный текст

  • Назначение: статьи, контент с форматированием (исторически — CKEditor 5 в админке).
  • Хранение: HTML-строка.
  • В шаблоне: HTML — выводить через {!! !!}, не {{ }}.
blade
{!! $fields['article_body'] !!}

tiptap — Tiptap-редактор

  • Назначение: альтернативный rich-editor (Tiptap), используется для более структурированного контента и встраивания медиа.
  • Хранение: контент в формате Tiptap (HTML или JSON — зависит от настроек поля в data).
  • В шаблоне: удобнее всего использовать встроенный компонент:
blade
<x-cms::tiptap :content="$fields['body']" />

html — Произвольный HTML

  • Назначение: вставка готовых HTML-фрагментов, embed-кодов (видео, iframe).
  • Хранение: строка.
  • В шаблоне: через {!! !!}.
blade
{!! $fields['embed_code'] !!}

WARNING

Содержимое html не санитизируется. Доверяйте только редакторам с правами blocks.edit. Для пользовательского ввода — отдельная очистка через Purifier или аналог.

select — Выбор из списка

  • Назначение: один вариант из заданного множества.
  • Опции: в block_fields.data.options — массив [{value, label}].
  • Хранение: строка (выбранное value).
  • В шаблоне: строка.
blade
<section class="hero hero--{{ $fields['align'] }}">...</section>

checkbox — Чекбокс

  • Хранение: boolean.
  • В шаблоне: boolean. Дефолт — false.
blade
@if ($fields['show_cta'])
    <x-cms::link :value="$fields['cta_link']">{{ $fields['cta_text'] }}</x-cms::link>
@endif

radio — Радио-кнопки

  • Идентично select по хранению и резолву, отличается только UI в админке.
  • Хранение: массив {url, text, target} (либо строка — нормализуется в массив автоматически).
  • В шаблоне: массив. Поля доступны как $fields['cta']['url'], $fields['cta']['text'], $fields['cta']['target'].
  • Дефолт: ['url' => '', 'text' => '', 'target' => '_self'].
blade
<a href="{{ $fields['cta']['url'] }}" target="{{ $fields['cta']['target'] }}">
    {{ $fields['cta']['text'] }}
</a>

Или через встроенный компонент:

blade
<x-cms::link :value="$fields['cta']" />

date — Дата

  • Хранение: строка в формате ISO (YYYY-MM-DD).
  • В шаблоне: строка. Форматирование — через Carbon:
blade
{{ \Carbon\Carbon::parse($fields['event_date'])->format('d.m.Y') }}

datetime — Дата и время

  • Идентично date, формат включает время.
blade
{{ \Carbon\Carbon::parse($fields['published_at'])->format('d.m.Y H:i') }}

array — Повторитель (repeater)

  • Назначение: список однотипных элементов (отзывы, преимущества, FAQ-пары).
  • Структура: подполя определяются как обычные BlockField с parent_id = <array field id> — вложенные записи в той же таблице.
  • Хранение: массив массивов.
  • В шаблоне: массив, итерируется через @foreach. Вложенные значения — FieldsBag.
blade
@foreach ($fields['features'] as $feature)
    <div class="card">
        <x-cms::image :file="$feature['icon']" />
        <h3>{{ $feature['title'] }}</h3>
        <p>{{ $feature['description'] }}</p>
    </div>
@endforeach

Внутри array могут быть любые типы (включая ссылки на файлы, страницы, других repeater'ов).

color — Цвет

  • Хранение: строка с hex-кодом (#4480F4) или другим форматом цвета.
  • В шаблоне: строка.
blade
<div style="background: {{ $fields['accent_color'] }};">...</div>

page — Ссылка на страницу CMS

  • Хранение: ID страницы из pages.
  • В шаблоне: модель Templite\Cms\Models\Page (или null). Резолвится eager-load'ом.
blade
@if ($fields['parent_page'])
    <a href="{{ $fields['parent_page']->url }}">
{{ $fields['parent_page']->title }}
    </a>
@endif

user — Ссылка на пользователя

  • Хранение: ID пользователя сайта.
  • В шаблоне: модель пользователя (или null). Резолвер регистрируется через BlockDataResolver::registerType('user', ...) в Service Provider CMS.

Шопные типы (модуль templite/shop)

Если установлен модуль шопа, появляются три дополнительных типа:

ТипНазначение
categoryСсылка на категорию товаров
productСсылка на товар
product_optionСсылка на опцию товара

Все три работают по той же схеме, что и page / user: хранится ID, в шаблон попадает модель.

Без установленного модуля шопа эти типы остаются в списке BlockField::FIELD_TYPES, но резолвер для них не зарегистрирован — значение в шаблоне будет null.


Кастомные типы

Расширить набор типов можно через BlockDataResolver::registerType() — обычно из ServiceProvider пакета или из app/Providers/AppServiceProvider.php.

php
use Templite\Cms\Services\BlockDataResolver;
use App\Models\Article;

public function boot(BlockDataResolver $resolver): void
{
    $resolver->registerType('article', fn ($id) => Article::find($id));
}

После регистрации $fields['related_article'] в шаблоне вернёт модель Article или null. Для отображения нового типа в UI добавления полей в админке также нужен соответствующий Vue-компонент-редактор.


Подводные камни

Подводные камни

  • Зарезервированные ключи (id, type, block, page, data, fields, global, actions, request, slot, attributes, errors) — использовать как key поля нельзя.
  • img и file возвращают модель File, не URL. Для прямого URL — $fields['hero']?->url. Чаще удобнее использовать <x-cms::image>.
  • link всегда нормализуется в массив {url, text, target}, даже если в БД лежит строка. Не выводите {{ $fields['cta'] }} напрямую — FieldsBag::__toString() вернёт пустую строку. Используйте либо обращение по ключу ($fields['cta']['url']), либо <x-cms::link>.
  • array (repeater) хранит вложенные поля как отдельные записи в block_fields с parent_id. При резолве файлы/страницы/пользователи внутри repeater'а тоже подгружаются eager-load'ом.
  • default_value в БД — всегда строка. Для типов checkbox, number, array корректное значение получается через FieldValueCaster::cast() при загрузке. Не пытайтесь хранить структурированный JSON в default_value — для опций типа используйте колонку data.
  • FieldsBag для несуществующего ключа возвращает пустой Bag, а не null. Конструкция $fields['x'] ?? 'fallback' не сработает?? смотрит на null, а тут всегда объект. Для проверки наличия — isset($fields['x']) или $fields->offsetExists('x').
  • Эффективность: все ссылки на файлы, страницы и кастомные сущности резолвятся одним SQL-запросом на тип сразу по всей странице. N+1 не возникает.

Связанные разделы

Распространяется под лицензией MIT.