Я использовал Zend Framework 2 в недавнем проекте, чтобы оценить его возможности, и один из первых блокировщиков, которые у меня были, пытался понять, как, черт возьми, использовать элемент формы Select. На момент написания этой статьи нет хорошей документации о том, как ее использовать, поэтому вместо этого я расскажу, как ее использовать в этом посте.
Менеджер альбомов, Пересмотрено
Для этой демонстрации я буду использовать приложение Zend-Skeleton, над которым я первоначально работал, следуя учебному пособию по Zend Framework 2. Я остановился на своем предыдущем посте, где мы сохранили наш код с помощью аннотаций форм. Вы можете следить за мной в этом посте, проверив мой проект zend-skeleton на github и отметив тег «select-begin».
Начнем с того, что у нас есть аннотированный объект модели Album с заголовком и исполнителем:
<?php
// module/Album/src/Album/Model/Album.php:
namespace Album\Model;
use Zend\Form\Annotation as Form;
class Album{
/**
* @Form\Required(false)
* @Form\Attributes({"type":"hidden"})
*/
public $id;
/**
* @Form\Required(true)
* @Form\Attributes({"type":"text"})
* @Form\Options({"label":"Artist"})
* @Form\Filter({"name":"StringTrim"})
* @Form\Validator({"name":"StringLength", "options":{"min":1, "max":100}})
*/
public $artist;
/**
* @Form\Required(true)
* @Form\Attributes({"type":"text"})
* @Form\Options({"label":"Title"})
* @Form\Filter({"name":"StringTrim"})
* @Form\Filter({"name":"StripTags"})
*/
public $title;
Итак, мы написали наше приложение, и наш клиент доволен, однако он хотел бы иметь возможность пометить альбомы жанром. При добавлении или редактировании нового альбома должен быть раскрывающийся жанр с такими параметрами, как Rap, Jazz, Alternative, Rock и Country.
Оказывается, это очень просто, нам просто нужно добавить новое поле в нашу модель и элемент формы Select:
/**
* @Form\Type("Zend\Form\Element\Select")
* @Form\Options({"label":"Genre", "value_options":{
* "Rap":"Rap",
* "Jazz":"Jazz",
* "Alternative":"Alternative",
* "Rock":"Rock"
* }
* })
*/
public $genre;
Теперь начинается стандартная часть, добавляя ее к вашим представлениям и AlbumTable (скоро я опубликую блог о замене TableGateways). Я оставлю это как упражнение для читателя. Смотрите тег select-annotated, чтобы увидеть законченную работу здесь. Вам также необходимо добавить колонку жанра в таблицу альбомов самостоятельно (бу … нет миграций).
Получение значений из внешнего источника
Обычно мы получаем жанры из таблицы базы данных или из какого-либо другого внешнего источника, не закодированного в виде списка значений в аннотации. Чтобы понять, как сделать это лучше, давайте сначала извлечем совокупность значений из аннотации формы и сделаем это в другом месте. Очевидное место для манипулирования формой после факта, очевидно, не в контроллере, поэтому давайте выделим создание формы во что-то, что может ее инкапсулировать. Из-за отсутствия каких-либо соглашений наше руководство от фреймворка здесь, давайте просто назовем его AlbumFormBuilder и станем сильными.
Сначала мы создаем новый класс с именем FormBuilder в модуле / Album / src / Album / Form / FormBuilder. Ранее (как и во время последнего урока) мы применили метод extract для извлечения общего метода из addAction и editAction для обработки логики построения формы.
private function form(){
$builder = new AnnotationBuilder();
$form = $builder->createForm(new Album());
$form->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Add',
'id' => 'submitbutton',
),
));
return $form;
}
Теперь нам просто нужно переместить этот метод в наш новый класс FormBuilder и назвать его чем-то значимым, например, «newForm».
<?php
namespace Album\Form;
use Zend\Form\Annotation\AnnotationBuilder;
use Album\Model\Album;
class FormBuilder {
public function newForm(){
$builder = new AnnotationBuilder();
$form = $builder->createForm(new Album());
$form->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Add',
'id' => 'submitbutton',
),
));
return $form;
}
}
Теперь мы можем просто поместить этот объект в существующий метод в AlbumController и увидеть, что он все еще работает (и я обычно делаю это во время рефакторинга). Но чтобы действительно использовать мощь, мы должны создать этого нового строителя с помощью заводского механизма ZF2. Итак, мы открываем module / Album / Module.php и следующее в разделе фабрики:
public function getServiceConfig(){
return array(
'factories' => array(
'Album\Model\AlbumTable' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new AlbumTable($dbAdapter);
return $table;
},
// add this
'Album\Form\FormBuilder' => function($sm){
return new \Album\Form\FormBuilder();
}
),
);
}
И измените AlbumController, чтобы использовать его через сервисный локатор.
private function form(){
$sm = $this->getServiceLocator();
return $sm->get('Album\Form\FormBuilder')->newForm();
}
Теперь, когда логика лучше инкапсулирована вне контроллера, давайте просто установим параметры значений непосредственно в классе компоновщика.
...
$form = $builder->createForm(new Album());
$form->get('genre')->setValueOptions(
array(
"Alternative",
"Country",
"Jazz",
"Rap",
"Rock"
)
);
...
Теперь, если мы сохраним это и проверим приложение, мы заметим, что наше новое дополнение полностью переопределяет то, что было в аннотации. Так как они нам больше не нужны, мы можем удалить атрибут value_options из нашей аннотации формы. Идите вперед и отредактируйте или добавьте новый альбом, выберите жанр и сохраните его. Заметьте что-нибудь? Если вы используете массив строковых литералов, все значения будут индексами элемента, а фактические значения будут метками.
У нас есть два варианта: либо указать ключи, которые представляют значения параметров, либо явно определить метку и значение (да, это немного странно). Смотрите примеры ниже для быстрого ознакомления.
/**
* Generates the following:
* <option value="alt">Alternative</option>
* <option value="country">Country</option>
* <option value="jazz">Jazz</option>
* <option value="rap">Rap</option>
* <option value="rock">Rock</option>
*/
array(
'alt' => 'Alternative',
'country' => 'Country',
'jazz' => 'Jazz',
'rap' => 'Rap',
'rock' => 'Rock'
)
Это на самом деле не дало мне того, чего я изначально ожидал … Я ожидал увидеть ключи массива в виде меток, а значения в качестве значений. Однако использование массива с ключами будет в основном соответствовать тому же левому и правому расположению, которое будут иметь фактические элементы выбора элементов. Если вы хотите быть более явным, вы также можете сделать следующее:
array(
array('value' => 'alt', 'label' => 'Alternative'),
array('value' => 'country', 'label' => 'Country'),
array('value' => 'jazz', 'label' => 'Jazz'),
array('value' => 'rock', 'label' => 'Rock'),
)
Последний шаг: извлечение ценностей
Была причина, по которой я использовал управление зависимостями ZF2 для извлечения нового экземпляра FormBuilder, и это заключалось в том, чтобы легко извлекать варианты значений для предоставления чего-то другого, возможно, другого шлюза таблиц. Итак, давайте продолжим и переместим этот массив, который будет возвращен новым GenreTable.
<?php
// modules/Album/src/Album/Model/GenreTable.php
namespace Album\Model;
class GenreTable {
public function fetchAllAsArray(){
return array(
'alt' => 'Alternative',
'country' => 'Country',
'jazz' => 'Jazz',
'rap' => 'Rap',
'rock' => 'Rock'
);
}
}
Теперь мы модифицируем FormBuilder, чтобы взять GenreTable в качестве аргумента конструктора и использовать его для заполнения параметров значения.
class FormBuilder {
private $genres;
public function __construct($genres) {
$this->genres = $genres;
}
public function newForm(){
$builder = new AnnotationBuilder();
$form = $builder->createForm(new Album());
$form->get('genre')->setValueOptions(
$this->genres->fetchAllAsArray()
);
...
}
}
Наконец, мы обновляем module / Album / Module.php, чтобы добавить экземпляр нового GenreTable.
'Album\Model\GenreTable' => function($sm){
return new \Album\Model\GenreTable();
},
'Album\Form\FormBuilder' => function($sm){
return new \Album\Form\FormBuilder(
$sm->get('Album\Model\GenreTable')
);
}
Теперь мы можем изменить объект, введенный в FormBuilder, для извлечения жанров из базы данных, а не использовать массив в памяти, когда мы будем готовы, но я устал от написания TableGateways. В моем следующем посте я сделаю свой код немного DRY’er, разорвав эти шлюзы таблиц в пользу доктрины.
Вы можете увидеть мои законченные работы для этого поста на github .