Рендеринг пользовательских элементов формы с помощью FormElement ViewHelper в Zend Framework 2

Я создал новый класс элемента формы для специальной сложной цели (поле ввода текста с дополнительной кнопкой для открытия всплывающего окна «мастер поиска»).

Чтобы правильно отобразить этот элемент, я также создал помощник вида формы. Все работает и пока нормально.

Однако, если я попытаюсь отобразить форму с помощью помощника представления FormCollection, элемент отобразится как базовый элемент ввода. Это связано с тем, что вспомогательная функция представления FormElement, на которую опирается вспомогательная функция FormCollection, использует жестко закодированную последовательность предложений if для сопоставления типа элемента с конкретной вспомогательной функцией представления формы. Он не может сопоставить класс моего элемента и поэтому возвращается к FormInput.

т.е. (взято из Zend/Form/View/Helper/FormElement.php, строки 41-49):

    if ($element instanceof Element\Button) {
        $helper = $renderer->plugin('form_button');
        return $helper($element);
    }

    if ($element instanceof Element\Captcha) {
        $helper = $renderer->plugin('form_captcha');
        return $helper($element);
    }

    ...

    $helper = $renderer->plugin('form_input');
    return $helper($element);

и так далее.

Я немного застрял здесь, потому что эта архитектура на самом деле не способствует расширяемости.

Единственное решение, которое пришло мне в голову (кроме рендеринга формы вручную), — это расширить вспомогательный класс представления FormElement и, таким образом, создать собственный вспомогательный класс представления CustomFormElement. Однако из-за его сложности я поместил пользовательский элемент в собственный модуль. Поэтому мне пришлось бы динамически писать этот помощник CustomFormElement, чтобы добавлять пользовательские элементы из любого модуля. Я не думаю, что это рекомендуемая процедура.

Есть ли другое решение или, может быть, даже мой полный подход не рекомендуется? Заранее спасибо!


person Daniel M    schedule 11.09.2012    source источник


Ответы (5)


Похоже, мы оба сталкиваемся с проблемами формы в Zend. Я думаю, что его можно было бы лучше интегрировать со всей структурой MVC.

Я думаю, что ваш подход является правильным. Я мог бы подумать о следующем

  1. Дайте вашим элементам переменную с именем helper, как в ZF1.
  2. Создайте пользовательский рендерер элемента формы, который ТАКЖЕ будет проверять атрибут рендерера элемента формы, чтобы решить, как его визуализировать.

Вы можете повторно использовать ViewHelperProviderInterface или создать свой собственный интерфейс:

class CustomElement implements ViewHelperProviderInterface
{
     public function getViewHelperConfig()
     {
          return array('type' => '\My\View\Helper');
     }
}

or

class CustomElement implements FormHelperProviderInterface
{
     public function getFormHelperConfig()
     {
          return '\My\View\Helper';
          // or
          return new My\View\Helper();
     }
}

Затем в вашем классе FormElement вы можете сделать следующее:

    if ('week' == $type) {
        $helper = $renderer->plugin('form_week');
        return $helper($element);
    }

    if ($element instanceof THEINTERFACE) {
          return $renderer->plugin($element->getFormHelperConfig());
    }

    $helper = $renderer->plugin('form_input');
    return $helper($element);

Это, вероятно, то, что вы имели в виду в любом случае.

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

Кроме того, каждый модуль должен будет ТОЛЬКО предоставить ключ helper_map в конфигурации модуля, чтобы его помощники по представлению были доступны во время рендеринга с компонентами MVC.

person Jerry Saravia    schedule 11.09.2012
comment
да, лучшая интеграция, но при этом сохранение слабой связи со структурой MVC для компонента формы было бы здорово. - person Jerry Saravia; 12.09.2012
comment
Этот ответ устарел сейчас. Теперь есть более несвязанные способы сделать это. - person Daniel Skarbek; 11.04.2015
comment
@DanielSkarbek, я получил отрицательный голос за устаревший ответ? Вопрос старый, как и мой ответ. Этого было достаточно для того времени, когда был задан вопрос. - person Jerry Saravia; 12.04.2015
comment
@JerrySaravia, да, поскольку недавно были добавлены другие ответы, которые лучше, я пытался привлечь к ним внимание, проголосовав против принятого ответа. (Правда, это связано с дополнениями к структуре, которых не было, когда был написан этот ответ.) Есть ли лучший способ сделать это? - person Daniel Skarbek; 14.04.2015
comment
@DanielSkarbek, не наказывайте меня за устаревший ответ, когда в то время не было новых возможностей. Вместо этого проголосуйте за других, чтобы показать, что их мнение более благоприятно, чем у меня, и сделайте комментарий, говоря об этом. - person Jerry Saravia; 15.04.2015
comment
Привет всем, посмотрите другие решения ниже, так как ZF2 изменился и добавил более простой способ сделать это. См. пример @DanielSkarbek. - person Jerry Saravia; 15.04.2015

