Я использовал 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 .