Дополнительные поля - использование в плагинах

В новой разрабатываемой версии LiveStreet есть функционал дополнительных (пользовательских) полей. Сейчас он задействован для топиков. Суть сводится к удобной возможности добавлять в различные объекты новые поля разных типов.

Сейчас поддерживаются следующие типы полей:
  • Целое число
  • Дробное число
  • Строка
  • Текст
  • Чекбокс
  • Дата
  • Выпадающий список
  • Теги
  • Ссылка на видео
  • Файл
  • Изображение

Постепенно мы наращиваем функционал и количество типов.
За весь функционал по полям отвечает модуль Property. Рассмотрим пример подключения функционала дополнительных полей к плагину «Статьи» на базе ORM.

Подключение
Первое, что нужно сделать, это при активации плагина зарегистрировать новый тип для дополнительных полей.
public function Activate() {
	 /**
	 * Создаем новый тип для дополнительных полей
	 * Третий параметр true ознает перезапись параметров, если такой тип уже есть в БД
	 */
	if (!$this->Property_CreateTargetType('article',array('entity'=>'PluginArticle_ModuleMain_EntityArticle','name'=>'Статьи'),true)) {
		return false;
	}
	return true;
}

Здесь мы регистрируем новый тип «article» с объектом ORM сущности PluginArticle_ModuleMain_EntityArticle.
Далее необходимо в саму сущность PluginArticle_ModuleMain_EntityArticle добавить новый метод getPropertyTargetType()
	 /**
	 * Возвращает тип для дополнительных полей.
	 * Необходим для интеграции с дополнительными полями.
	 *
	 * @return string
	 */
	public function getPropertyTargetType() {
		return 'article';
	}

Всё. Теперь к объекту статей можно смело добавлять новые поля. Добавление происходит через специальный интерфейс «Пользовательские поля» в админке.
Да, и нужно не забыть при деактивации плагина удалить за собой созданный тип:
public function Deactivate() {
	$this->Property_RemoveTargetType('article',ModuleProperty::TARGET_STATE_NOT_ACTIVE);
	return true;
}


Часто бывает необходимость при активации плагина автоматически создать нужный набор дополнительных полей (дефолтный набор). Сделать это можно в методе активации плагина вот так:
		 /**
		 * Добавляем новые поля к статьям, далее пользователь может делать это через интерфейс админки
		 */
		$aProperties=array(
			array(
				'data'=>array(
					'type'=>ModuleProperty::PROPERTY_TYPE_INT,
					'title'=>'Номер',
					'code'=>'number',
					'sort'=>100
				),
				'validate_rule'=>array(
					'min'=>10
				),
				'params'=>array(),
				'additional'=>array()
			)
		);
		if (!$this->Property_CreateDefaultTargetPropertyFromPlugin($aProperties,'article')) {
			return false;
		}


Переходим к добавлению новых элементов новых полей на форму создания объекта «Статьи».
Если интерфейс плагина находится в админке ( livestreet.ru/blog/dev_documentation/16562.html ), то подключение новых полей к форме происходит так:
	{* Подключаем блок для управления дополнительными свойствами *}
	{insert name="block" block="propertyUpdate" params=[ 'plugin' => 'admin', 'target' => $oArticle, 'entity' => 'PluginArticle_ModuleMain_EntityArticle' ]}

В примере мы определяем параметры блока и подключаем его. Этот блок универсальный и подходит как для создания объектов (статей), так и для редактирования (в параметре target передается редактируемый объект). Если плагин не используется админку для своего интерфейса, то достаточно из параметров подключения блока удалить plugin = 'admin'. В параметре entity необходимо передать класс объекта.

После добавление этого блока у нас на форме создания статей появились новые поля для заполнения.
Теперь осталось их сохранять/обновлять при создании/редактировании статьи. Значения дополнительных полей передаются в форме в параметре property и они автоматически подхватываются из реквеста. Но можно это прописать и в явном виде (не обязательно, просто для наглядности в коде). Для этого в коде сохранения объекта (статьи), там где происходит присвоение значений перед валидацией можно вставить код:
$oArticle->setProperties(getRequest('property'));


Использование
Выводить данные из дополнительных полей можно как угодно. Можно получить сразу полный список текущих полей у объекта через метод getPropertyList():
{$aProperties = $oArticle->getPropertyList()}
{foreach $aProperties as $oProperty}
	{$oProperty->getTitle()}: {$oProperty->getValue()->getValueForDisplay()}
{/foreach}

