Skip to content
Concrete5 на русском языке

Добавление пользовательских значений и параметров объектов в тип атрибута

Теперь, когда мы создали пользовательский атрибут, который расширяет тип базового атрибута, давайте дальше поработаем с этотим атрибутом и добавим к нему пользовательские настройки и данные. Как только вы освоите это, вы сможете создать любой тип атрибута.

Атрибут Местоположение Офиса из нашего предыдущего примера, позволяет нам выбрать местоположение офиса на данной странице. Давайте расширим его, чтобы он делал еще пару вещей:

  1. При добавлении атрибута мы можем выбрать, следует ли перечислять параметры в виде выпадающего списка  или как ряд радио кнопок.
  2. При сохранении атрибута, давайте выбирем, разрешим ли мы использовать пользовательское текстовое название для сохраненного местоположения. Если да, мы будем использовать этот текстовое название при отображении атрибута (о котором мы будем беспокоиться в следующем документе).

Для этих модификаций потребуется несколько изменений в коде атрибута.

  1. Нам придется изменить контроллер атрибутов так, чтобы он больше не наследовал от класса Concrete\Attribute\Number\Controller, поскольку он больше не сохраняет свои значения данных в объектах Concrete\Core\Entity\Attribute\Value\Value\NumberValue (и вместо этого будет предоставлять свои собственные объекты значений данных.)
  2. Мы должны добавить пользовательский ярлык в объект PropCo\Property\Location, так как он нам понадобится в нашем пользовательском объекте.
  3. Нам нужно будет добавить новый объект настроек, чтобы сохранить, будет ли созданный ключ атрибута выводиться в виде выпадающего списка или в виде списка радио кнопок.
  4. Мы должны будем добавить новый объект значения данных, который хранит как выбранный ID местоположения, так и пользовательский ярлык, если он существует.

Это может показаться большой работой, но система атрибутов concrete5 делает это так, что вы можете легко добавлять эти объекты, и нам даже не придется писать никаких запросов к базе данных для хранения данных.

Измените Контроллер

Во-первых, измените контроллер на наследование с базового контроллера атрибута по умолчанию. Этот:

class Controller extends \Concrete\Attribute\Number\Controller

станет таким

use Concrete\Core\Attribute\Controller as AttributeController; 
class Controller extends AttributeController

Затем добавим индексирование поиска по целому числу в этот контроллер атрибутов:

protected $searchIndexFieldDefinition = array('type' => 'integer', 'options' => array('default' => 0, 'notnull' => false));

(Не беспокойтесь о том, как это работает, для этого есть отдельная документация.)

Измените Объект PropCo\Property\Location

Давайте добавим свойство custom label, getter и setter  через конструктор:

<?php
namespace PropCo\Property;
class Location
{

    protected $propertyLocationID;
    protected $customLabel;

    const LOCATION_CROWN_PLAZA = 1;
    const LOCATION_TOWN_SQUARE = 2;
    const LOCATION_HILL_ROAD = 3;
    const LOCATION_UPTOWN_AVENUE = 4;

    public function __construct($propertyLocationID, $customLabel)
    {
        $this->propertyLocationID = $propertyLocationID;
        $this->customLabel = $customLabel;
    }

    public function getLocationID()
    {
        return $this->propertyLocationID;
    }

    public function getCustomLabel()
    {
        return $this->customLabel;
    }

}

Создание объекта настроек и формы типа

Сначала давайте создадим форму, которая будет отображаться при добавлении атрибута этого типа. Если вы добавите type_form.php и метод type_form() в контроллер, этот метод будет запускаться каждый раз, когда атрибут этого типа добавляется или обновляется, и одновременно будет отображаться форма.

public function type_form()
{

}

Мы начнем с пустого метода. Тогда давайте создадим этот type_form.php шаблон в  application/attributes/property_location/type_form.php:

<fieldset>
    <legend><?=t('Настройки Местоположения')?></legend>

    <div class="form-group">
        <label class="control-label" for="formDisplayMethod">Метод вывода формы</label>
        <select class="form-control" name="formDisplayMethod">
            <option value="select">Выпадающий список</option>
            <option value="radio_list">Список радио кнопок</option>
        </select>
    </div>
 </fieldset>

