I want to add unique Elements in a Zend Form Collection. I found this awesome work from Aron Kerr
I do the forms and fieldsets like in Aron Kerr´s Example and it works fine.
In my case i create a Form to insert a collection of stores from a company.
My Form
First of all i have a Application\Form\CompanyStoreForm with a StoreFieldset like this:
$this->add(array(
'name' => 'company',
'type' => 'Application\Form\Stores\CompanyStoresFieldset',
));
The Fieldsets
Application\Form\Stores\CompanyStoresFieldset has a Collection of Store Entities like this:
$this->add(array(
'type' => 'Zend\Form\Element\Collection',
'name' => 'stores',
'options' => array(
'target_element' => array(
'type' => 'Application\Form\Fieldset\StoreEntityFieldset',
),
),
));
Application\Form\Fieldset\StoreEntityFieldset
$this->add(array(
'name' => 'storeName',
'attributes' => ...,
'options' => ...,
));
//AddressFieldset
$this->add(array(
'name' => 'address',
'type' => 'Application\Form\Fieldset\AddressFieldset',
));
The difference to Arron Kerrs CategoryFieldset is I adding one more fieldset: Application\Form\Fieldset\AddressFieldset
Application\Form\Fieldset\AddressFieldset has a text-element streetName.
The Inputfilters
The CompanyStoresFieldsetInputFilter has no elements to validate.
The StoreEntityFieldsetInputFilter has validators for storeName and the Application\Form\Fieldset\AddressFieldset like this
public function __construct() {
$factory = new InputFactory();
$this->add($factory->createInput([
'name' => 'storeName',
'required' => true,
'filters' => array( ....
),
'validators' => array(...
),
]));
$this->add(new AddressFieldsetInputFilter(), 'address');
}
The AddressFieldset has another Inputfilter AddressFieldsetInputFilter. In AddressFieldsetInputFilter I adding a InputFilter for streetName.
FormFactory
Adding all Inputfilters to the Form like this
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;
}
I use Aron Kerrs CollectionInputFilter.
The storeName should be unique in the whole collection. All works fine, so far!
But now my problem!
The streetName should be unique in the whole collection. But the Element is in the AddressFieldset. I can´t do something like this:
$storeInputFilter->setUniqueFields(array('storeName', 'address' => array('streetName')));
I thought I should extend Aron Kerrs isValid() from 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) < 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->getMessage()) ? $this->getMessage() : $this::UNIQUE_MESSAGE;
foreach($duplicates as $duplicate)
{
$this->invalidInputs[$duplicate][$elementName] = array('unique' => $message);
}
}
}
return $valid;
}
}
First of all I try (just for testing) to add a error message to streetName in the first entry of the collection.
$this->invalidInputs[0]['address']['streetName'] = array('unique' => $message);
But it doens´t work.
Adding it to storeName it works
$this->invalidInputs[0]['storeName'] = array('unique' => $message);
I think the reason is the Fieldset has an own InputFilter()?
When i do a var_dump($this->collectionValues()) i received a multidimensional array of all values (also of the addressFieldset). That´s fine! But i can´t add error messages to the element in the fieldset.
How can i do this? I don´t want to insert all Elements of the AddressFieldset in the StoreEntityFieldset. (I use the AddressFieldset also in other Forms)
I figured it out. You simply can add values with
I don´t know how it does not work yesterday. It was another bug.
I wrote a solution for my problem. Maybe it´s not the best, but it works for me.
CollectionInputFilter
Define a CollectionInputFilter (in a factory)
So let me explain:
Now, we can add elements as unique in fieldsets in our collection. We can NOT add collection fieldsets in our collection and not another fieldsets in our fieldsets. In my opinion if anyone want to do this cases, they better should refactor the form :-)
setUniqueFields Add a simple element as unique
If you want to add a element as unique in a fieldset
We can add special messages for every element with setMessage
Add Message for a Element in the collection
Add message for a Element in a fieldset