Я задал аналогичный вопрос некоторое время назад, что привело к структурированию форм, полей и входных фильтров.
Я тщательно применяю принцип разделения проблем, чтобы разделить 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);
И на всякий случай все это становится еще более ясным: отладочная фотография, которая помогает
После другого дня отладки (и ругательства) я нашел ответ!
Этот вопрос помог мне, указав на 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).
Теперь, когда я понял это, на самом деле это имеет смысл.