Вот и все. Группируйте свою настраиваемую форму в наборе полей и поместите ее в нужное Местоположение, и к любому типу, когда вы создаете или обновляете атрибут Местоположение Офиса, ваши настройки будут отображаться внизу:

Настройка атрибута в Панели управления

 

Значение формы 'formDisplayMethod' будет включено в сообщение, когда будет создан или обновлен ключ атрибута. Теперь нам нужно создать объект для хранения этого значения и присоединить его к форме для объекта в контроллере.

Создание Объекта Настроек

Затем давайте создадим класс объектов настроек. Это простой класс PHP, расположенный в нужном месте, с аннотациями на него, чтобы сообщить Doctrine ORM, как настроить объект в базе данных. Вот наш класс:

<?php
namespace Application\Entity\Attribute\Key\Settings;

use Concrete\Core\Entity\Attribute\Key\Settings\Settings;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="atPropertyLocationSettings")
 */
class PropertyLocationSettings extends Settings
{
    /**
     * @ORM\Column(type="string")
     */
    protected $formDisplayMethod = false;

    public function getAttributeTypeHandle()
    {
        return 'property_location';
    }

    /**
     * @return mixed
     */
    public function getFormDisplayMethod()
    {
        return $this->formDisplayMethod;
    }

    /**
     * @param mixed $formDisplayMethod
     */
    public function setFormDisplayMethod($formDisplayMethod)
    {
        $this->formDisplayMethod = $formDisplayMethod;
    }
}

Этот файл должен быть помещен в application/src/Entity/Attribute/Key/Settings/PropertyLocationSettings для автоматической загрузки.

После того, как этот файл находится на своем месте, зайдите в Dashboard> System и Settings> Environment> Объекты базы данных и нажмите кнопку «Обновить объекты». Это приведет к повторному сканированию всех мест сущностей и созданию любых таблиц базы данных для сущностей, которые не существуют (в том числе atPropertyLocationSettings для этого атрибута).

Теперь, когда у нас есть класс настроек, добавим код контроллера, который отвечает за отправку нашей записи формы формы и ее сохранение в объекты настроек.

Сохранение данных

Теперь давайте сохраним опубликованные данные в нашем объекте настроек. Сначала импортируйте класс настроек в наш контроллер:

use Application\Entity\Attribute\Key\Settings\PropertyLocationSettings;

Затем создайте метод, который скажет нашему контроллеру, где найти наш объект настроек:

public function getAttributeKeySettingsClass()
{
    return PropertyLocationSettings::class;
}

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

Затем реализуем метод saveKey($data); этот метод вызывается во время, когда создается или обновляется ключ атрибута. $data - это просто значения POST из формы.

public function saveKey($data)
{
    /**
     * @var $settings PropertyLocationSettings
     */
    $settings = $this->getAttributeKeySettings();

    $formDisplayMethod = 'select';
    if (isset($data['formDisplayMethod']) && $data['formDisplayMethod']) {
        $formDisplayMethod = $data['formDisplayMethod'];
    }

    $settings->setFormDisplayMethod($formDisplayMethod);

    return $settings;
}

Controller::getAttributeKeySettings либо создает новый экземпляр объекта настроек, либо извлекает объект настроек текущего атрибута. Затем мы ищем наше значение formDisplayMethod в POST, и вызовите установщик (setter) для этого аспекта объекта Settings. Затем верните объект настроек $settings в конце метода.

Вот и всё! Система атрибутов позаботится о сохранении объекта и обеспечении его доступности везде.

Изменение формы типа для использования настроек

Теперь, когда мы правильно сохраним настройки, давайте изменим нашу форму типа, чтобы при редактировании атрибутов этого типа были выбраны правильные настройки. type_form() станет такой:

public function type_form()
{
    $settings = $this->getAttributeKeySettings();
    /**
     * @var $settings PropertyLocationSettings
     */
    $this->set('formDisplayMethod', $settings->getFormDisplayMethod());
}

А type_form.php станет такой:

<fieldset>
    <legend><?= t('Настройки местоположения офиса') ?></legend>

    <div class="form-group">
        <label class="control-label" for="formDisplayMethod">Метод вывода формы</label>
        <select class="form-control" name="formDisplayMethod">
            <option value="select"
                    <?php if (isset($formDisplayMethod) && $formDisplayMethod == 'select') { ?>selected<?php } ?>>Select
                Выпадающий список
            </option>
            <option value="radio_list"
                    <?php if (isset($formDisplayMethod) && $formDisplayMethod == 'radio_list') { ?>selected<?php } ?>>
                Список радио кнопок
            </option>
        </select>
    </div>

