Validación de colección de formularios zf2: elementos únicos en conjuntos de campos

Quiero agregar Elementos únicos en una Colección de Formularios Zend. Encontré este trabajo increíble de Aron Kerr

Hago las formas y los fieldsets como en el ejemplo de Aron Kerr y funciona bien.

En mi caso, creo un Formulario para insertar una colección de tiendas de una empresa.

Mi forma

En primer lugar, tengo Application \ Form \ CompanyStoreForm con un StoreFieldset como este:

$this->add(array( 'name' => 'company', 'type' => 'Application\Form\Stores\CompanyStoresFieldset', )); 

Los conjuntos de campo

Application \ Form \ Stores \ CompanyStoresFieldset tiene una colección de entidades de tienda como esta:

 $this->add(array( 'type' => 'Zend\Form\Element\Collection', 'name' => 'stores', 'options' => array( 'target_element' => array( 'type' => 'Application\Form\Fieldset\StoreEntityFieldset', ), ), )); 

Aplicación \ Form \ Fieldset \ StoreEntityFieldset

 $this->add(array( 'name' => 'storeName', 'attributes' => ..., 'options' => ..., )); //AddressFieldset $this->add(array( 'name' => 'address', 'type' => 'Application\Form\Fieldset\AddressFieldset', )); 

La diferencia con Arron Kerrs CategoryFieldset es que agrego un fieldset más: Application \ Form \ Fieldset \ AddressFieldset

Application \ Form \ Fieldset \ AddressFieldset tiene un elemento de texto streetName .

Los filtros de entrada

The CompanyStoresFieldsetInputFilter no tiene elementos para validar.

StoreEntityFieldsetInputFilter tiene validadores para storeName y Application \ Form \ Fieldset \ AddressFieldset como este

 public function __construct() { $factory = new InputFactory(); $this->add($factory->createInput([ 'name' => 'storeName', 'required' => true, 'filters' => array( .... ), 'validators' => array(... ), ])); $this->add(new AddressFieldsetInputFilter(), 'address'); } 

AddressFieldset tiene otro Inputfilter AddressFieldsetInputFilter . En AddressFieldsetInputFilter , agrego un InputFilter para streetName .

FormFactory