Я думаю, что самый простой способ - расширить Zend\Form\View\Helper\FormElement, обработать ваши типы полей в вашем методе render() и зарегистрировать ваш FormElement как FormElement по умолчанию для вашего приложения/модуля. Предполагая, что у вас есть свой собственный TestField, который вы хотели бы отобразить:

namespace Application\Form\View\Helper; 

use \Zend\Form\ElementInterface;
use \Zend\Form\View\Helper\FormElement
use \Application\Form\Element\TestField;

class MyFormElement extends FormElement
{
    public function render(ElementInterface $element)
    {
        $renderer = $this->getView();
        if (!method_exists($renderer, 'plugin')) {
            // Bail early if renderer is not pluggable
            return '';
        }

        //your custom fields go here...
        if ($element instanceof TestField) {
            $helper = $renderer->plugin('\Application\Form\View\Helper\FormTestField');
            return $helper($element);
        }

        return parent::render($element);
    }
}

И в Application/config/module.config.php:

'view_helpers' => array(
    'invokables' => array(
         'form_element' => 'Application\Form\View\Helper\MyFormElement',
    )
)
person marcini    schedule 22.11.2012
comment
Этот ответ устарел. Класс FormElement теперь предоставляет функции расширения, поэтому вам не нужно переопределять класс таким образом. - person Daniel Skarbek; 11.04.2015

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

<?php $this->plugin('FormElement')->addType('text', 'formcustom'); ?> 

Это перезапишет помощник представления, используемый в помощниках FormRow,FormCollection, используя ваш помощник представления по имени ключа:

в вашей конфигурации

'view_helpers' => array(
    'invokables' => array(
        'formcustom' => 'Application\Form\View\Helper\FormCustom',
    )
),

Когда был задан этот вопрос, метода, возможно, не было. Но это сейчас.

person Emery King    schedule 16.01.2015
comment
Делать это в представлении кажется неуместным. Я думаю, что помощник должен быть зарегистрирован заранее, и в представлении помощники formRow() или formCollection() должны просто делать правильные вещи. Итак, вероятно, это комбинация регистрации view_helpers, которую вы имеете здесь, плюс какое-то заводское переопределение для formElement, как показывает selimoves в своем ответе. - person Daniel Skarbek; 11.04.2015

Следующее — это то, что я сделал, и мне кажется, что это правильный уровень хранения вещей отдельно и аккуратно.

Данный:

  • Новый элемент: MyModule\Form\MyElement, который расширяет Zend\Form\Element.
  • Новый вспомогательный класс представления для MyElement: MyModule\Form\View\Helper\FormMyElement, который расширяет Zend\Form\View\Helper\AbstractHelper.

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

'view_helpers' => array(
    'invokables'=> array(
        'formMyElement' => 'MyModule\Form\View\Helper\FormMyElement',
    ),
    'factories' => array(
        'formElement' => function($sm) {
            $helper = new \Zend\Form\View\Helper\FormElement();
            $helper->addClass('MyModule\Form\MyElement', 'formMyElement');
            return $helper;
        }
    ),
),

Суть в том, что вы предоставляете новый фабричный метод для FormElement, который по-прежнему создает тот же стандартный класс (нет необходимости его переопределять), но также вызывает метод addClass для регистрации вашего пользовательского помощника в качестве подходящего помощника для вашего пользовательского элемента. Если вы не хотите делать короткое имя для своего помощника, вы можете удалить раздел invokables и поместить FQCN в вызов addClass, но мне нравится иметь короткое имя доступным.

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

Кстати, я нашел эту страницу, которая не касается вспомогательной стороны уравнения, но говорит о регистрации новых классов элементов формы и о том, как переопределить встроенные классы: http://framework.zend.com/manual/current/en/modules/zend.form.advanced-use-of-forms.html

person Daniel Skarbek    schedule 11.04.2015

person    schedule
comment
Это правильная стратегия, но пример излишен. Класс FormElement уже имеет функцию addType(string $type, string $plugin). Не нужно создавать подкласс FormElement, просто используйте фабрику formElement (или form_element), которая вызывает addType и, возможно, addClass. - person claytond; 21.02.2015