</fieldset>

Простой и читаемый код!

Изменение формы для использования настроек

Наконец, настало время использовать этот параметр, чтобы повлиять на то, как формируется вывод. Измените form() так чтобы использовалась настройка:

public function form()
{
    if (is_object($this->attributeValue)) {
        $number = $this->attributeValue->getValueObject();
        if (is_object($number)) {
            $this->set('propertyLocationID', $number->getValue());
        }
    }
    $settings = $this->getAttributeKeySettings();
    /**
     * @var $settings PropertyLocationSettings
     */
    $this->set('formDisplayMethod', $settings->getFormDisplayMethod());
}

и давайте изменим form.php для перехода к использованию списка радиокнопок, если настройки поддерживают его:

<?php if (isset($formDisplayMethod) && $formDisplayMethod == 'radio_list') { ?>

    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 1) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('value')?>" value="1"> Crown Plaza</label>
    </div>
    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 2) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('value')?>" value="2"> Town Square</label>
    </div>
    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 3) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('value')?>" value="3"> Hill Road</label>
    </div>
    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 4) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('value')?>" value="4"> Uptown Avenue</label>
    </div>

<?php } else { ?>

    <select class="form-control" name="<?=$view->field('value')?>">
        <option value="">Select a Location</option>
        <option value="1" <?php if (isset($propertyLocationID) && $propertyLocationID == 1) { ?>selected<?php }  ?>>Crown Plaza</option>
        <option value="2" <?php if (isset($propertyLocationID) && $propertyLocationID == 2) { ?>selected<?php }  ?>>Town Square</option>
        <option value="3" <?php if (isset($propertyLocationID) && $propertyLocationID == 3) { ?>selected<?php }  ?>>Hill Road</option>
        <option value="4" <?php if (isset($propertyLocationID) && $propertyLocationID == 4) { ?>selected<?php }  ?>>Uptown Avenue</option>
    </select>

<?php } ?>

Вот и все. Мы проверяем настройки и изменяем их на основе форм-фактора.

Создание Объектов Значений Данных и Формы Пользовательского Атрибута

Создание объекта данных

Чтобы начать работу с объектом пользовательских данных, нам нужно создать объект данных так же, как мы создали объект настроек. Мы собираемся сохранить в нем наш ID Местоположения объекта (в целочисленном поле), а также пользовательский ярлык. Этот новый объект значений данных не будет иметь отношения к объекту Число, так как добавление этой функции эффективно делает их совершенно не связанными.

Сначала создайте объект сущности (entity) в

 application/src/Entity/Attribute/Value/Value/PropertyLocationValue.php.

<?php
namespace Application\Entity\Attribute\Value\Value;

use Concrete\Core\Entity\Attribute\Value\Value\AbstractValue;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="atPropertyLocation")
 */
class PropertyLocationValue extends AbstractValue
{
    /**
     * @ORM\Column(type="integer", nullable=true)
     */
    protected $propertyLocationID = 0;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    protected $customLabel = '';

    /**
     * @return mixed
     */
    public function getPropertyLocationID()
    {
        return $this->propertyLocationID;
    }

    /**
     * @param mixed $propertyLocationID
     */
    public function setPropertyLocationID($propertyLocationID)
    {
        $this->propertyLocationID = $propertyLocationID;
    }

    /**
     * @return mixed
     */
    public function getCustomLabel()
    {
        return $this->customLabel;
    }

    /**
     * @param mixed $customLabel
     */
    public function setCustomLabel($customLabel)
    {
        $this->customLabel = $customLabel;
    }


}

