zf2 表单集合验证 - 字段集中的唯一元素
zf2 Form Collection Validation - Unique Elements in Fieldsets
我想在 Zend 表单集合中添加独特的元素。
我从 Aron Kerr
找到了这个很棒的作品
我按照 Aron Kerr 的示例中的方式处理表单和字段集,并且效果很好。
在我的例子中,我创建了一个表单来插入公司的商店集合。
我的表格
首先我有一个 Application\Form\CompanyStoreForm 和这样的 StoreFieldset:
$this->add(array(
'name' => 'company',
'type' => 'Application\Form\Stores\CompanyStoresFieldset',
));
字段集
Application\Form\Stores\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',
));
与 Arron Kerrs CategoryFieldset 的不同之处在于我添加了一个字段集:Application\Form\Fieldset\AddressFieldset
Application\Form\Fieldset\AddressFieldset 有一个文本元素 streetName.
输入过滤器
CompanyStoresFieldsetInputFilter 没有要验证的元素。
StoreEntityFieldsetInputFilter 有 storeName 和 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 有另一个 Inputfilter AddressFieldsetInputFilter。
在 AddressFieldsetInputFilter 中,我为 streetName.
添加了一个 InputFilter
FormFactory
像这样将所有输入过滤器添加到表单
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。
storeName 在整个集合中应该是唯一的。
到目前为止一切正常!
但现在我的问题来了!
streetName 在整个集合中应该是唯一的。
但是元素在 AddressFieldset 中。
我不能做这样的事情:
$storeInputFilter->setUniqueFields(array('storeName', 'address' => array('streetName')));
我想我应该从 CollectionInputFilter
扩展 Aron Kerrs isValid()
Aron Kerrs 原创 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()) 时,我收到了一个包含所有值(也是地址字段集)的多维数组。
没关系!但是我无法向字段集中的元素添加错误消息。
我该怎么做?
我不想在 StoreEntityFieldset 中插入 AddressFieldset 的所有元素。 (我在其他表单中也使用了 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');
所以让我解释一下:
现在,我们可以在集合的字段集中添加元素作为唯一元素。
我们不能在我们的集合中添加集合字段集,也不能在我们的字段集中添加另一个字段集。
在我看来,如果有人想做这种情况,他们最好重构表格 :-)
setUniqueFields
添加一个简单的元素作为 unique
array('your-unique-element','another-element');
如果要将元素添加为字段集中的唯一元素
array('your-unique-element', 'fieldsetname' => array('your-unique-element-in-fieldset'))
我们可以使用 setMessage
为每个元素添加特殊消息
为集合中的元素添加消息
array('storeName' => 'Just insert one entry...')
为字段集中的元素添加消息
array('fieldset-name' => array('your-unique-element-in-fieldset' => 'You already insert ..'))
我想在 Zend 表单集合中添加独特的元素。 我从 Aron Kerr
找到了这个很棒的作品我按照 Aron Kerr 的示例中的方式处理表单和字段集,并且效果很好。
在我的例子中,我创建了一个表单来插入公司的商店集合。
我的表格
首先我有一个 Application\Form\CompanyStoreForm 和这样的 StoreFieldset:
$this->add(array(
'name' => 'company',
'type' => 'Application\Form\Stores\CompanyStoresFieldset',
));
字段集
Application\Form\Stores\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',
));
与 Arron Kerrs CategoryFieldset 的不同之处在于我添加了一个字段集:Application\Form\Fieldset\AddressFieldset
Application\Form\Fieldset\AddressFieldset 有一个文本元素 streetName.
输入过滤器
CompanyStoresFieldsetInputFilter 没有要验证的元素。
StoreEntityFieldsetInputFilter 有 storeName 和 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 有另一个 Inputfilter AddressFieldsetInputFilter。 在 AddressFieldsetInputFilter 中,我为 streetName.
添加了一个 InputFilterFormFactory
像这样将所有输入过滤器添加到表单
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。
storeName 在整个集合中应该是唯一的。 到目前为止一切正常!
但现在我的问题来了!
streetName 在整个集合中应该是唯一的。 但是元素在 AddressFieldset 中。 我不能做这样的事情:
$storeInputFilter->setUniqueFields(array('storeName', 'address' => array('streetName')));
我想我应该从 CollectionInputFilter
扩展 Aron Kerrs isValid()Aron Kerrs 原创 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()) 时,我收到了一个包含所有值(也是地址字段集)的多维数组。 没关系!但是我无法向字段集中的元素添加错误消息。
我该怎么做? 我不想在 StoreEntityFieldset 中插入 AddressFieldset 的所有元素。 (我在其他表单中也使用了 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');
所以让我解释一下:
现在,我们可以在集合的字段集中添加元素作为唯一元素。 我们不能在我们的集合中添加集合字段集,也不能在我们的字段集中添加另一个字段集。 在我看来,如果有人想做这种情况,他们最好重构表格 :-)
setUniqueFields 添加一个简单的元素作为 unique
array('your-unique-element','another-element');
如果要将元素添加为字段集中的唯一元素
array('your-unique-element', 'fieldsetname' => array('your-unique-element-in-fieldset'))
我们可以使用 setMessage
为每个元素添加特殊消息为集合中的元素添加消息
array('storeName' => 'Just insert one entry...')
为字段集中的元素添加消息
array('fieldset-name' => array('your-unique-element-in-fieldset' => 'You already insert ..'))