NoRecordExists输入筛选器


NoRecordExists inputfilter

我有一个字段集,用于"添加"answers"编辑"窗体。

字段集实现InputFilterProviderInterface以提供其验证。

在验证添加操作时,我需要检查数据库中是否存在具有相同值的数据库记录,因此我使用NoRecordExists验证器。

到目前为止一切都很好。但是,当我在编辑表单中使用相同的字段集时,验证将失败,因为显然已经有一个具有特定值的记录,即正在编辑的记录。

因此,我转到NoRecordExists验证器的exclude选项,并排除我正在编辑的记录的带有"id"(这是我的主键字段(的记录。

所以我已经快到了,唯一我不能解决的是如何在getInputFilterSpecification中创建输入过滤器时获得我想要排除的"id"值。

这是我的字段集代码。如果有人能告诉我如何从getInputFilterSpecification中访问表单(或绑定对象(的其他属性,我将不胜感激。

也许我需要以不同的方式实现我的imputfilter才能做到这一点?或者甚至实现一个自定义验证器?但是,对于一个看起来很常见的用例来说,定制验证器肯定是过于夸张了。。。

非常感谢。:wq

<?php
namespace Kickoff'Form'Competition;
use Kickoff'Form'AbstractFieldset,
    Kickoff'Model'Entities'Competition,
    DoctrineModule'Stdlib'Hydrator'DoctrineObject as DoctrineHydrator,
    Zend'InputFilter'InputFilterProviderInterface;

class CompetitionFieldset extends AbstractFieldset implements InputFilterProviderInterface
{
    public function init()
    {
        $this->setName('Competition')
            ->setHydrator(new DoctrineHydrator($this->getObjectManager(),'Kickoff'Model'Entities'Competition'))
            ->setObject(new Competition())
            ->setLabel('Competition')
            ->setAttribute('class','form-collection');
        $this->add(array(
            'type' => 'Zend'Form'Element'Hidden',
            'name' => 'id',
        ));
        $this->add(array(
            'name' => 'name',
            'options' => array(
                'label' => 'Competition name',
                'admin_inline' => true,
            ),
        ));
        $this->add(array(
            'name' => 'long_name',
            'options' => array(
                'label' => 'Competition long name',
                'admin_inline' => true,
            ),
            'attributes' => array(
                'class' => 'input-xxlarge',
            ),
        ));
        $this->add(array(
            'type' => 'Zend'Form'Element'Collection',
            'name' => 'leagues',
            'options' => array(
                'label' => 'Leagues',
                'count' => 0,
                'should_create_template' => true,
                'allow_add' => true,
                'target_element' => array(
                    'type' => 'LeagueFieldset',
                ),
            ),
        ));
    }

    /**
     * Implement InputFilterProviderInterface
     */
    public function getInputFilterSpecification()
    {
        return array(
            'name' => array(
                'filters'  => array(
                    array('name' => 'Zend'Filter'StringTrim'),
                ),
                'validators' => array(
                    'notempty' => array(
                        'name' => 'NotEmpty',
                        'break_chain_on_failure' => true,
                        'options' => array(
                            'messages' => array('isEmpty' => 'Competition name is required.',),
                        ),
                    ),
                    'length' => array(
                        'name' => 'StringLength',
                        'options' => array(
                            'max' => '64',
                            'messages' => array(
                                'stringLengthTooLong' => 'Competition name must be no more than 64 characters.',
                            ),
                        ),
                    ),
                    'unique' => array(
                        'name' => 'Db'NoRecordExists',
                        'options' => array(
                            'table' => 'competition',
                            'field' => 'name',
                            'adapter' => $this->serviceManager->getServiceLocator()->get('db'),
                            'exclude' => array(
                                'field' => 'id',
                                'value' => '',
                            ),
                            'messages' => array(
                                'recordFound' => 'A competition already exists with this name',
                            ),
                        ),
                    ),
                ),
            ),
            'long_name' => array(
                'filters'  => array(
                    array('name' => 'Zend'Filter'StringTrim'),
                ),
                'validators' => array(
                    'length' => array(
                        'name' => 'StringLength',
                        'options' => array(
                            'max' => '128',
                            'messages' => array(
                                'stringLengthTooLong' => 'Competition long name must be no more than 128 characters.',
                            ),
                        ),
                    ),
                ),
            ),
        );
    }
}

编辑:将我的"编辑"控制器操作添加到此帖子:

public function editCompetitionAction()
{
    $id = $this->params()->fromRoute('competition_id');
    $repository = $this->getEntityManager()->getRepository('Kickoff'Model'Entities'Competition');
    $competition = $repository->find($id);
    if (null == $competition) {
        $this->getResponse()->setStatusCode(404);
        return;
    }
    $formManager = $this->serviceLocator->get('FormElementManager');
    $form = $formManager->get('Kickoff'Form'Competition'CompetitionForm');
    $form->bind($competition);
    $request = $this->getRequest();
    if ($request->isPost()) {
        $form->setData($request->getPost());
        $this->logger->debug("Validator is ".print_r($form->getValidator(),1));
        if ($form->isValid()) {
            $this->getEntityManager()->persist($competition);
            $this->getEntityManager()->flush();
        }
    }
    return array(
        'form' => $form,
    );
}

在InputFilter之外检查该条件会更简单。

如果您这样做,那么您可以使用相同的表单进行更新和插入。

您可以a(使用单独的操作来更新和插入(CRUD(,或者b(如果您希望他们在某种条件下更新/插入,请执行类似的操作

// form validates for update or insert now...
if($form->isValid()) {
    if($mapper->exists($object)) {
        $mapper->update($object);
    }
    else {
        $mapper->save($object);
    }
}

在这里我找到了添加操作编辑操作的解决方案

控制器:

addAction:

            $postData = $this->request->getPost ();
            $dbAdapter = $this->getServiceLocator()->get('Zend'Db'Adapter'Adapter');
            $form->setInputFilter(new FormFilter($dbAdapter));
            $form->setData ($postData);
            if (!$form->isValid ()) {
                $viewModel->error = true;
                return $viewModel;
            } 

editAction:

            $post = $request->getPost();
            $dbAdapter = $this->getServiceLocator()->get('Zend'Db'Adapter'Adapter');                
            $form->setInputFilter(new FormFilter($dbAdapter,$Id));
            $form->setData ($post);
            $Id = $post['id'];
            if (!$form->isValid ()) {
                    $viewModel->error = true;
                    $viewModel->Id = $Id;
                    $viewModel->form = $form;
                    return $viewModel;
            }

表单过滤器文件中:

class FormFilter extends InputFilter {
    public function __construct ($dbAdapter, $id = '')
    {
        $this->dbAdapter = $dbAdapter;
        $this->add(array(
                'name'       => 'name',
                'required'   => true,
                'filters'  => array(
                        array('name' => 'StripTags'),
                        array('name' => 'StringTrim'),
                ),
                'validators' => array(
                        array(
                                'name'    => 'StringLength',
                                'options' => array(
                                        'encoding' => 'UTF-8'
                                ),
                        ),
                        array(
                                'name'    => 'Zend'Validator'Db'NoRecordExists',
                                'options' => array(
                                        'table' => 'test',
                                        'field' => 'name',
                                        'adapter' => $this->dbAdapter,
                                        'exclude' => array(
                                                'field' => 'id',
                                                'value' => $id,
                                        ),
                                        'messages' => array(
                                                'Zend'Validator'Db'NoRecordExists::ERROR_RECORD_FOUND => 'The specified name already exists in database'
                                        ),
                                ),
                        ),
                ),
        ));
    }
}   

在这种情况下使用此验证器是错误的。

结果:验证器向数据库发送一个SELECT查询。如果它发现了什么,它将报告"无效"。

如果它没有找到什么,它会报告"有效",但如果同时第二个请求也做了同样的事情,并且也得到了"有效"。谁赢了?一个查询的失败是如何处理的,因为显然您想将输入写入数据库?

这被称为TOCTOU问题。向数据库写入唯一记录只能通过尝试插入新记录并等待数据库投诉非唯一索引冲突来完成。这是写入操作的预期结果,可以处理。

验证器并非毫无用处:您仍然可以使用它来检查数据库中是否存在问题,例如,在Ajax查询中,用户正在填写用户名等。从数据库中检查并获取布尔值仅用于读取是完全可以的。但作为输入验证器,这是错误的。

这是我的工作解决方案:

$inputFilter->add($factory->createInput(array(
            'name'     => 'role_name',
            'required' => true,
            'filters'  => array(
                array('name' => 'StripTags')
            ),
            'validators' => array(
                array(
                    'name'    => 'StringLength',
                    'options' => array(
                        'encoding' => 'UTF-8',
                        'min'      => 2,
                        'max'     => 15
                    ),
                ),
                array(
                    'name'    => 'Zend'Validator'Db'NoRecordExists',
                    'options' => array(
                        'table' => 'user_role',
                        'field' => 'code',
                        'adapter' => 'Zend'Db'TableGateway'Feature'GlobalAdapterFeature::getStaticAdapter(),
                        'messages' => array(
                            'Zend'Validator'Db'NoRecordExists::ERROR_RECORD_FOUND => 'The specified name already exists in database' 
                        ),
                    ),
                ),
            ),
        )));

我找到了这个问题的解决方案。基本上,默认的NoRecordExists验证器希望在配置参数中排除该值和列。这可以在控制器中进行更改,如Ritesh所述;我玩了一段时间,得到了这个解决方案。

我使用的是isValid函数中提供的上下文数组变量。不是发送id值,而是发送表单字段的值以从中提取

在InputFilter中,您有以下

$this->add ( array (
    'name'  => 'user_email',
    'required' => true,
    'filters' => array (
        array ( 
            'name' => 'StringTrim',
        ),
        array ( 
            'name' => 'StripTags',
        ),
    ),
    'validators' => array (
        array (
            'name' => 'EmailAddress',
            'options' => array (
                'domain' => true,
            )
        ),
        array (
            'name' => 'Application'Validator'NoRecordExists',
            'options' => array (
                'table' => 'user',
                'field' => 'user_email',
                'adapter' => 'Zend'Db'TableGateway'Feature'GlobalAdapterFeature::getStaticAdapter( ),
                'exclude' => array(
                    'field' => 'user_id',
                    'formvalue' => 'user_id',
                ),
            )
        ),
    )
) );

表单中定义了一个隐藏元素user_id;在检查时使用了那里设置的值

<?php
namespace Application'Validator;
class NoRecordExists extends 'Zend'Validator'Db'NoRecordExists {
    public function isValid( $value, $context=array( ) ) {
        $exclude = $this->getExclude( );
        if( is_array( $exclude ) ){
            if ( array_key_exists( 'formvalue', $exclude ) ) {
                $formvalue = $exclude[ 'formvalue' ];
                $exclude[ 'value' ] = $context[ $formvalue ];
                $this->setExclude( $exclude );
            }
        }
        return parent::isValid( $value );
    }
}

希望这能帮助