заботясь о наследовании объекта Concrete\Core\Entity\Attribute\Value\Value\AbstractValue (обратите внимание: будьте осторожны, чтобы не наследовать от неправильного класса AbstractValue, так как в пространстве имен есть очень похожий на этот. Убедитесь, что он содержит Value\Value дважды до AbstractValue.

Затем обновите объекты на странице «Панель управления> Система» и «Настройки > Окружающая среда > Объекты базы данных», как и прежде, чтобы автоматически создать таблицу базы данных значений.

Измените Контроллер для Передачи Новых Данных

Теперь мы больше не используем $value(который был частью

 Concrete\Core\Entity\Attribute\Value\Value\NumberValue), мы используем $propertyLocationID и $customLabel. Поэтому мы должны убедиться, что наша форма form() берет эти данные из нашего объекта значений данных и передает его в шаблон.

Наш метод формы выглядел так:

public function form()
{
    if (is_object($this->attributeValue)) {
        $number = $this->attributeValue->getValueObject();
        if (is_object($number)) {
            $this->set('propertyLocationID', $number->getValue());
        }
    }
    $settings = $this->getAttributeKeySettings();
    /**
     * @var $settings PropertyLocationSettings
     */
    $this->set('formDisplayMethod', $settings->getFormDisplayMethod());
}

Мы передаем $propertyLocationID, но это больше не будет работать, потому что getValue() на самом деле не возвращает число, так как мы больше не используем объект значений числовых данных. Поскольку мы не переопределили это в нашем методе Application\Entity\Attribute\Value\Value\PropertyLocationValue  (подробнее об этом позже), он просто возвращает объект PropertyLocationValue. Поэтому давайте изменим эти строки, чтобы получить соответствующие релевантные данные и установить их в шаблон.

public function form()
{
    if (is_object($this->attributeValue)) {
        $propertyLocationValue = $this->attributeValue->getValueObject();
        if (is_object($propertyLocationValue)) {
            $this->set('propertyLocationID', $propertyLocationValue->getPropertyLocationID());
            $this->set('customLabel', $propertyLocationValue->getCustomLabel());
        }
    }
    $settings = $this->getAttributeKeySettings();
    /**
     * @var $settings PropertyLocationSettings
     */
    $this->set('formDisplayMethod', $settings->getFormDisplayMethod());
}

Добавьте поле пользовательского ярлыка в форму

Теперь мы берем нашу новую измененную форму и добавляем наше настраиваемое поле вниз. Мы будем использовать это поле для отображения имени Местоположения, если оно заполнено для атрибута.

Кроме того, обязательно измените вызов $view->field('value') на $view->field('propertyLocationID'). Поскольку мы не используем контроллер Числа, чтобы справиться с этим, нам все равно придется создавать нашу собственную процедуру сохранения значений атрибутов, а «propertyLocationID» имеет больше смысла в качестве имени переменной для этой цели, поскольку она соответствует имени нашего свойства объекта значений данных. Кроме того, убедитесь, что данные, которые мы установили через метод form() выводятся в форме самих элементов управления. Когда мы позаботимся обо всем этом, наш шаблон формы будет выглядит следующим образом:

<?php if (isset($formDisplayMethod) && $formDisplayMethod == 'radio_list') { ?>

    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 1) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('propertyLocationID')?>" value="1"> Crown Plaza</label>
    </div>
    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 2) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('propertyLocationID')?>" value="2"> Town Square</label>
    </div>
    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 3) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('propertyLocationID')?>" value="3"> Hill Road</label>
    </div>
    <div class="radio">
        <label><input <?php if (isset($propertyLocationID) && $propertyLocationID == 4) { ?>checked<?php }  ?> type="radio" name="<?=$view->field('propertyLocationID')?>" value="4"> Uptown Avenue</label>
    </div>

<?php } else { ?>

    <select class="form-control" name="<?=$view->field('propertyLocationID')?>">
        <option value="">Select a Location</option>
        <option value="1" <?php if (isset($propertyLocationID) && $propertyLocationID == 1) { ?>selected<?php }  ?>>Crown Plaza</option>
        <option value="2" <?php if (isset($propertyLocationID) && $propertyLocationID == 2) { ?>selected<?php }  ?>>Town Square</option>
        <option value="3" <?php if (isset($propertyLocationID) && $propertyLocationID == 3) { ?>selected<?php }  ?>>Hill Road</option>
        <option value="4" <?php if (isset($propertyLocationID) && $propertyLocationID == 4) { ?>selected<?php }  ?>>Uptown Avenue</option>
    </select>

<?php } ?>

<div class="form-inline">
    <label><?=t('Custom Label')?></label>
    <input class="form-control" type="text" value="<?=$customLabel?>" name="<?=$view->field('customLabel')?>">
</div>

Обновите индекс поиска

