如何在ZF2中创建表单输入/元素

时间:2012-09-09 19:22:16

标签: php doctrine-orm zend-framework2

编辑:我的主要问题现在变成了'我如何以一种干净的方式将带有教义实体管理器的ServiceManager放到我的表单,元素和输入类的手中?'继续阅读以查看完整的帖子。

我打算在这里尝试一下,所以请耐心等待。让我知道我哪里出错/正确或我可以改进的地方

我正在尝试创建注册表单。我可以使用ZfcUser模块,但我想自己做。我也在使用带有Doctrine2的ZF2,这让我远离那个模块。

我的策略就是这个,

  1. 创建一个名为注册表单的表单类

  2. 为每个元素创建单独的“元素”类,其中每个元素都有一个输入规范

  3. 由于每个元素都是一个独立的类,我可以单独测试每个元素。

  4. 一切似乎没问题,直到我想在我的用户名元素中添加一个验证器,用于检查用户名是否尚未使用。

    这是迄今为止的代码

    namepsace My\Form;
    
    use Zend\Form\Form,
        Zend\Form\Element,
        Zend\InputFilter\Input,
        Zend\InputFilter\InputFilter,
    
    /**
     * Class name : Registration
     */
    class Registration
        extends Form
    {
    
        const USERNAME     = 'username';
        const EMAIL        = 'email';
        const PASSWORD     = 'password';
        const PASS_CONFIRM = 'passwordConfirm';
        const GENDER       = 'gender';
        const CAPTCHA      = 'captcha';
        const CSRF         = 'csrf';
        const SUBMIT       = 'submit';
    
        private $captcha = 'dumb';
    
        public function prepareForm()
        {
            $this->setName( 'registration' );
    
            $this->setAttributes( array(
                'method' => 'post'
            ) );
    
            $this->add( array(
                'name'       => self::USERNAME,
                'type'       => '\My\Form\Element\UsernameElement',
                'attributes' => array(
                    'label'     => 'Username',
                    'autofocus' => 'autofocus'
                )
                )
            );
    
            $this->add( array(
                'name'       => self::SUBMIT,
                'type'       => '\Zend\Form\Element\Submit',
                'attributes' => array(
                    'value' => 'Submit'
                )
            ) );
    
        }
    
    }
    

    我删除了很多我认为没有必要的东西。这是我的用户名元素。

    namespace My\Form\Registration;
    
    use My\Validator\UsernameNotInUse;
    use Zend\Form\Element\Text,
        Zend\InputFilter\InputProviderInterface,
        Zend\Validator\StringLength,
        Zend\Validator\NotEmpty,
        Zend\I18n\Validator\Alnum;
    
    /**
     *
     */
    class UsernameElement
        extends Text
        implements InputProviderInterface
    {
    
        private $minLength = 3;
        private $maxLength = 128;
    
        public function getInputSpecification()
        {
            return array(
                'name'     => $this->getName(),
                'required' => true,
                'filters'  => array(
                    array( 'name'       => 'StringTrim' )
                ),
                'validators' =>
                array(
                    new NotEmpty(
                        array( 'mesages' =>
                            array(
                                NotEmpty::IS_EMPTY => 'The username you provided is blank.'
                            )
                        )
                    ),
                    new AlNum( array(
                        'messages' => array( Alnum::STRING_EMPTY => 'The username can only contain letters and numbers.' )
                        )
                    ),
                    new StringLength(
                        array(
                            'min'      => $this->getMinLength(),
                            'max'      => $this->getMaxLength(),
                            'messages' =>
                            array(
                                StringLength::TOO_LONG  => 'The username is too long. It cannot be longer than ' . $this->getMaxLength() . ' characters.',
                                StringLength::TOO_SHORT => 'The username is too short. It cannot be shorter than ' . $this->getMinLength() . ' characters.',
                                StringLength::INVALID   => 'The username is not valid.. It has to be between ' . $this->getMinLength() . ' and ' . $this->getMaxLength() . ' characters long.',
                            )
                        )
                    ),
                    array(
                        'name'    => '\My\Validator\UsernameNotInUse',
                        'options' => array(
                            'messages' => array(
                                UsernameNotInUse::ERROR_USERNAME_IN_USE => 'The usarname %value% is already being used by another user.'
                            )
                        )
                    )
                )
            );
        }    
    }
    

    现在这是我的验证器

    namespace My\Validator;
    
    use My\Entity\Helper\User as UserHelper,
        My\EntityRepository\User as UserRepository;
    use Zend\Validator\AbstractValidator,
        Zend\ServiceManager\ServiceManagerAwareInterface,
        Zend\ServiceManager\ServiceLocatorAwareInterface,
        Zend\ServiceManager\ServiceManager;
    
    /**
     *
     */
    class UsernameNotInUse
        extends AbstractValidator
        implements ServiceManagerAwareInterface
    {
    
        const ERROR_USERNAME_IN_USE = 'usernameUsed';
    
        private $serviceManager;
    
        /**
         *
         * @var UserHelper
         */
        private $userHelper;
        protected $messageTemplates = array(
            UsernameNotInUse::ERROR_USERNAME_IN_USE => 'The username you specified is being used already.'
        );
    
        public function isValid( $value )
        {
            $inUse = $this->getUserHelper()->isUsernameInUse( $value );
            if( $inUse )
            {
                $this->error( UsernameNotInUse::ERROR_USERNAME_IN_USE, $value );
            }
    
            return !$inUse;
        }
    
        public function setUserHelper( UserHelper $mapper )
        {
            $this->userHelper = $mapper;
            return $this;
        }
    
        /**
         * @return My\EntityRepository\User
         */
        public function getUserHelper()
        {
            if( $this->userHelper == null )
            {
                $this->setUserHelper( $this->getServiceManager()->get( 'doctrine.entitymanager.orm_default' )->getObjectRepository( 'My\Entity\User') );
            }
            return $this->userHelper;
        }
    
        public function setServiceManager( ServiceManager $serviceManager )
        {
            echo get_class( $serviceManager );
            echo var_dump( $serviceManager );
            $this->serviceManager = $serviceManager;
            return $this;
        }
    
        /**
         *
         * @return ServiceManager
         */
        public function getServiceManager( )
        {
            return $this->serviceManager;
        }
    
    }
    

    为什么这对我来说似乎是一个好主意?

    1. 这似乎是一个很好的可测试性/重用选择,因为如果需要,我可以在我的应用程序中单独重复使用这些元素。

    2. 我可以对每个元素生成的每个输入进行单元测试,以确保它正确接受/拒绝输入。

    3. 这是元素

      的单元测试示例
      public function testFactoryCreation()
      {
          $fac = new Factory();
      
          $element = $fac->createElement( array(
              'type' => '\My\Form\Registration\UsernameElement'
              ) );
          /* @var $element \My\Form\Registration\UsernameElement  */
      
          $this->assertInstanceOf( '\My\Form\Registration\UsernameElement',
                                   $element );
      
          $input      = $fac->getInputFilterFactory()->createInput( $element->getInputSpecification() );
          $validators = $input->getValidatorChain()->getValidators();
          /* @var $validators \Zend\Validator\ValidatorChain */
      
          $expectedValidators = array(
              'Zend\Validator\StringLength',
              'Zend\Validator\NotEmpty',
              'Zend\I18n\Validator\Alnum',
              'My\Validator\UsernameNotInUse'
          );
      
          foreach( $validators as $validator )
          {
              $actualClass = get_class( $validator['instance'] );
              $this->assertContains( $actualClass, $expectedValidators );
      
              switch( $actualClass )
              {
                  case 'My\Validator\UsernameNotInUse':
                      $helper = $validator['instance']->getUserHelper();
                      //HAVING A PROBLEM HERE
                      $this->assertNotNull( $helper );
                      break;
      
                  default:
      
                      break;
              }
          }
      
      }
      

      我遇到的问题是验证器无法正确获取UserHelper,这实际上是来自doctrine的UserRepository。发生这种情况的原因是验证器只能作为ServiceManager访问ValidatorPluginManager,而不是访问应用程序范围的ServiceManager。

      我在Validator部分得到了这个错误,但是如果我在通用服务管理器上调用相同的get方法它没有问题。

      1) Test\My\Form\Registration\UsernameElementTest::testFactoryCreation
      Zend\ServiceManager\Exception\ServiceNotFoundException: Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for doctrine.entitymanager.orm_default
      

      验证器中的var_dump($ serviceManager)显示它属于ValidatorPluginManager类。

      我尝试将工厂放在service_manager条目中,如此

      'service_manager' => array(
                      'factories' => array(
                          'My\Validator\UsernameNotInUse' => function( $sm )
                          {
                              $validator = new \My\Validator\UsernameNotInUse();
                              $em        = $serviceManager->get( 'doctrine.entitymanager.orm_default' );
                              /* @var $em \Doctrine\ORM\EntityManager */
                              $validator->setUserHelper( $em->getRepository( '\My\Entity\User' ) );
      
                              return $validator;
                          }
                      )
      

      但这不起作用,因为它没有咨询应用程序级别的服务管理器。

      总的来说,这是我的问题:

      1. 这种将形式和元素分开的策略是不是很好?我应该继续这样吗?什么是替代品? (我是为了可测试而破坏内容)我原本只用所有输入的组合测试表单本身,但似乎我试图做太多。

        < / LI>
      2. 如何解决上述问题?

      3. 我是否应该以其他方式使用Zend的表单/元素/输入部分?

1 个答案:

答案 0 :(得分:8)

这是我的验证器,使用静态方法注入entityManager并使用任何doctine实体。

<?php

namespace Base\Validator;

use Traversable;
use Zend\Stdlib\ArrayUtils;
use Zend\Validator\AbstractValidator;
use Doctrine\ORM\EntityManager;

class EntityUnique extends AbstractValidator
{
    const EXISTS = 'exists';

    protected $messageTemplates = array(
        self::EXISTS => "A %entity% record already exists with %attribute% %value%",
    );

    protected $messageVariables = array(
        'entity' => '_entity',
        'attribute' => '_attribute',
    );


    protected $_entity;
    protected $_attribute;
    protected $_exclude;

    protected static $_entityManager;

    public static function setEntityManager(EntityManager $em) {

        self::$_entityManager = $em;
    }

    public function getEntityManager() {

        if (!self::$_entityManager) {

            throw new \Exception('No entitymanager present');
        }

        return self::$_entityManager;
    }

    public function __construct($options = null)
    {
        if ($options instanceof Traversable) {
            $options = ArrayUtils::iteratorToArray($token);
        }

        if (is_array($options)) {

            if (array_key_exists('entity', $options)) {

                $this->_entity = $options['entity'];
            }

            if (array_key_exists('attribute', $options)) {

                $this->_attribute = $options['attribute'];
            }

            if (array_key_exists('exclude', $options)) {

                if (!is_array($options['exclude']) ||
                    !array_key_exists('attribute', $options['exclude']) ||
                    !array_key_exists('value', $options['exclude'])) {

                    throw new \Exception('exclude option must contain attribute and value keys');
                }

                $this->_exclude = $options['exclude'];
            }
        }

        parent::__construct(is_array($options) ? $options : null);
    }

    public function isValid($value, $context = null)
    {
        $this->setValue($value);

        $queryBuilder = $this->getEntityManager()
            ->createQueryBuilder()
            ->from($this->_entity, 'e')
            ->select('COUNT(e)')
            ->where('e.'. $this->_attribute . ' = :value')
            ->setParameter('value', $this->getValue());

        if ($this->_exclude) {

            $queryBuilder = $queryBuilder->andWhere('e.'. $this->_exclude['attribute'] . ' != :exclude')
                ->setParameter('exclude', $this->_exclude['value']);
        }

        $query = $queryBuilder->getQuery();        
        if ((integer)$query->getSingleScalarResult() !== 0) {

            $this->error(self::EXISTS);
            return false;
        }

        return true;
    }
}

即。我正在将它用于theese表单元素,这些元素也经过测试并且工作正常:

<?php

namespace User\Form\Element;

use Zend\Form\Element\Text;
use Zend\InputFilter\InputProviderInterface;

class Username extends Text implements InputProviderInterface
{
    public function __construct() {

        parent::__construct('username');
        $this->setLabel('Benutzername');
        $this->setAttribute('id', 'username');
    }

    public function getInputSpecification() {

        return array(
            'name' => $this->getName(),
            'required' => true,
            'filters'  => array(
                array(
                    'name' => 'StringTrim'
                ),
            ),
            'validators' => array(
                array(
                    'name' => 'NotEmpty',
                    'break_chain_on_failure' => true,
                    'options' => array(
                        'messages' => array(
                            'isEmpty' => 'Bitte geben Sie einen Benutzernamen ein.',
                        ),
                    ),
                ),
            ),
        );
    }
}

创建新用户时

<?php

namespace User\Form\Element;

use Zend\InputFilter\InputProviderInterface;
use User\Form\Element\Username;

class CreateUsername extends Username implements InputProviderInterface
{
    public function getInputSpecification() {

        $spec = parent::getInputSpecification();
        $spec['validators'][] = array(
            'name' => 'Base\Validator\EntityUnique',
            'options' => array(
                'message' => 'Der name %value% ist bereits vergeben.',
                'entity' => 'User\Entity\User',
                'attribute' => 'username',  
            ),    
        );

        return $spec;
    }
}

在现有用户中进行编辑时

<?php

namespace User\Form\Element;

use Zend\InputFilter\InputProviderInterface;
use User\Form\Element\Username;

class EditUsername extends Username implements InputProviderInterface
{
    protected $_userId;

    public function __construct($userId) {

        parent::__construct();
        $this->_userId = (integer)$userId;
    }

    public function getInputSpecification() {

        $spec = parent::getInputSpecification();
        $spec['validators'][] = array(
            'name' => 'Base\Validator\EntityUnique',
            'options' => array(
                'message' => 'Der name %value% ist bereits vergeben.',
                'entity' => 'User\Entity\User',
                'attribute' => 'username',
                'exclude' => array(
                    'attribute' => 'id',
                    'value' => $this->_userId,  
                ),
            ),    
        );

        return $spec;
    }
}