Я хочу реализовать форму, которая довольно проста. Единственное, что усложняет ситуацию, — это то, что я использую две коллекции в своей форме. Отображение двух коллекций в представлении работает как шарм. Проблема заключается в валидации и связанной с этим гидратации связанной сущности формы. Если все проверено и ошибок не возникает, экземпляр формы пытается гидрировать связанный объект и в результате выдает исключение:
Zend \ Hydrator \ ArraySerializable :: hydrate ожидает, что предоставленный объект реализует exchangeArray () или populate ()
Но сначала пример кода …
Форма занятий
namespace Application\Form;
use Zend\Form\Element\Collection;
use Zend\Form\Element\Text;
use Zend\Form\Form;
class MyForm extends Form
{
public function __construct($name = '', $options = [])
{
parent::__construct($name, $options);
$this->setAttribute('method', 'post');
$this->setAttribute('id', 'my-form');
}
public function init()
{
$this->add([
'name' => 'my-text-field',
'type' => Text::class,
'attributes' => [
...
],
'options' => [
...
],
]);
// The first collection
$this->add([
'name' => 'first-collection',
'type' => Collection::class,
'options' => [
'count' => 2,
'should_create_template' => true,
'template_placeholder' => '__index__',
'allow_add' => true,
'allow_remove' => true,
'target_element' => [
'type' => FieldsetOne::class,
],
],
]);
// the second collection
$this->add([
'name' => 'second-collection',
'type' => Collection::class,
'options' => [
'count' => 2,
'should_create_template' => true,
'template_placeholder' => '__index__',
'allow_add' => true,
'allow_remove' => true,
'target_element' => [
'type' => FieldsetTwo::class,
],
],
]);
}
}
Указанные классы Fieldset, связанные с коллекциями, выглядят практически одинаково.
namespace Application\Form;
use Zend\Form\Element\Number;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
class FieldsetOne extends Fieldset implements InputFilterProviderInterface
{
public function init()
{
$this->add([
'name' => 'my-number',
'type' => Number::class,
'options' => [
...
],
'attributes' => [
...
],
]);
}
public function getInputFilterSpecification()
{
return [
'my-number' => [
'required' => true,
'filters' => [
[
'name' => StripTags::class,
],
[
'name' => ToInt::class,
],
],
'validators' => [
[
'name' => NotEmpty::class,
],
[
'name' => IsInt::class,
'options' => [
'locale' => 'de_DE',
],
],
],
],
];
}
}
Подведенные формы получили две коллекции числовых элементов. Все данные, которые предоставляются через форму, должны в конечном итоге в следующей сущности.
Класс входного фильтра
Форма фильтруется и проверяется с помощью следующего входного фильтра. Входной фильтр будет привязан к форме через фабрику. Фабрика будет показана позже.
class MyFormInputFilter extends InputFilter
{
public function init()
{
$this->add([
'name' => 'my-text-field',
'required' => true,
'filters' => [
[
'name' => StripTags::class,
],
[
'name' => StringTrim::class,
],
],
]);
}
}
Входной фильтр содержит только настройки для my-text-field
элемент. Коллекции будут проверены с InputFilterProviderInterface
в наборах полей, установленных в качестве целевых элементов. Класс входного фильтра создается на фабрике и записывается в input_filters
раздел в module.config.php
,
Форма объекта
Сущность будет привязана как объект к форме на фабрике, как это выглядит в следующем примере.
namespace Application\Entity;
class MyFormEntity
{
protected $myTextField;
protected $firstCollection;
protected $secondCollection;
public function getMyTextField()
{
return $this->myTextField;
}
public function setMyTextField($myTextField)
{
$this->myTextField = $myTextField;
return $this;
}
public function getFirstCollection()
{
return $this->firstCollection;
}
public function setFirstCollection(array $firstCollection)
{
$this->firstCollection = $firstCollection;
return $this;
}
public function getSecondCollection()
{
return $this->secondCollection;
}
public function setSecondCollection(array $secondCollection)
{
$this->secondCollection = $secondCollection;
return $this;
}
}
Эта сущность будет привязана как объект к форме. Форма будет гидратирована быть собственной Zend ClassMethods
класс гидратор. Для коллекций две гидраторные стратегии добавляются в гидратор. Стратегии гидратора для коллекций выглядят следующим образом.
namespace Application\Hydrator\Strategy;
class FirstCollectionStrategy extends DefaultStrategy
{
public function hydrate($value)
{
$aEntities = [];
if (is_array($value)) {
foreach ($value as $key => $data) {
$aEntities[] = (new ClassMethods(false))->hydrate($data, new CollectionOneEntity());
}
}
return $aEntities;
}
}
Эта стратегия будет гидрировать данные из коллекции один в соответствующий объект.
Все на заводе
Это фабрика, которая создает экземпляр формы.
class MyFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$parentLocator = $serviceLocator->getServiceLocator();
$filter = $parentLocator->get('InputFilterManager')->get(MyFormInputFilter::class);
$hydrator = (new ClassMethods())
->addStrategy('first-collection', new FirstCollectionStrategy())
->addStrategy('second-collection', new SecondCollectionStrategy());
$object = new MyFormEntity();
$form = (new MyForm())
->setInputFilter($filter)
->setHydrator($hydrator)
->setObject($object);
return $form;
}
}
Эта фабрика упоминается в form_elements
раздел в module.config.php
файл.
Эта проблема
Все отлично работает Элемент ввода, а также коллекции отображаются в представлении. Если форма отправлена и $form->isValid()
метод вызывается в контроллере все заканчивается в BadMethodCallException
,
Zend \ Hydrator \ ArraySerializable :: hydrate ожидает, что предоставленный объект реализует exchangeArray () или populate ()
Я не связывал сущности коллекции с формой в контроллере, потому что стратегии гидратора добавляются в гидратор формы, который должен гидратировать сущность формы. Это имеет смысл для меня, потому что зенд форма может связывать только один объект. Если я позвоню bind
Метод дважды в контроллере, первый связанный объект будет перезаписан.
Можно ли добавить более одного объекта с bind
метод формы, так что две коллекции могут быть обработаны? Как могут выглядеть альтернативы? Что я делаю не так?
Задача ещё не решена.
Других решений пока нет …