Agregar todos los filtros de entrada al formulario como este

  public function createService(ServiceLocatorInterface $serviceLocator) { $form = $serviceLocator->get('FormElementManager')->get('Application\Form\CompanyStoreForm'); //Create a Form Inputfilter $formFilter = new InputFilter(); //Create Inputfilter for CompanyStoresFieldsetInputFilter() $formFilter->add(new CompanyStoresFieldsetInputFilter(), 'company'); //Create Inputfilter for StoreEntityFieldsetInputFilter() $storeInputFilter = new CollectionInputFilter(); $storeInputFilter->setInputFilter(new StoreEntityFieldsetInputFilter()); $storeInputFilter->setUniqueFields(array('storeName')); $storeInputFilter->setMessage('Just insert one entry with this store name.'); $formFilter->get('company')->add($storeInputFilter, 'stores'); $form->setInputFilter($formFilter); return $form; } 

Yo uso Aron Kerrs CollectionInputFilter.

StoreName debe ser único en toda la colección. Todo funciona bien, hasta ahora!

¡Pero ahora mi problema!

The streetName debe ser único en toda la colección. Pero el elemento está en AddressFieldset. No puedo hacer algo como esto:

 $storeInputFilter->setUniqueFields(array('storeName', 'address' => array('streetName'))); 

Pensé que debería extender Aron Kerrs isValid () de CollectionInputFilter

Aron Kerrs Original isValid ()

 public function isValid() { $valid = parent::isValid(); // Check that any fields set to unique are unique if($this->uniqueFields) { // for each of the unique fields specified spin through the collection rows and grab the values of the elements specified as unique. foreach($this->uniqueFields as $k => $elementName) { $validationValues = array(); foreach($this->collectionValues as $rowKey => $rowValue) { // Check if the row has a deleted element and if it is set to 1. If it is don't validate this row. if(array_key_exists('deleted', $rowValue) && $rowValue['deleted'] == 1) continue; $validationValues[] = $rowValue[$elementName]; } // Get only the unique values and then check if the count of unique values differs from the total count $uniqueValues = array_unique($validationValues); if(count($uniqueValues) getMessage()) ? $this->getMessage() : $this::UNIQUE_MESSAGE; foreach($duplicates as $duplicate) { $this->invalidInputs[$duplicate][$elementName] = array('unique' => $message); } } } return $valid; } } 

Antes que nada, bash (solo para probar) agregar un mensaje de error a streetName en la primera entrada de la colección.

 $this->invalidInputs[0]['address']['streetName'] = array('unique' => $message); 

Pero no funciona

Agregarlo a storeName funciona

 $this->invalidInputs[0]['storeName'] = array('unique' => $message); 

Creo que la razón es que Fieldset tiene un propio InputFilter ()?

Cuando hago un var_dump ($ this-> collectionValues ​​()) recibí una matriz multidimensional de todos los valores (también del addressFieldset). ¡Esta bien! Pero no puedo agregar mensajes de error al elemento en el fieldset.

¿Cómo puedo hacer esto? No quiero insertar todos los elementos de AddressFieldset en StoreEntityFieldset. (Uso el AddressFieldset también en otras formas)

Me lo imaginé. Simplemente puede agregar valores con

 $this->invalidInputs[]['address']['streetName'] = array('unique' => $message); 

No sé cómo no funciona ayer. Fue otro error.

Escribí una solución para mi problema. Tal vez no sea el mejor, pero funciona para mí.

CollectionInputFilter

 class CollectionInputFilter extends ZendCollectionInputFilter { protected $uniqueFields; protected $validationValues = array(); protected $message = array(); const UNIQUE_MESSAGE = 'Each item must be unique within the collection'; /** * @return the $message */ public function getMessageByElement($elementName, $fieldset = null) { if($fieldset != null){ return $this->message[$fieldset][$elementName]; } return $this->message[$elementName]; } /** * @param field_type $message */ public function setMessage($message) { $this->message = $message; } /** * @return the $uniqueFields */ public function getUniqueFields() { return $this->uniqueFields; } /** * @param multitype:string $uniqueFields */ public function setUniqueFields($uniqueFields) { $this->uniqueFields = $uniqueFields; } public function isValid() { $valid = parent::isValid(); // Check that any fields set to unique are unique if($this->uniqueFields) { foreach($this->uniqueFields as $key => $elementOrFieldset){ // if the $elementOrFieldset is a fieldset, $key is our fieldset name, $elementOrFieldset is our collection of elements we have to check if(is_array($elementOrFieldset) && !is_numeric($key)){ // We need to validate every element in the fieldset that should be unique foreach($elementOrFieldset as $elementKey => $elementName){ // $key is our fieldset key, $elementName is the name of our element that should be unique $validationValues = $this->getValidCollectionValues($elementName, $key); // get just unique values $uniqueValues = array_unique($validationValues); //If we have a difference, not all are unique if(count($uniqueValues) < count($validationValues)) { // The counts didn't match so now grab the row keys where the duplicate values were and set the element message to the element on that row $duplicates = array_keys(array_diff_key($validationValues, $uniqueValues)); $valid = false; $message = ($this->getMessageByElement($elementName, $key)) ? $this->getMessageByElement($elementName, $key) : $this::UNIQUE_MESSAGE; // set error messages foreach($duplicates as $duplicate) { //$duplicate = our collection entry key, $key is our fieldsetname $this->invalidInputs[$duplicate][$key][$elementName] = array('unique' => $message); } } } } //its just a element in our collection, $elementOrFieldset is a simple element else { // in this case $key is our element key , we don´t need the second param because we haven´ta fieldset $validationValues = $this->getValidCollectionValues($elementOrFieldset); $uniqueValues = array_unique($validationValues); if(count($uniqueValues) < count($validationValues)) { // The counts didn't match so now grab the row keys where the duplicate values were and set the element message to the element on that row $duplicates = array_keys(array_diff_key($validationValues, $uniqueValues)); $valid = false; $message = ($this->getMessageByElement($elementOrFieldset)) ? $this->getMessageByElement($elementOrFieldset) : $this::UNIQUE_MESSAGE; foreach($duplicates as $duplicate) { $this->invalidInputs[$duplicate][$elementOrFieldset] = array('unique' => $message); } } } } } return $valid; } /** * * @param type $elementName * @param type $fieldset * @return type */ public function getValidCollectionValues($elementName, $fieldset = null){ $validationValues = array(); foreach($this->collectionValues as $rowKey => $collection){ // If our values are in a fieldset if($fieldset != null && is_array($collection[$fieldset])){ $rowValue = $collection[$fieldset][$elementName]; } else{ //collection is one element like $key => $value $rowValue = $collection[$elementName]; } // Check if the row has a deleted element and if it is set to 1. If it is don't validate this row. if($rowValue == 1 && $rowKey == 'deleted') continue; $validationValues[$rowKey] = $rowValue; } return $validationValues; } public function getMessages() { $messages = array(); if (is_array($this->getInvalidInput()) || $this->getInvalidInput() instanceof Traversable) { foreach ($this->getInvalidInput() as $key => $inputs) { foreach ($inputs as $name => $input) { if(!is_string($input) && !is_array($input)) { $messages[$key][$name] = $input->getMessages(); continue; } $messages[$key][$name] = $input; } } } return $messages; } } 

Definir un CollectionInputFilter (en una fábrica)

 $storeInputFilter = new CollectionInputFilter(); $storeInputFilter->setInputFilter(new StoreEntityFieldsetInputFilter()); $storeInputFilter->setUniqueFields(array('storeName', 'address' => array('streetName'))); $storeInputFilter->setMessage(array('storeName' => 'Just insert one entry with this store name.', 'address' => array('streetName' => 'You already insert a store with this street name'))); $formFilter->get('company')->add($storeInputFilter, 'stores'); 

Entonces déjame explicarte:

Ahora, podemos agregar elementos como únicos en conjuntos de campo en nuestra colección. No podemos agregar conjuntos de campo de colección en nuestra colección y no otros conjuntos de campo en nuestros conjuntos de campos. En mi opinión, si alguien quiere hacer estos casos, será mejor que refactorice la forma 🙂

setUniqueFields Agrega un elemento simple como único

 array('your-unique-element','another-element'); 

Si desea agregar un elemento como único en un fieldset

 array('your-unique-element', 'fieldsetname' => array('your-unique-element-in-fieldset')) 

Podemos agregar mensajes especiales para cada elemento con setMessage

Agregar mensaje para un elemento en la colección

 array('storeName' => 'Just insert one entry...') 

Agregar mensaje para un elemento en un conjunto de campo

 array('fieldset-name' => array('your-unique-element-in-fieldset' => 'You already insert ..'))