Как создать входы / элементы формы в ZF2

РЕДАКТИРОВАТЬ: мой главный вопрос теперь сталКак мне каким-то чистым способом передать ServiceManager с менеджером сущностей doctrine в мои классы формы, элемента и ввода? Читайте дальше, чтобы увидеть полный пост.

Я собираюсь попробовать и спросить примером здесь, так что терпите меня. Дайте мне знать, где яя иду не так, как надо или где я мог бы улучшить

Я пытаюсь создать регистрационную форму. Я мог бы использовать модуль ZfcUser, но я хочу сделать это самостоятельно. Я'Я также использую ZF2 с Doctrine2, что немного уводит меня от этого модуля.

Моя стратегия была такая,

Создайте класс формы под названием форма регистрации

Создать отдельный роликэлемент' классы для каждого элемента, где каждый элемент будет иметь входную спецификацию

Поскольку каждый элемент является отдельным классом от формы, я могу тестировать каждый из них отдельно.

Все казалось нормальным, пока я не хотел добавить в свой элемент имени пользователя валидатор, который бы проверял, что имя пользователя НЕ используется.

Вот код на данный момент

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

}

Почему это показалось мне хорошей идеей?

Это казалось хорошим выбором для тестируемости / повторного использования, поскольку в случае необходимости я мог бы повторно использовать элементы отдельно в своем приложении.

Я мог бы модульно протестировать каждый вход, генерируемый каждым элементом, чтобы убедиться, что он правильно принимает / отклоняет ввод.

Это пример моего юнит-теста для элемента

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 из доктрины. Причина этого заключается в том, что валидаторы получают доступ к ValidatorPluginManager только как ServiceManager, а не имеют доступ к 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;
                    }
                )

но это нене работает, потому что этоне консультируется с менеджером службы прикладного уровня.

Итак, в целом, вот мои вопросы:

Является ли эта стратегия разделения формы и элементов хорошей? Должен ли я продолжать идти по этому пути? Какие есть альтернативы? (Ям, чтобы разбить вещи ради тестируемости) Я собирался протестировать ТОЛЬКО саму форму первоначально с комбинацией ВСЕХ входов, но казалось, что ябуду пытаться сделать слишком много.

Как решить проблему, описанную выше?

Должен ли я использовать части формы / элемента / ввода Zend каким-то другим способом, которым яя не вижу?

Ответы на вопрос(1)

Ваш ответ на вопрос