Cómo crear entradas / elementos de formulario en ZF2

EDITAR: Mi pregunta principal se ha convertido ahora en "¿Cómo puedo obtener el ServiceManager con el administrador de entidades de doctrina en las manos de mis clases de formulario, elemento e ingreso de alguna manera limpia?" Sigue leyendo para ver el post completo.

Voy a tratar de preguntar con el ejemplo aquí, así que tengan paciencia conmigo. Déjame saber dónde me voy mal / bien o dónde podría mejorar

Estoy tratando de crear un formulario de registro. Podría usar el módulo ZfcUser pero quiero hacerlo por mi cuenta. También estoy usando ZF2 con Doctrine2, así que eso me aleja un poco de ese módulo.

Mi estrategia fue esta,

Crear una clase de formulario llamada formulario de registro

Cree clases de 'elemento' separadas para cada elemento donde cada elemento tendrá una especificación de entrada

Como cada elemento es una clase separada del formulario, puedo probar cada unidad por separado.

Todo parecía estar bien hasta que quise agregar un validador a mi elemento de nombre de usuario que verificara que el nombre de usuario NO esté aún en uso.

Aquí está el código hasta ahora

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

    }

}

He eliminado muchas cosas que creo que no es necesario. Aquí está mi elemento de nombre de usuario a continuación.

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

Ahora aquí está mi validador

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

}

¿Por qué esto me pareció una buena idea?

Parecía una buena elección de prueba / reutilización, ya que podría reutilizar los elementos por separado en mi aplicación si fuera necesario.

Podría hacer una prueba unitaria de cada entrada generada por cada elemento para asegurarme de que acepta / rechaza correctamente la entrada.

Este es el ejemplo de mi prueba de unidad para el elemento.

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

}

El problema que tengo es que el validador no puede obtener el UserHelper correctamente, que es realmente un UserRepository de la doctrina. La razón por la que esto sucede es porque los validadores solo tienen acceso al ValidatorPluginManager como ServiceManager en lugar de tener acceso a la aplicación ServiceManager.

Recibo este error para la parte del Validador, aunque si llamo al mismo método de obtención en el administrador de servicios general, funciona sin problemas.

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

El var_dump ($ serviceManager) en el validador me muestra que es de la clase ValidatorPluginManager.

Intenté poner una fábrica en la entrada service_manager como tal

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

pero eso no funcionó porque no está consultando al administrador de servicios de nivel de aplicación.

Así que, en general, aquí están mis preguntas:

¿Es esta estrategia de separar la forma y los elementos una buena? ¿Debo seguir así? ¿Qué son las alternativas? (Estoy a favor de dividir las cosas por el bien de la verificabilidad) Iba a probar SOLAMENTE el formulario originalmente con una combinación de TODAS las entradas, pero parecía que estaba intentando hacer demasiado.

¿Cómo resuelvo el problema que tengo arriba?

¿Debo usar las partes de Forma / Elemento / Entrada de Zend de alguna otra manera que no esté viendo?

Respuestas a la pregunta(1)

Su respuesta a la pregunta