Затем давайте обновим метод getSearchIndexValue, который есть в нашем контролере. Поскольку у нас есть объект значений пользовательских данных, нам нужно будет его использовать, чтобы вернуть идентификатор, а не объект данных с числовыми значениями метода getValue.

public function getSearchIndexValue()
{
    if ($this->attributeValue) {
        /**
         * @var $value PropertyLocationValue
         */
        $value = $this->attributeValue->getValueObject();
        return $value->getPropertyLocationID();
    }
}

Позаботимся о Сохранении Данных

Наконец, давайте рассмотрим сохранение данных. Мы должны реализовать три метода для сохранения данных атрибутов:

createAttributeValueFromRequest()
createAttributeValue($mixed)
getAttributeValueClass()

createAttributeValueFromRequest запускается, когда атрибут сохраняется через стандартные пользовательские интерфейсы, такие как диалог атрибутов Карты сайта, панель атрибутов, слайды атрибутов пользователей и т. д. ... createAttributeValue запускается всякий раз, когда $object-> setAttribute ('my_property_location_attribute', $value) запускается через код, с любым вашим дополнительным кодом.

Оба метода должны возвращать экземпляр объекта

 Application\Entity\Attribute\Value\Value\PropertyLocationValue, с соответствующими данными отправленным через POST запрос, и записанными в объект. Вот как можно было реализовать эти методы.

Наконец, метод getAttributeValueClassиспользуется для определения имени объекта, используемого для хранения значения атрибута. Вы можете повторно использовать его во всех контроллерах, но он использовался в методе getAttributeValueObject() в базовом контроллере для извлечения соответствующего объекта значения данных атрибута.

Сначала импортируйте класс Application\Entity\Attribute\Value\Value\PropertyLocationValue наверх пространства имен контроллера:

use Application\Entity\Attribute\Value\Value\PropertyLocationValue;

Затем добавьте этот код:

/**
 * @param $location Location
 * @return PropertyLocationValue
 */
public function createAttributeValue($location)
{
    $value = new PropertyLocationValue();
    $value->setPropertyLocationID($location->getLocationID());
    $value->setCustomLabel($location->getCustomLabel());
    return $value;
}

public function createAttributeValueFromRequest()
{
    $value = new PropertyLocationValue();
    $data = $this->post();
    $value->setPropertyLocationID($data['propertyLocationID']);
    $value->setCustomLabel($data['customLabel']);
    return $value;
}

public function getAttributeValueClass()
{
    return PropertyLocationValue::class;
}

Первый метод создает значение атрибута, основанное на переданном объекте Местоположение, так что в будущем этот код будет работать

$location = new Location(1, 'Мое местоположение');
$page->setAttribute('location', $location);

Второй создает значение ($value) из запроса. И третий говорит системе атрибутов, какой класс сущностей (entity) используется для извлечения параметров объекта значения данных из диспетчера сущностей (entity maneger) Doctrine ORM.

Заметка

В createAttributeValueFromRequest$this->post() является единственным надежным способом получения значений формы атрибута из запроса. Это потому, что значения формы атрибута содержат в них дополнительные данные, чтобы гарантировать, что несколько форм ключей атрибутов могут сосуществовать в одной форме.

Изменим Метод getValue().

Наконец, мы должны изменить метод  getValue() нашего контроллера, потому что он работал с числовым значением, и он не знал о нашем пользовательском ярлыке. Если мы изменим его на этот код:

public function getValue()
{
    /**
     * @var $value PropertyLocationValue
     */
    $value = $this->attributeValue->getValueObject();
    if ($value) {
        return new Location($value->getPropertyLocationID(), $value->getCustomLabel());
    }
}

он правильно выведет объект PropCo\Property\Location.

Заключение

Вот и все. Теперь мы добавили пользовательские настройки и пользовательский объект данных к нашему атрибуту. Вызов   getAttribute() на странице, пользователе или файле с нашим атрибутом Местоположение Офиса возвращает один из наших пользовательских объектов, и мы сохраняем специальный ярлык вместе с идентификатором ID. Всё без написания функций для базы данных.

Это не обязательно самая чистая модель данных - вы могли бы сделать некоторую работу, чтобы избежать дублирования усилий между объектом  PropCo\Property\Location и объектом Application\Entity\Attribute\Value\Value\PropertyLocationValue, но, надеюсь, это приведет к повороту колеса и вы начинете создавать собственные типы атрибутов с пользовательскими настройками и объектами значений.