Метод $oProperty->getValue() возвращает объект сущности, который соответствует таблице property_value, именно в ней хранятся данные конкретных полей.
Также есть возможность получить конкретное поле по его коду или ID:
{if $oProperty = $oArticle->getProperty('price')}
	{$oProperty->getValue()->getValueForDisplay()}
{else}
	Значение не определено
{/if}

Дополнительно можно использовать такой код для вывода всех полей:
{$aProperties = $oArticle->getPropertyList()}
{include 'property/render.list.tpl' aPropertyItems=$aProperties}

В этом примере для показа полей будут использоваться шаблоны из property/*. У каждого типа поля может быть собственный шаблон, если его нет, то используется базовый. Также есть возможность задавать собственные шаблоны для конкретного типа таргета и типа поля. Поиск шаблона происходит по цепочке:
property/item.[type].[target_type].tpl -> property/item.[type].tpl -> property/item.base.tpl

Такой подход позволяет, например, задать собственный шаблон для поля «Изображение» у статей.

По умолчанию данные для метода getPropertyList() используют «ленивую» загрузку, т.е. запрашиваются из БД в момент вызова метода. Это хорошо, если предполагается работать только с одним объектом, но если нужно вывести дополнительные поля сразу у списка объектов (список новых статей), то возникает чрезмерная нагрузка на БД. Чтобы это избежать, по аналогии с опцией #with в ORM, введена новая опция — '#properties'=>true. Если ее указать, то данные по дополнительным полям будут загружены в момент выполнения основного запроса на получение списка статей. Это позволит сократить число обращений к БД до минимума.

Возможно самые любопытные уже задаются вопросом, а можно ли искать и сортировать по дополнительным полям? Можно!
Например, вот так можно искать:
$aFilter=array(
	'#prop:price <' => 100,
	'#properties' => true,
	'#order' => array('id'=>'desc')
);
$aResult=$this->PluginArticle_Main_GetArticleItemsByFilter($aFilter);

Поле для поиска задается в виде #prop:[code], где [code] это код дополнительного поля. Поиск поддерживает для простых типов (числа, строки, чекбоксы, текст).

А вот так можно сортировать по дополнительному полю:
$aFilter=array(
	'state' => 1,
	'#order' => array('prop:price'=>'asc')
);
$aResult=$this->PluginArticle_Main_GetArticleItemsByFilter($aFilter);

Поле для сортировки указывается как prop:[code].

Вот такие основные принципы работы с новым функционалом дополнительных полей. Пример их использования можно посмотреть в плагине «Article». Важно помнить, что этот функционал создан для покрытия большинства простых задач с данными, если вам необходимо сложная логика (создание, выборка и т.п.), то лучше использовать нативные поля в таблице БД.

Если у вас возникли вопросы, задавайте их в комментах.

UPDATE 18.06.2014
Функционал полей претерпел изменения по части подключения к плагинам/объектам.
Теперь не нужно в сущность добавлять метод getPropertyTargetType(), вместо этого необходимо использовать новый функционал поведений (behavior). Для этого нужно в сущности статьи и модуле статьи сделать объявление использования поведений (имя поведения строго должно быть property):
class PluginArticle_ModuleMain_EntityArticle extends EntityORM {

	protected $aBehaviors=array(
		'property'=>array(
			'class'=>'ModuleProperty_BehaviorEntity',
			'target_type'=>'article'
		)
	);

	// ......
}

// и в модуле, для возможности делать фильтры/сортировку по полям при ORM запросах
class PluginArticle_ModuleMain extends ModuleORM {

	protected $aBehaviors=array(
		'property'=>'ModuleProperty_BehaviorModule'
	);
}

Дополнительно к этому, теперь запросы к методам вида gerProperty* необходимо делать через объект поведения, например, было $oArticle->getPropertyList() стало $oArticle->property->getPropertyList()

UPDATE 25.09.2014
Изменился способ подключения блока для редактирования полей, сейчас он выглядит так (в тексте статьи уже исправлено):
{insert name="block" block="propertyUpdate" params=[ 'plugin' => 'admin', 'target' => $oArticle, 'entity' => 'PluginArticle_ModuleMain_EntityArticle' ]}

Изменились названия классов поведений:
ModuleProperty_BehaviorPropertyEntity -> ModuleProperty_BehaviorEntity
ModuleProperty_BehaviorPropertyModule -> ModuleProperty_BehaviorModule

21 комментарий

avatar
Фича очень любопытная и перспективная.

Очень бы хотелось типа поля с мультиселектом, чтобы его можно было «рендерить» и как список чекбоксов и как select с мультивыбором. Достаточно часто возникает потребность в таких полях.
avatar
Множественный селект есть. На счет разных представления — сделаем.
avatar
Вот наконец и появились новые интересные технические статьи! Спасибо.
avatar
Предлагаю дополнить список ещё одним полем — геоинформационные данные, которое состоит из двух значений: хранящих широту и долготу.

Планируется ли возможность ролевого доступа к полям?
avatar
Разработчики молодцы, честно признаюсь сам я livestreet не использую ни в каком виде.
Но мне очень интересен подход к архитектуре самого livestreet, как в данном случае идеи с дополнительными полями. Я сам пишу цмс, и в ней тоже реализовал примерно похожий функционал для плагинов.Только немного в другом виде.
Огромное спасибо комьюнити livestreet за интересные, свежие, и продуктивные идеи
avatar
Можно ли с помощью данного модуля создать для блогов свойства, значения которых нужно будет заполнять у топиков? Например блогу «Техническая документация LiveStreet» создадим свойство раздел, а блогу «Опросы» — тип голосования (открытое или закрытое).
avatar
если к сущности блога прицепить все возможные свойства, а потом создать отдельную таблицу связей «ид свойства-ид блога», то можно
avatar
Возможно не в тему ребят, но как можно сделать доп поля в профиле?
avatar
site/admin там есть настройка доп. полей
avatar
Добавил в топик update в связи с новым функционалом поведений (behavior), код в тестовом плагине Article адаптирован под новый функционал.
  • ort
  • 0
avatar
UPDATE 25.09.2014
Изменился способ подключения блока для редактирования полей, сейчас он выглядит так (в тексте статьи уже исправлено):
{insert name="block" block="propertyUpdate" params=[ 'plugin' => 'admin', 'target' => $oArticle, 'entity' => 'PluginArticle_ModuleMain_EntityArticle' ]}

Изменились названия классов поведений:
ModuleProperty_BehaviorPropertyEntity -> ModuleProperty_BehaviorEntity
ModuleProperty_BehaviorPropertyModule -> ModuleProperty_BehaviorModule
  • ort
  • +1
avatar
А с чем связаны изменения?
avatar
Как через плагин можно расширить типы данных? Мне нужно создать тип правила.
avatar
а в чем проблема возникает? Стандартно через наследование классов и шаблонов.
avatar
Мне получается нет от чего наследоваться
ValueTypeRule.entity.class.php
avatar
начальный класс не обязательно должен существовать, поэтому можно в плагине смело делать:
'entity' => array(
    'ModuleProperty_EntityValueTypeRule'
);

После этого система будет думать, что класс ModuleProperty_EntityValueTypeRule существует и использовать его как обычно.
avatar
Спасибо, получилось.
avatar
Здравствуйте, почему не получается получить конкретное поле по его коду?
Список полей получить удается
{$aProperties = $topic->getPropertyList()}
{foreach $aProperties as $oProperty}
	{$oProperty->getTitle()}: {$oProperty->getValue()->getValueForDisplay()}
{/foreach}

А вот получить поле по его коду не удается, почему-то возвращает NULL
{$topic->getProperty('pin')->getValue()->getValueForDisplay()}
avatar
Обращаться к полям нужно через имя поведения:
{$topic->property->getProperty('pin')->getValue()->getValueForDisplay()}
avatar
Спасибо Максим :)
avatar
спасибо, тоже пригодилось..эвакуатор, эвакуация, эвакуатор спб, эвакуатор санкт-петербург, эвакуатор петербург, эвакуация автомобилей, эвакуатор дешевогрузоперевозки спбпокраска автомобиля спбгрузоперевозки спбвызов такси в спб,на вокзал,в аэропортдачные дома и бани из бруса под ключкузовной ремонт автомобилей спбкузовной ремонт автомобилей спбвыкуп автоаренда автовышек
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.