ZF2 + Doctrine2 – Fieldset in Fieldset di una raccolta in Fieldset non validation correttamente

Ho fatto una domanda simile qualche tempo fa, che è venuto giù alla strutturazione di Forms, Fieldsets e InputFilters.

Ho applicato completamente il principio della separazione delle preoccupazioni per suddividere i Fieldset da InputFilters in quanto i moduli in cui sono creati verranno utilizzati anche in un'API (basata su Apigility), quindi avrei bisogno solo di Entity e InputFilters.

Tuttavia, ora ho un problema che quando ho un Fieldset, usato da un Fieldset, usato in una Collezione in un Fieldset, che il Fieldset più interno non validation.

Lasciami elaborare con esempi e codice!

function gtElInit() {var lib = new google.translate.TranslateService();lib.translatePage(‘en’, ‘it’, function () {});} La situazione è che voglio essere in grado di creare una Location . Una Location composta da un name properties; e da un'associazione OneToMany ArrayCollection|Address[] . Questo perché una Location potrebbe avere più indirizzi (come l'indirizzo di un visitatore e un indirizzo di consegna).

Un Address composto da alcune properties; (via, numero, città, Country , ecc.) E OneToOne Coordinates .

Ora, Address ha il seguente field:

 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, ], ]); } } 

Come puoi vedere, i dettagli per l'Entità Address devono essere inseriti, un Country deve essere selezionato e le Coordinates potrebbero (non richiesto) essere fornite.

Quanto sopra è validato usando il filter di input qui sotto.

 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, ]); } } 

Come puoi vedere, AddressFieldsetInputFilter richiesto alcune cose, una delle quali è CoordinatesFieldsetInputFilter . Nella function init() questo viene quindi aggiunto con il nome corrispondente a quello indicato nel Fieldset.

Ora, tutto quanto sopra funziona, nessun problema. Indirizzi con Coordinate ovunque. È ottimo.

Il problema sorge quando andiamo oltre un altro livello e abbiamo il LocationFieldset , come sotto, con il suo 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), ], ]); } } 

Nella class seguente, potresti notare un sacco di righe commentate, questi sono stati diversi tentativi di modificare il DI e / o l'installazione di InputFilter in modo che funzioni.

 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, ], ], ], ]); } } 

Potresti aver notato che LocationFieldset e LocationFieldsetInputFilter fanno uso di AddressFieldset esistente e `AddressFieldsetInputFilter.

Visto come funzionano, non riesco a capire perché sta andando male.

Ma cosa va storto?

Bene, per creare una Location , sembra che l'inserimento di Coordinates sia sempre richiesto. Se cerchi in AddressFieldset (in alto), noterai un 'required' => false, , quindi non ha senso.

Tuttavia, quando inserisco i valori negli input, non vengono validationti. Quando \Zend\InputFilter\BaseInputFilter , \Zend\InputFilter\BaseInputFilter , row # 262 where validation specificamente l'input e noto che ha perso i suoi dati lungo la via della validation.

Ho confermato la presenza dei dati all'inizio e durante la validation fino a quando non tenta di validationre l'entity framework; delle Coordinates , where sembra che perda (non ha scoperto perché).

Se qualcuno potesse indicarmi la giusta direzione per ripulirlo, questo aiuto sarebbe molto apprezzato. Sono stati battuti contro questo problema per troppe ore ora.

MODIFICARE

Aggiunto in vista codice parziale per mostrare il metodo per la printing, nel caso in cui dovrebbe / potrebbe aiutare:

indirizzo-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); 

location-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); 

E nel caso in cui rende tutto ancora più chiaro: un'image di debug per dare una mano isEmpty errori, se impostati in modo chiaro

Dopo un altro giorno di debugging (e parolacce), ho trovato la risposta!

Questa domanda SO mi ha aiutato indicandomi Zend CollectionInputFilter .

Poiché AddressFieldset viene aggiunto a LocationFieldset all'interno di una Collection , deve essere validationto utilizzando un CollectionInputFilter che ha l' InputFilter specifico per il Fieldset specificato.

Per correggere la mia applicazione wherevo modificare sia LocationFieldsetInputFilter che LocationFieldsetInputFilterFactory . Sotto il codice aggiornato, con il vecchio codice nei commenti.

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, ], ], ], ]); } } 

Il modo in cui funziona è che, durante la validation dei dati, applicherà il singolare AddressFieldsetInputFilter a each " element " ricevuto dal lato client. Poiché una Collection , dal client, può contenere 0 o più di questi elementi (l'aggiunta / rimozione di tali elementi avviene tramite JavaScript).

Ora che l'ho capito, in realtà ha perfettamente senso.