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

Расширение базового типа атрибута

Вот довольно понятный, распространенный случай использования: вы разработчик, создающий веб-сайт для компании по управлению имуществом, и у этой компании есть четыре офиса, в которых они работают:

  • Crown Plaza
  • Town Square
  • Hill Road
  • Uptown Avenue

Они не будут меняться, но вам нужно будет использовать это, чтобы связать пользователей и, возможно, страницы с этими местами.

Объект Местоположения

На нашем веб-сайте управления собственностью мы создали пользовательский объект для этих местоположений, найденный в PropCo\Property\Location. (Мы используем автозагрузчики пользовательских приложений для сопоставления application/src/PropCo/Property/Location.php с этим настраиваемым объектом.). PropCo\Property\Locationэто объект, который мы использовали для представления этих четырех местоположений. Самое главное, это тот объект, который мы хотим передать, когда мы работаем с атрибутами. Мы не хотим напрямую работать с числами, которые соответствуют нашим идентификаторам местоположения за кулисами - мы заботимся о самом объекте.

<?php
namespace PropCo\Property;
class Location 
{

    protected $propertyLocationID;

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

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

Из нашего пользовательского кода видно, что Crown Plaza имеет числовой идентификатор 1, Town Square 2 и т. д. Итак, если мы хотим использовать пользовательские объекты в нашем атрибуте, давайте создадим совершенно новый тип настраиваемого атрибута, называемый «property_location», но его основанием будет номер пользовательского атрибута. Зачем? Поскольку мы собираемся хранить номера, соответствующие этим местоположениям, нам не нужно писать почти такой же код. Все объекты настроек и значений будут работать так же, как и сегодня, и мы можем сосредоточиться на создании настраиваемого контроллера, который будет переводить эти сохраненные числа в наши объекты местоположения.

Создайте Контроллер

Сначала создайте файл controller.php в application/attributes/property_location/controller.php. Как и в случае с настраиваемыми типами блоков, application/attributes в каталоге, там где находятся типы пользовательских атрибутов. Этот файл может быть в основном пустым в данный момент, за исключением соответствующего пространства имен и имени класса, которое мы хотим сделать подклассом Concrete\Attribute\Number\Controller класса, поскольку мы используем поля данных Число, чтобы сохранить значение этого атрибута.

<?php
namespace Application\Attribute\PropertyLocation;
class Controller extends \Concrete\Attribute\Number\Controller
{

}

Добавьте Тип Атрибута

На странице /dashboard/system/attributes/types сайта,найдите список настраиваемых типов атрибутов в нижней части экрана и нажмите «Установить». (Примечание: если вы получили ошибку на этом экране или не видите какой-либо атрибут, отключите кеширование переопределения, перейдя в / dashboard / system / optimization / cache и найдите раздел «Кэш и производительность». Более подробную информацию можно найти здесь..

custom-attribute-type.png

Не забудьте назначить типу атрибута одну или несколько категорий атрибутов на этом же экране, иначе вы не сможете добавить тип атрибута ни к одной странице, файлу или пользователю.

Добавьте Ключ Атрибута

Теперь нам нужно создать экземпляр типа атрибута, который мы можем использовать для привязки нашего объекта местоположения к странице, файлу или пользователю. Давайте создадим его для страниц. Создайте новый ключ атрибута property_location с дескриптором 'property_location' в атрибутах страницы:

Сначала выберите местоположение свойства как тип нового атрибута, а затем создайте ключ:

Атрибут Место офиса

Итак у нас есть ключ атрибута, который мы можем использовать на страницах. Теперь пришло время реализовать некоторые методы контроллера и создать некоторые шаблоны атрибутов. Если мы не реализуем какие-либо методы контроллера или какие-либо пользовательские формы, наш атрибут будет выглядеть и функционировать точно так же, как атрибут Число (number), так как это класс, который он расширяет.

Иконка

Проще всего начать с функциональности, которая определяет значок, используемый нашим атрибутом. Это метод getIconFormatter. Используйте этот метод, который возвращает объект Concrete\Core\Attribute\IconFormatterInterface. Наиболее распространенным и используемым в ядре concrete5 в настоящее время ядром являетсяConcrete\Core\Attribute\FontAwesomeIconFormatter, который просто принимает имя значка значка шрифта Font Awesome в качестве аргумента единственного конструктора.

Сначала мы выясним, какой значок использовать. Поскольку это место здания, давайте используем для этого атрибута значёк дома. Если вы проверите эту ссылку, вы увидите, что значок дома в Font Awesome называется «home», так что мы будем использовать его.

Затем импортируйте определение класса в контроллер, добавив его в начало класса:

use Concrete\Core\Attribute\FontAwesomeIconFormatter;

Затем реализуем метод в контроллере:

public function getIconFormatter()
{
    return new FontAwesomeIconFormatter('home');
}

Вот и всё! Всюду, где атрибуты этого типа используются или перечислены, будет использоваться этот значок.

Форма

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

При добавлении пользовательского атрибута Место Офиса на страницу мы хотим, чтобы он выглядел как меню выбора со списком четырёх местоположений. Итак, давайте реализуем пользовательский метод form() в контроллере, и создадим пользовательский шаблон form.php в директории application/attributes/property_location/. (Примечание: при визуализации формы атрибута, выводится сначала метод контроллера form(), если он существует, а затем включается шаблон form.php, если он тоже создан.)

public function form()
{

}

Наш метод form() пока пустой. Теперь давайте создадим шаблон form.php . Он будет содержать меню выбора, соответствующее нашему примеру. В application/attributes/property_location/form.php, мы добавим следующий код:

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

Здесь пара замечаний:

Во-первых, вы заметите имя, которое мы даем нашему единственному элементу управления: <?=$view->field('value')?>. $view является переменной, соответствующей текущему объекту Concrete\Core\Attribute\View. Он автоматически вставляется и доступен в любом шаблоне атрибута. View::field() это метод, который принимает читаемое имя параметра и переводит его в имя управления формой, которое гарантировано будет уникальным на странице (поскольку, в конце концов, несколько атрибутов одного и того же типа могут использоваться на одной странице. Что делать, если вы создали два атрибута местоположения офиса и поместили их на одну страницу? Вам нужно будет убедиться, что система может различать местоположение атрибута A и атрибута B.)

Почему мы выбрали «value» в качестве аргумента для поля в нашем методе? Потому что «value» - это имя поля, используемого атрибутом Число. Поскольку мы расширяем эго, если мы назовем наш элемент формы таким же образом, мы можем повторно использовать некоторые методы обработки формы, которые предоставляет атрибут Число.

Затем вы заметите, что мы жестко кодируем наши идентификаторы в списке. Это предназначено для этой документации. В идеале вы замените 1, 2, 3 и 4 в этом примере PropCo\Property\Location::LOCATION_CROWN_PLAZA,PropCo\Property\Location::LOCATION_TOWN_SQUARE, и т.д...

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

меню выбора из списка

 

И если мы сохраним атрибут, идентификатор выбранного параметра ID сохраняется за кулисами.

Форма Редактирования

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

public function form()
{
    if (is_object($this->attributeValue)) {
        $value = $this->attributeValue->getValue();
        $this->set('propertyLocationID', $value);
    }
}

И изменим код нашего шаблона формы, чтобы он выглядел так:

<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>

Давайте рассмотрим этот код. Во-первых, в методе form () мы проверяем, определено ли свойство attributeValue в контроллере. Важно помнить, что метод form() запускается, когда атрибуты добавляются и редактируются, и только в последнем случае, если свойство attributeValue, будет существовать. Вот почему мы выполняем проверку. Controller :: $ attributeValue всегда будет объектом Concrete\Core\Attribute\AttributeValueInterface, но пока единственной реально выполнимой реализацией этого является Concrete\Core\Entity\Attribute\Value\AbstractValue объект и классы (например, PageValue, FileValue, UserValue), которые от него расширяются.

Этот интерфейс определяет метод с именем getValue(), который при запуске отвечает за возврат значения атрибута в его исходном формате. Что это значит? Исходный формат - это ответ, с которым атрибут работает наиболее непосредственно. Это означает, что атрибут Image / File возвращает экземпляр объекта Concrete\Core\Entity\File\File, Атрибут типа Число возвращает число с десятичный знаками, атрибут Текстовое поле возвращает текст, а атрибут Дата-Время возвращает объект DateTime. Другие методы возвращают свои значения в разных форматах, но getValue () должен возвращать формат, наиболее соответствующий этому типу атрибута. Это потому, что getAttribute() метод, найденный на страницах, у пользователей и в файлах (и любые другие пользовательские категории атрибутов) извлекает это значение в первую очередь.

Поскольку мы расширяем контроллер атрибута типа Число, метод getValue() вернет десятичное число. Это означает, что мы можем просто передать его прямо в наш шаблон формы. Мы передаем его как имя переменной $propertyLocationID, чтобы сделать наш шаблон понятным. Затем мы просто проверяем, установлен ли параметр $propertyLocationID и равен определенному идентификатору местоположения из нашего шаблона, и это позволяет нам выбрать параметр свойства.

Изменяем метод getValue()

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

Однако, скажем, я хочу получить более программную привлекательность. Например, если я автоматически вывожу местоположение офиса на странице

$propertyLocation = $page->getAttribute('property_location'); // Вернет 1, 2, 3 или 4

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

$propertyLocationID = $page->getAttribute('property_location');
$location = new \PropCo\Property\Location($propertyLocationID);

Однако не будет ли больше смысла, чтобы наш атрибут Место Офиса фактически возвращал объект Место Офиса, когда вы вызываете $page->getAttribute()? Это пример типа атрибута, который не возвращает объект в его формате родного значения. Мы должны это исправить. К счастью, все это можно сделать в нашем настраиваемом контроллере атрибутов, не беспокоясь о том, как изменить за кулисами объекты значений данных атрибутов в виде числа.

Сначала импортируйте объект Место Офиса в класс:

use PropCo\Property\Location;

И добавьте этот метод в класс Application\Attribute\PropertyLocation\Controller :

public function getValue()
{
    $value = $this->attributeValue->getValueObject();
    if ($value) {
        return new Location($value->getValue());
    }
}

Давайте тоже пройдем через это. В getValue() мы получаем указанное выше абстрактное значение, и по этому значению мы вызываем getValueObject, который также является частью AttributeValueInterface. Это возвращает объект непосредственный объект данных, используемый нашим атрибутом — в этом случае его Concrete\Core\Entity\Attribute\Value\Value\NumberValue, поскольку мы привязываем наш атрибут к атрибуту Число. Затем, если это значение существует, мы создаем наше местоположение, используя метод NumberValue :: getValue () (который возвращает десятичное число).

Важно

Этот код немного отличается от кода, который мы создали раньше. В методе form () выше мы использовали $this-> attributeValue->getValue(), а здесь мы используем $this->attributeValue->getValueObject() – почему? Разве мы не можем просто использовать getValue()чтобы перейти к десятичному представлению значения атрибута Число? Нет! Причина этого в том, что getValue() объект значения атрибута сначала проверяет, имеет ли тип атрибута собственный пользовательский метод getValue() в контроллере, и если да, то он использует этот метод для извлечения значения. Это позволяет нам делать именно то, что мы делаем здесь. Поэтому, если бы мы использовали $this->attributeValue->getValue() и указали в контроллере метод getValue(), как мы делаем это здесь, мы получили бы рекурсивный цикл. Так что не делайте этого. Вместо этого получите базовый объект значений данных и используйте эго.

Обновите Метод form()

Если вы внимательно читали, вы поймете, что это означает, что нам нужно также изменить  базовый код формы. Наш существующий метод form() необходимо изменить в соответствии с новым требованием. Измененив его на этот получим:

public function form()
{
    if (is_object($this->attributeValue)) {
        $number = $this->attributeValue->getValueObject();
        if (is_object($number)) {
            $this->set('propertyLocationID', $number->getValue());
        }
    }
}

Добавьте getSearchIndexValue()

Теперь, когда мы обновили getValue() так, чтобы он возвращал объект, мы должны добавить в контроллер специальный метод под названием getSearchIndexValue()getSearchIndexValue() возвращает данные в формате, который ожидает индекс поиска для данного типа атрибута. Это описано в другом месте, но важно отметить, что контроллер атрибута Число использует getValue() для возврата десятичного числа для индексатора поиска. Поскольку мы обновили getValue() , который возвращает объект, то если мы не предоставим новый метод getSearchIndexValue() , который будет возвращать число, то мы будем пытаться вставить объект в таблицу базы данных индекса поиска, что приведет к ошибкам. Итак, добавим это в контроллер:

public function getSearchIndexValue()
{
    if ($this->attributeValue) {
        $value = $this->attributeValue->getValueObject();
        return $value->getValue();
    }
}

Он следует той же схеме, что и другие методы, и должен быть достаточно прост для понимания.

Заключение

Вот и всё! Мы создали пользовательский тип атрибута с очень маленьким кодом, основанным на существующем типе ядра concrete5. В ядре так много настраиваемых типов атрибутов, что этого подхода должно быть достаточно для разных типов атрибутов.

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