Jak tworzyć wejścia / elementy formularza w ZF2

EDYCJA: Moje główne pytanie stało się teraz 'Jak mogę dostać ServiceManager z menedżerem encji doktryny do rąk mojej formy, elementu i klas wejściowych w jakiś czysty sposób?' Czytaj dalej, aby zobaczyć pełny post.

Spróbuję tutaj zadać przykład, więc zrób to ze mną. Daj mi znać, gdzie się mylę / dobrze lub gdzie mogę poprawić

Próbuję utworzyć formularz rejestracyjny. Mogę użyć modułu ZfcUser, ale chcę to zrobić sam. Używam również ZF2 z Doctrine2, więc odsuwa mnie to nieco od tego modułu.

Moja strategia była taka,

Utwórz klasę formularza o nazwie formularz rejestracyjny

Utwórz osobne klasy „elementu” dla każdego elementu, gdzie każdy element będzie miał specyfikację wejścia

Ponieważ każdy element jest oddzielną klasą od formularza, mogę przetestować każdy z nich osobno.

Wszystko wydawało się w porządku, dopóki nie chciałem dodać walidatora do elementu mojej nazwy użytkownika, który sprawdzałby, że nazwa użytkownika NIE jest jeszcze używana.

Oto kod do tej pory

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

    }

}

Usunąłem wiele rzeczy, które moim zdaniem nie są konieczne. Oto mój element nazwy użytkownika poniżej.

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

Teraz jest mój walidator

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

}

Dlaczego wydawało mi się to dobrym pomysłem?

Wydawało się, że jest to dobry wybór do testowania / ponownego użycia, ponieważ w razie potrzeby mogę ponownie użyć elementów oddzielnie w mojej aplikacji.

Mogłem przetestować każde wejście wygenerowane przez każdy element, aby upewnić się, że poprawnie akceptuje / odrzuca dane wejściowe.

To jest przykład mojego testu jednostkowego dla elementu

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

}

Problem, który mam, polega na tym, że walidator nie może poprawnie pobrać UserHelpera, który jest naprawdę UserRepository z doktryny. Dzieje się tak, ponieważ walidatory uzyskują dostęp do ValidatorPluginManager tylko jako ServiceManager, zamiast mieć dostęp do szerokiego wachlarza ServiceManager.

Dostaję ten błąd dla części Validator, chociaż jeśli wywołam tę samą metodę get na ogólnym menedżerze usług, działa bez problemów.

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) w walidatorze pokazuje mi, że należy do klasy ValidatorPluginManager.

Próbowałem umieścić fabrykę we wpisie service_manager w ten sposób

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

ale to nie zadziałało, ponieważ nie konsultuje się z menedżerem usług na poziomie aplikacji.

Oto moje pytania:

Czy ta strategia rozdzielenia formy i elementów jest dobra? Czy powinienem iść dalej w ten sposób? Jakie są alternatywy? (Jestem za łamanie rzeczy ze względu na testowalność) Zamierzałem przetestować TYLKO samą formę pierwotnie z kombinacją WSZYSTKICH wejść, ale wydawało się, że próbuję zrobić za dużo.

Jak rozwiązać problem, który mam powyżej?

Czy powinienem używać formularza / elementu / części wejściowych Zend w inny sposób, którego nie widzę?

questionAnswers(1)

yourAnswerToTheQuestion