Skip to content

Latest commit

 

History

History
417 lines (298 loc) · 16.7 KB

File metadata and controls

417 lines (298 loc) · 16.7 KB

Контентная модель и сущности

EE_FrameWork использует гибкую контентную модель, где структура описывается не жёсткими колонками одной таблицы, а цепочкой типов, свойств, наборов и сущностей.

Этот раздел нужен, чтобы быстро понять:

  • где хранится структура;
  • где хранятся реальные значения;
  • как связаны категории, страницы, пользователи и свойства;
  • какие части модели обеспечиваются БД, а какие кодом.

Базовая цепочка

Контентная ось проекта выглядит так:

property types
    -> properties
        -> property set to properties
            -> property sets
                -> category type to property set
                    -> categories types
                        -> categories
                            -> pages

property sets
    -> user role to property set
        -> user roles
            -> users

property values хранят реальные значения для categories, pages и users

Если говорить языком таблиц:

ee_property_types
    -> ee_properties
        -> ee_property_set_to_properties
            -> ee_property_sets
                -> ee_category_type_to_property_set
                    -> ee_categories_types
                        -> ee_categories
                            -> ee_pages

ee_property_sets
    -> ee_user_role_to_property_set
        -> ee_user_roles
            -> ee_users

ee_property_values

Что хранится где

ee_property_types.fields

Это не полный шаблон свойства. Здесь хранится только структура полей типа свойства:

  • uid
  • type

Пример:

[
  { "uid": "pf_a1b2c3", "type": "text" },
  { "uid": "pf_d4e5f6", "type": "textarea" }
]

ee_properties.default_values

Это уже прикладной шаблон конкретного свойства.

Здесь лежат:

  • uid
  • type
  • label
  • title
  • default
  • required
  • multiple

То есть именно property определяет, как выглядит поле для конкретного сценария.

ee_property_values.property_values

Это реальные данные конкретной сущности.

Здесь структура почти та же, но вместо default используется value.

Именно эта таблица хранит значения категории, страницы или пользователя.

Структурные значения date-range

Поле date-range хранит интервал дат как объект с двумя строковыми границами:

{
  "uid": "pf_period",
  "type": "date-range",
  "value": {
    "from": "01.06",
    "to": "15.06"
  }
}

Для repeatable/composite-свойств value становится массивом таких объектов. Индекс элемента должен совпадать с индексами остальных fields этого же repeatable-свойства:

{
  "uid": "pf_period",
  "type": "date-range",
  "value": [
    { "from": "01.06", "to": "15.06" },
    { "from": "16.06", "to": "30.06" }
  ]
}

Ядро нормализует этот формат через единый property contract. Пустой интервал сохраняется как { "from": "", "to": "" } или отбрасывается там, где не требуется сохранять позицию repeatable-элемента.

date-range считается поисковым полем: from и to попадают в поисковый текст сущности. В фильтры этот тип не попадает автоматически, потому что корректный фильтр по датам должен проверять пересечение интервалов, а не сравнивать одно скалярное значение.

Вложенные повторяемые группы repeatable-group

repeatable-group нужен для случаев, когда одно поле свойства должно хранить несколько строк, а каждая строка состоит из нескольких связанных значений. Типичный пример — набор периодов и цен, где один период связан ровно с одной ценой.

В ee_property_types.fields такой field описывается только верхним uid/type:

[
  { "uid": "pf_prices", "type": "repeatable-group" }
]

Полная схема дочерних полей хранится в ee_properties.default_values:

{
  "uid": "pf_prices",
  "type": "repeatable-group",
  "label": "Prices",
  "fields": [
    {
      "uid": "period",
      "type": "date-range",
      "label": "Period",
      "default": { "from": "", "to": "" }
    },
    {
      "uid": "price",
      "type": "number",
      "label": "Price",
      "default": ""
    }
  ],
  "default": []
}

Живое значение не-repeatable свойства хранится как массив строк:

{
  "uid": "pf_prices",
  "type": "repeatable-group",
  "value": [
    {
      "values": {
        "period": { "from": "01.06", "to": "15.06" },
        "price": "2500"
      }
    }
  ]
}

Если repeatable-group находится внутри repeatable/composite-свойства, появляется два уровня массивов:

  • внешний массив соответствует индексу основного повторяемого элемента;
  • внутренний массив содержит строки группы для этого элемента.
{
  "uid": "pf_prices",
  "type": "repeatable-group",
  "value": [
    [
      {
        "values": {
          "period": { "from": "01.06", "to": "15.06" },
          "price": "2500"
        }
      }
    ],
    [
      {
        "values": {
          "period": { "from": "16.06", "to": "30.06" },
          "price": "3000"
        }
      }
    ]
  ]
}

Ограничения ядра:

  • дочерние поля группы не могут быть repeatable-group, file или image;
  • дочерние choice-поля должны иметь собственный options/default в схеме группы;
  • пустые строки группы отбрасываются при нормализации;
  • индексы строк не считаются стабильными идентификаторами.

repeatable-group участвует в поисковом индексе через flatten вложенных значений. В ee_filters этот тип не попадает даже для выбора: обычные materialized filters работают только с плоскими field values. Фильтры по вложенным датам, ценам и пересечению периодов должны реализовываться отдельным nested-filter контрактом поверх property layer.

Важный принцип

Ни ee_property_types, ни ee_properties, ни ee_property_sets не являются хранилищем живых значений.

