11

編集:私の主な質問は、「教義エンティティマネージャーを備えたServiceManagerをフォーム、要素、および入力クラスにクリーンな方法で渡すにはどうすればよいですか?」になりました。投稿全体を見るには読んでください。

ここで例を挙げて質問しますので、ご容赦ください。どこが間違っている/正しいのか、どこを改善できるのか教えてください

登録フォームを作成しようとしています。ZfcUserモジュールを使用することもできますが、これを自分で実行したいと思います。Doctrine2でZF2も使用しているので、そのモジュールから少し離れています。

私の戦略はこれでした、

  1. 登録フォームと呼ばれるフォームクラスを作成します

  2. 要素ごとに個別の「要素」クラスを作成します。各要素には入力仕様があります。

  3. 各要素はフォームとは別のクラスであるため、それぞれを個別に単体テストできます。

ユーザー名がまだ使用されていないことを確認するバリデーターをユーザー名要素に追加するまでは、すべて問題ないように見えました。

これまでのコードは次のとおりです

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

    }

}

必要ないと思うものをたくさん削除しました。これが私のusername要素です。

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. 各要素によって生成された各入力を単体テストして、入力を正しく受け入れる/拒否することを確認できます。

これは、要素の単体テストの例です

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を正しくフェッチできないことです。これは、実際には教義からのUserRepositoryです。これが発生する理由は、バリデーターがアプリケーション全体のServiceManagerにアクセスするのではなく、ServiceManagerとしてValidatorPluginManagerにのみアクセスできるためです。

バリデーター部分でこのエラーが発生しますが、一般的なサービスマネージャーで同じ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. フォームと要素を分離するこの戦略は良いものですか?私はこのように続けるべきですか?代替手段は何ですか?(私はテスト容易性のためにものを分割するためのものです)私は元々すべての入力の組み合わせでフォーム自体だけをテストするつもりでしたが、私はやりすぎのようでした。

  2. 上記の問題を解決するにはどうすればよいですか?

  3. Zendのフォーム/要素/入力部分を、表示されていない他の方法で使用する必要がありますか?

4

1 に答える 1

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

すなわち。私はこれをこれらのフォーム要素にも使用しています。これらのフォーム要素もテストされ、正常に機能しています。

<?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;
    }
}
于 2012-09-13T19:34:03.557 に答える