Я хочу добавить уникальные элементы в коллекцию форм Zend.
Я нашел эту удивительную работу от Арон Керр
Я делаю формы и наборы полей, как в Примере Арона Керра, и он отлично работает.
В моем случае я создаю форму, чтобы вставить коллекцию магазинов от компании.
Моя форма
Прежде всего, у меня есть Application \ Form \ CompanyStoreForm с StoreFieldset, как это:
$this->add(array(
'name' => 'company',
'type' => 'Application\Form\Stores\CompanyStoresFieldset',
));
Наборы полей
Application \ Form \ Магазины \ CompanyStoresFieldset есть коллекция сущностей магазина, как это:
$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',
));
Разница с Арроном Керрсом CategoryFieldset я добавляю еще один fieldset: Application \ Form \ Fieldset \ AddressFieldset
Application \ Form \ Fieldset \ AddressFieldset имеет текстовый элемент название улицы.
Входные фильтры
CompanyStoresFieldsetInputFilter не имеет элементов для проверки
StoreEntityFieldsetInputFilter имеет валидаторы для название магазина и Application \ Form \ Fieldset \ AddressFieldset как это
public function __construct() {
$factory = new InputFactory();
$this->add($factory->createInput([
'name' => 'storeName',
'required' => true,
'filters' => array( ....
),
'validators' => array(...
),
]));
$this->add(new AddressFieldsetInputFilter(), 'address');
}
AddressFieldset имеет другой входной фильтр AddressFieldsetInputFilter.
В AddressFieldsetInputFilter Я добавил InputFilter для название улицы.
FormFactory
Добавление всех Inputfilters в форму, как это
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;
}
Я использую Aron Kerrs CollectionInputFilter.
название магазина должен быть уникальным во всей коллекции.
Пока все отлично работает!
Но теперь моя проблема!
название улицы должен быть уникальным во всей коллекции.
Но Элемент находится в AddressFieldset.
Я не могу сделать что-то вроде этого:
$storeInputFilter->setUniqueFields(array('storeName', 'address' => array('streetName')));
Я думал, что я должен расширить Арон Керрс является действительным() от CollectionInputFilter
Арон Керрс Оригинал 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;
}
}
Прежде всего я пытаюсь (только для тестирования) добавить сообщение об ошибке в streetName в первой записи коллекции.
$this->invalidInputs[0]['address']['streetName'] = array('unique' => $message);
Но это не работает.
Добавление его в storeName это работает
$this->invalidInputs[0]['storeName'] = array('unique' => $message);
Я думаю, причина в том, что Fieldset имеет собственный InputFilter ()?
Когда я делаю var_dump ($ this-> collectionValues ()) я получил многомерный массив всех значений (также addressFieldset).
Все в порядке! Но я не могу добавить сообщения об ошибках в элемент в fieldset.
Как я могу это сделать?
Я не хочу вставлять все элементы набора AddressField в набор StoreEntityField. (Я использую AddressFieldset также в других формах)
Я понял. Вы можете просто добавить значения с
$this->invalidInputs[<entry-key>]['address']['streetName'] = array('unique' => $message);
Я не знаю, как это не работает вчера. Это была еще одна ошибка.
Я написал решение для моей проблемы. Возможно, это не самое лучшее, но это работает для меня.
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´t a 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;
}
}
Определить CollectionInputFilter (на фабрике)
$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');
Итак, позвольте мне объяснить:
Теперь мы можем добавить элементы как уникальные в fieldsets в нашей коллекции.
Мы не можем добавлять наборы полей коллекции в нашу коллекцию, а не другие наборы полей в наших наборах полей.
На мой взгляд, если кто-то захочет сделать это, им лучше провести рефакторинг формы 🙂
setUniqueFields
Добавить простой элемент как уникальный
array('your-unique-element','another-element');
Если вы хотите добавить элемент как уникальный в fieldset
array('your-unique-element', 'fieldsetname' => array('your-unique-element-in-fieldset'))
Мы можем добавить специальные сообщения для каждого элемента с setMessage
Добавить сообщение для элемента в коллекции
array('storeName' => 'Just insert one entry...')
Добавить сообщение для элемента в fieldset
array('fieldset-name' => array('your-unique-element-in-fieldset' => 'You already insert ..'))
Других решений пока нет …