ZF2 + Doctrine2 – Fieldset в Fieldset коллекции в Fieldset не проверяется правильно

Я задал аналогичный вопрос некоторое время назад, что привело к структурированию форм, полей и входных фильтров.

Я тщательно применяю принцип разделения проблем, чтобы разделить Fieldsets от InputFilters, поскольку модули, которые они создали, также будут использоваться в API (основанный на Apigility), поэтому мне нужны только Entities и InputFilters.

Тем не менее, у меня теперь есть проблема, когда у меня есть Fieldset, используемый Fieldset, используемый в коллекции в Fieldset, что внутреннее поле Fieldset не проверяется.

Позвольте мне уточнить примеры и код!

Ситуация заключается в том, что я хочу иметь возможность создать Location . A Location состоит из name свойства и OneToMany ArrayCollection|Address[] . Это связано с тем, что Location может иметь несколько адресов (например, адрес посетителей и адрес доставки).

Address состоит из нескольких свойств (улицы, номера, города, Country и т. Д.) OneToOne Coordinates ассоциации OneToOne Coordinates .

Теперь Address имеет следующий набор полей:

 class AddressFieldset extends AbstractFieldset { public function init() { parent::init(); // More properties, but you get the idea $this->add([ 'name' => 'street', 'required' => false, 'type' => Text::class, 'options' => [ 'label' => _('Street'), ], ]); $this->add([ 'name' => 'country', 'required' => false, 'type' => ObjectSelect::class, 'options' => [ 'object_manager' => $this->getEntityManager(), 'target_class' => Country::class, 'property' => 'id', 'is_method' => true, 'find_method' => [ 'name' => 'getEnabledCountries', ], 'display_empty_item' => true, 'empty_item_label' => '---', 'label' => _('Country'), 'label_generator' => function ($targetEntity) { return $targetEntity->getName(); }, ], ]); $this->add([ 'type' => CoordinatesFieldset::class, 'required' => false, 'name' => 'coordinates', 'options' => [ 'use_as_base_fieldset' => false, ], ]); } } 

Как вы можете видеть, должны быть указаны данные для Address сущности, должна быть выбрана Country , и могут быть предоставлены Coordinates (не обязательно).

Вышеприведенное утверждение проверяется с использованием InputFilter ниже.

 class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter { /** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */ protected $coordinatesFieldsetInputFilter; public function __construct( CoordinatesFieldsetInputFilter $filter, EntityManager $objectManager, Translator $translator ) { $this->coordinatesFieldsetInputFilter = $filter; parent::__construct([ 'object_manager' => $objectManager, 'object_repository' => $objectManager->getRepository(Address::class), 'translator' => $translator, ]); } /** * Sets AddressFieldset Element validation */ public function init() { parent::init(); $this->add($this->coordinatesFieldsetInputFilter, 'coordinates'); $this->add([ 'name' => 'street', 'required' => false, 'filters' => [ ['name' => StringTrim::class], ['name' => StripTags::class], ], 'validators' => [ [ 'name' => StringLength::class, 'options' => [ 'min' => 3, 'max' => 255, ], ], ], ]); $this->add([ 'name' => 'country', 'required' => false, ]); } } 

Как вы можете видеть, для параметра AddressFieldsetInputFilter потребовалось несколько вещей, одним из которых является CoordinatesFieldsetInputFilter . В функции init() это добавляется с именем, соответствующим значению, указанному в полевом наборе.

Теперь все вышеперечисленные работы, без проблем. Адреса с координатами везде. Здорово.

Проблема возникает, когда мы идем на еще один уровень и имеем LocationFieldset , как показано ниже, с помощью LocationFieldsetInputFilter .

 class LocationFieldset extends AbstractFieldset { public function init() { parent::init(); $this->add([ 'name' => 'name', 'required' => true, 'type' => Text::class, 'options' => [ 'label' => _('Name'), ], ]); $this->add([ 'type' => Collection::class, 'name' => 'addresses', 'options' => [ 'label' => _('Addresses'), 'count' => 1, 'allow_add' => true, 'allow_remove' => true, 'should_create_template' => true, 'target_element' => $this->getFormFactory()->getFormElementManager()->get(AddressFieldset::class), ], ]); } } 

В приведенном ниже классе вы можете заметить кучу прокомментированных строк, это были разные попытки изменить DI и / или настройку InputFilter, чтобы он работал.

 class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter { /** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */ protected $addressFieldsetInputFilter; // /** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */ // protected $coordinatesFieldsetInputFilter; public function __construct( AddressFieldsetInputFilter $filter, // CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter, EntityManager $objectManager, Translator $translator ) { $this->addressFieldsetInputFilter = $filter; // $this->coordinatesFieldsetInputFilter = $coordinatesFieldsetInputFilter; parent::__construct([ 'object_manager' => $objectManager, 'object_repository' => $objectManager->getRepository(Location::class), 'translator' => $translator, ]); } /** * Sets LocationFieldset Element validation */ public function init() { parent::init(); $this->add($this->addressFieldsetInputFilter, 'addresses'); // $this->get('addresses')->add($this->coordinatesFieldsetInputFilter, 'coordinates'); $this->add([ 'name' => 'name', 'required' => true, 'filters' => [ ['name' => StringTrim::class], ['name' => StripTags::class], ], 'validators' => [ [ 'name' => StringLength::class, 'options' => [ 'min' => 3, 'max' => 255, ], ], ], ]); } } 

Возможно, вы заметили, что LocationFieldset и LocationFieldsetInputFilter используют существующий AddressFieldset и `AddressFieldsetInputFilter.

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

Но что не так?

Ну, чтобы создать Location , кажется, что требуется всегда вводить Coordinates . Если вы посмотрите в AddressFieldset (вверху), вы увидите 'required' => false, , поэтому это не имеет смысла.

Однако, когда я DO вводят значения на входах, они не проверяются. При отладке я попадаю в \Zend\InputFilter\BaseInputFilter , строка # 262, где он специально проверяет ввод, и я замечаю, что он потерял его данные по пути проверки.

Я подтвердил наличие данных в начале и во время проверки до тех пор, пока он не попытается проверить объект Coordinates , где он, кажется, потеряет его (не выяснили, почему).

Если кто-то может указать мне в правильном направлении, чтобы очистить это, эта помощь будет очень признательна. Уже несколько часов ударяют об этом вопросе.

РЕДАКТИРОВАТЬ

Добавлен в виде частичный код, чтобы показать метод печати, в случае, если это должно / могло бы помочь:

адресно-form.phtml

 <?php /** @var \Address\Form\AddressForm $form */ $form->prepare(); echo $this->form()->openTag($form); echo $this->formRow($form->get('csrf')); echo $this->formRow($form->get('address')->get('id')); echo $this->formRow($form->get('address')->get('street')); echo $this->formRow($form->get('address')->get('city')); echo $this->formRow($form->get('address')->get('country')); echo $this->formCollection($form->get('address')->get('coordinates')); echo $this->formRow($form->get('submit')); echo $this->form()->closeTag($form); 

местоположения form.phtml

 <?php /** @var \Location\Form\LocationForm $form */ $form->prepare(); echo $this->form()->openTag($form); echo $this->formRow($form->get('csrf')); echo $this->formRow($form->get('location')->get('id')); echo $this->formRow($form->get('location')->get('name')); //echo $this->formCollection($form->get('location')->get('addresses')); $addresses = $form->get('location')->get('addresses'); foreach ($addresses as $address) { echo $this->formCollection($address); } echo $this->formRow($form->get('submit')); echo $this->form()->closeTag($form); 

И на всякий случай все это становится еще более ясным: отладочная фотография, которая помогает isEmpty ошибки, когда четко установлены

Solutions Collecting From Web of "ZF2 + Doctrine2 – Fieldset в Fieldset коллекции в Fieldset не проверяется правильно"

После другого дня отладки (и ругательства) я нашел ответ!

Этот вопрос помог мне, указав на Zend CollectionInputFilter .

Поскольку AddressFieldset добавляется в LocationFieldset в Collection , он должен быть проверен с использованием CollectionInputFilter который имеет определенный InputFilter для указанного Fieldset .

Чтобы исправить мое приложение, мне пришлось модифицировать как LocationFieldsetInputFilter и LocationFieldsetInputFilterFactory . Ниже обновленного кода со старым кодом в комментариях.

LocationFieldsetInputFilterFactory.php

 class LocationFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory { /** * @param ServiceLocatorInterface|ControllerManager $serviceLocator * @return InputFilter */ public function createService(ServiceLocatorInterface $serviceLocator) { parent::setupRequirements($serviceLocator, Location::class); /** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */ $addressFieldsetInputFilter = $this->getServiceManager()->get('InputFilterManager') ->get(AddressFieldsetInputFilter::class); $collectionInputFilter = new CollectionInputFilter(); $collectionInputFilter->setInputFilter($addressFieldsetInputFilter); // Make sure to add the FieldsetInputFilter that is to be used for the Entities! return new LocationFieldsetInputFilter( $collectionInputFilter, // New // $addressFieldsetInputFilter, // Removed $this->getEntityManager(), $this->getTranslator() ); } } 

LocationFieldsetInputFilter.php

 class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter { // Removed // /** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */ // protected $addressFieldsetInputFilter ; // New /** @var CollectionInputFilter $addressCollectionInputFilter */ protected $addressCollectionInputFilter; public function __construct( CollectionInputFilter $addressCollectionInputFilter, // New // AddressFieldsetInputFilter $filter, // Removed EntityManager $objectManager, Translator $translator ) { // $this->addressFieldsetInputFilter = $filter; // Removed $this->addressCollectionInputFilter = $addressCollectionInputFilter; // New parent::__construct([ 'object_manager' => $objectManager, 'object_repository' => $objectManager->getRepository(Location::class), 'translator' => $translator, ]); } /** * Sets LocationFieldset Element validation */ public function init() { parent::init(); // $this->add($this->addressFieldsetInputFilter, 'addresses'); // Removed $this->add($this->addressCollectionInputFilter, 'addresses'); // New $this->add([ 'name' => 'name', 'required' => true, 'filters' => [ ['name' => StringTrim::class], ['name' => StripTags::class], ], 'validators' => [ [ 'name' => StringLength::class, 'options' => [ 'min' => 3, 'max' => 255, ], ], ], ]); } } 

То, как это работает, заключается в том, что во время проверки данных он будет применять уникальный AddressFieldsetInputFilter к каждому « element », полученному с клиентской стороны. Потому что Collection от клиента может быть 0 или более из этих элементов (поскольку добавление / удаление их выполняется с использованием JavaScript).

Теперь, когда я понял это, на самом деле это имеет смысл.