zf2表单集合验证 - 字段集中的唯一元素

时间:2015-05-21 14:13:24

标签: php zend-framework2 zend-form zend-inputfilter

我想在Zend表单集合中添加唯一元素。 我从Aron Kerr

找到了这个很棒的作品

我在Aron Kerr的例子中执行表单和字段集,它工作正常。

在我的情况下,我创建一个表格来插入公司的商店集合。

我的表格

首先,我有一个带有StoreFieldset的 Application \ Form \ CompanyStoreForm ,如下所示:

$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',
                ),
            ),
        ));

应用程序\表格\字段集\ 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 有一个text-element 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 在整个集合中应该是唯一的。 但是Element在AddressFieldset中。 我不能做这样的事情:

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

我认为我应该从 CollectionInputFilter 扩展Aron Kerrs isValid()

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;
}
}

首先,我尝试(仅用于测试)在集合的第一个条目中向streetName添加错误消息。

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

但它没有用。

将其添加到storeName可以正常工作

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

我认为原因是Fieldset有自己的InputFilter()?

当我执行 var_dump($ this-&gt; collectionValues())时,我收到了所有值的多维数组(也是addressFieldset的数组)。 没关系!但我无法将错误消息添加到字段集中的元素。

我该怎么做? 我不想在StoreEntityFieldset中插入AddressFieldset的所有元素。 (我也在其他表格中使用AddressFieldset)

1 个答案:

答案 0 :(得分:1)

我明白了。您只需使用

添加值即可
$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 添加一个简单的元素作为唯一

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 ..'))