Когда идет работа с формами часто необходимо делать не совсем стандартные селекты.
В данном посте я рассмотрю несколько примеров таких селектов и так же ObjectSelect от Doctrine.
В общем случае код кастомного селекта выглядит так. Далее пример общего кода и пример первого селекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<?php namespace Model\Form\Element; use Zend\Form\Element\Select; /** * Description of StatusSelect * * @author seyfer */ class StatusSelect extends Select { const NAME = 'status'; public function __construct($options = array()) { parent::__construct(self::NAME, $options); $this->configure($options); } /** * получить селект с контрактами * @param type $options */ private function configure($options = array()) { $optionsConfig = array( 'label' => 'Статус', 'value_options' => $options, 'disable_inarray_validator' => true, "attributes" => array( "value" => 0, ), 'empty_option' => '---', ); $this->setAttribute("id", self::NAME); $this->setOptions($optionsConfig); return $this; } public function configureWithData() { $options = \Model\Enum\Status::getConstants(); $optionsFlipped = array_flip($options); $this->configure($optionsFlipped); return $this; } } |
Все, кроме метода configureWithData() является стандартным кодом.
Сам метод в данном лучае использует перечисление Enum, которое я описывал в посте Эмуляция перечислений Enum в PHP
Само перечисление выглядит так.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php namespace Model\Enum; /** * Description of Status * * @author seyfer */ abstract class Status extends BaseEnum { const STATUS_NEW = 0; const STATUS_IN_PROCESS = 1; const STATUS_OK = 2; const STATUS_ERROR = 3; const STATUS_FOR_UPDATE = 4; } |
Очень просто и понятно. Если предположить, что на весь проект задан стандартный набор статусов и любая модель может использовать эти статусы – очень удобно делать выборку таким селектом по статусам.
В форме это инициируется так.
1 2 3 4 5 6 7 |
protected function configureStatusSelect() { $select = new \Model\Form\Element\StatusSelect(); $this->add($select->configureWithData()); } |
Теперь пример номер 2. В нем весь код, кроме названия селекта и метода конфигурирования идентичен.
В примере номер 2 мы будем заполнять select данными полученными с внешнего api. Даные получаются в json формате, специальная обертка процессит их в массив. Так же в обертке вшит способ отправки. По сути это Proxy. Используется в форме он так же, как и пример 1. В классе селекта следующий код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public function configureWithSeances() { $options = $this->getOptionsWithSeances(); $this->configure($options); return $this; } /** * выгружает контракты и делает их опциями * @return type */ public function getOptionsWithSeances() { $asb = Asb::getCachedInstance(); // $asb = new Asb(); $seances = $asb->getSeances(); $options = []; foreach ($seances as $seance) { $options[$seance->getId()] = $seance->getName(); } return $options; } |
Как видно закоментирован прямой вызов обертки и вызывается некий статический метод getCachedInstance(). Внутри реализован паттерн кеширования object из набора паттернов Zend 2. В качестве хранилища указан APC. Сделано это чтобы не слать запрос при каждом обновлении формы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * получить объект закешированный * по паттерну object * @return Asb */ public static function getCachedInstance($storage = null) { $object = new self; //кешируем ответ $cachedObj = \Zend\Cache\PatternFactory::factory('object', array( 'object' => $object, 'object_key' => __CLASS__, 'storage' => 'Zend\Cache\Storage\Adapter\Apc', // doesn't output anything // so the output don't need to be caught and cached 'cache_output' => false, ) ); return $cachedObj; } |
Третий пример это select который заполняет сам себя из базы данных используя Doctrine.
Можно реализовать такой select вручную с помощью инъекции EntityManager. Приведу полный код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
<?php namespace Admin\Form\Element; use Zend\Form\Element\Select; /** * Description of RoleSelect * * @author seyfer */ class RoleSelect extends Select { /** * @var \Doctrine\ORM\EntityManager */ protected $em; const NAME = "role"; public function __construct($options = array()) { parent::__construct(self::NAME, $options); $this->configure($options); } function getEm() { return $this->em; } function setEm(\Doctrine\ORM\EntityManager $em) { $this->em = $em; } /** * задает селект с выбором типа * @return RoleSelect */ private function configure($options = array()) { $optionsConfig = array( 'label' => 'Role: ', 'value_options' => $options, 'disable_inarray_validator' => true, "attributes" => array( "value" => 0, ) ); $this->setAttribute("id", self::NAME); $this->setEmptyOption("---"); $this->setOptions($optionsConfig); return $this; } /** * загрузить в селект дерево типов * @return RoleSelect */ public function configureWithRoles() { $options = $this->getOptionsWithRoles(); $this->configure($options); return $this; } /** * выгружает все дерево рекурсивно в опции * @return string */ public function getOptionsWithRoles() { if (!$this->em) { throw new \Exception(__METHOD__ . " set EntityManager first"); } $roles = $this->em ->getRepository(\Auth\Entity\Role::class) ->findAll(); foreach ($roles as $role) { $options[$role->getId()] = $role->getName(); } return $options; } } |
И в форме вызывается так-же. Кстати тут тоже можно добавить кеширование.
1 2 3 4 5 6 7 8 |
private function configureRoleSelect($options = array()) { $countSelect = new \Admin\Form\Element\RoleSelect(); $countSelect->setEM($this->em); $this->add($countSelect->configureWithRoles()); } |
Как видно из кода минус в том, что в форму тоже надо делать инъекцию EntityManager и в данном случае лучше делать это через конструктор формы.
Если же использовать реализацию от Doctrine – ObjectSeleсt, то тут отпадает необходимость в своем классе Select, но инъекцию EntityManager в форму делать все равно надо.
Вот тот же RoleSelect только через Doctrine\ObjectSelect
1 2 3 4 5 6 7 8 9 10 11 12 |
$role = new \DoctrineModule\Form\Element\ObjectSelect('role', [ 'object_manager' => $this->getEm(), 'target_class' => \Auth\Entity\Role::class, 'property' => 'name', 'label' => 'Role', 'empty_option' => '...' ]); $this->add($role); // $this->configureRoleSelect(); |
Комментарий оставлен, чтобы было понятно. Так же Doctrine позволяет использовать свой репозиторий для заданного в селекте Entity. Для этого в конфиге надо указать специальные параметры и имя метода из репозитория.
1 2 3 4 5 6 |
'is_method' => true, 'find_method' => array( 'name' => 'getActiveRoles', ), |
Подробнее в этом посте.
Создание своих Element\Select подобным образом может значительно повысить повторное использование кода и облегчить поддержку.
Так же можно комбинировать свои селекты со своим рендерингом, как это описано в этом посте – Свой Form Element со своим Render в Zend Framework 2