Они описывают:

  • структуру;
  • шаблон;
  • привязки.

Живые значения лежат только в ee_property_values.

Область видимости свойства

Свойство имеет entity_type:

  • category
  • page
  • user
  • all

Правило такое:

  • category применяется только к категориям;
  • page применяется только к страницам;
  • user применяется только к пользователям;
  • all применяется ко всем поддерживаемым сущностям, для которых набор свойств назначен через соответствующий механизм.

Если lifecycle меняет entity_type, система должна:

  • удалить лишние property_values;
  • создать недостающие;
  • нормализовать существующие.

Важно: all не означает, что свойство появится вообще у каждой записи в системе. Сначала набор должен быть назначен сущности:

  • для категорий и страниц — через тип категории и наследование типов;
  • для пользователей — через роль пользователя.

Внутренние lifecycle-свойства

Для карточек объектов размещения можно использовать отдельный внутренний набор свойств, который не участвует в публичном контенте напрямую.

Текущий рабочий пример:

  • Статус карточки
  • Режим размещения
  • Размещение активно до
  • Контакты проверены
  • Дата последней проверки
  • Следующая перепроверка
  • Источник карточки
  • Внутренний комментарий менеджера

Эти свойства хранятся в обычных ee_properties и ee_property_values, но создаются со статусом hidden.

Это важный практический приём:

  • свойства остаются доступны в админке;
  • свойства не попадают в search/filter слой, который работает только с active свойствами;
  • структура не требует отдельной таблицы под каждую бизнес-задачу.

Такой набор подходит для:

  • публикационного статуса;
  • коммерческого режима размещения;
  • ручной верификации контактов;
  • внутренних комментариев менеджера.

Деревья

В контентной модели есть два дерева.

Категории

Дерево категорий строится через:

ee_categories.parent_id

Страницы

Дерево страниц строится через:

ee_pages.parent_page_id

Если страница создаётся с parent_page_id, её category_id должен наследоваться от родительской страницы.

Наследование наборов свойств

У типа категории есть прямые наборы свойств, но эффективный набор считается динамически:

  • собственные наборы типа категории;
  • плюс наборы всех родителей по parent_type_id.

То есть унаследованные наборы не должны физически копироваться в таблицу связей.

Наборы свойств пользователей

Пользователи не расширяются новыми колонками под каждую проектную потребность. Для этого используется тот же property layer, что и для контента.

Схема такая:

ee_property_sets -> ee_user_role_to_property_set -> ee_user_roles -> ee_users

Практический workflow:

  1. Создайте тип свойства, свойство и набор свойств в разделе “Свойства”.
  2. Укажите у свойства entity_type = user или entity_type = all.
  3. Откройте карточку роли пользователя.
  4. Назначьте роли нужные наборы свойств.
  5. Откройте карточку пользователя с этой ролью: вкладка “Свойства” покажет поля из назначенных наборов.

Значения хранятся в ee_property_values:

  • entity_type = user;
  • entity_id = ee_users.user_id;
  • set_id и property_id указывают на набор и свойство;
  • language_code использует default content language, чтобы формат оставался совместим с общей property-моделью.

При полном удалении пользователя из архива его property_values с entity_type=user также удаляются.

Категории и страницы

Категория

При создании категории система должна собрать property_values для всех подходящих свойств её эффективных наборов.

Страница

При создании страницы система должна собрать property_values по эффективным наборам её категории.

Именно поэтому изменение:

  • типа категории;
  • состава набора;
  • entity_type свойства;
  • структуры типа свойства;

может запускать lifecycle-пересчёт.

Lifecycle и синхронизация

Тяжёлые операции в этой модели включают:

  • нормализацию default_values;
  • нормализацию property_values;
  • удаление устаревших значений;
  • создание недостающих значений;
  • пересборку после изменения состава набора;
  • пересборку после изменения наборов типа категории.

Практический вывод:

  • контентная модель очень гибкая;
  • но за неё нужно платить дисциплиной lifecycle и понятной инвалидацией.

Rich text и публичный HTML

Поля description, short_description и текстовые значения свойств могут хранить rich text. Для них важно разделять две операции:

  • нормализация приводит plain text или mixed HTML к ожидаемой структуре абзацев, переносов и ссылок;
  • sanitization удаляет небезопасный HTML перед публичным выводом.

Нормализация не заменяет sanitization.

Публичные шаблоны, которые выводят rich text без htmlspecialchars, должны получать HTML уже после DOM allowlist sanitizer. Минимальная политика sanitizer:

  • удалить <script> и активные элементы вроде svg, iframe, object, embed, form, video, audio;
  • удалить event-атрибуты вроде onclick;
  • удалить URL со схемами javascript:, data:, vbscript:;
  • оставить только разрешённые теги и атрибуты;
  • применять одинаковую политику ко всем новым rich-text поверхностям.

Что обеспечивается БД, а что кодом

Важная особенность EE_FrameWork: часть правил живёт физически в схеме БД, а часть обеспечивается приложением.

Поэтому при разработке важно разделять:

  • физические FK/UNIQUE/INDEX;
  • логические инварианты модели;
  • lifecycle-правила, которые не видны из одной только схемы.

Когда этот раздел особенно полезен

Идите сюда, если вы:

  • проектируете новый тип контента;
  • меняете состав свойств категории или страницы;
  • разбираете, почему у сущности появились или не появились property_values;
  • работаете с import/lifecycle/search/filter слоями.