So erstellen Sie Formulareingaben / -elemente in ZF2

BEARBEITEN: Meine Hauptfrage lautet nun: "Wie kann ich den ServiceManager mit dem Doctrine-Entity-Manager auf saubere Weise in die Hände meiner Formular-, Element- und Eingabe-Klassen legen?" Lesen Sie weiter, um den vollständigen Beitrag zu sehen.

Ich werde versuchen, hier mit gutem Beispiel voranzugehen. Lassen Sie mich wissen, wo ich falsch / richtig gehe oder wo ich mich verbessern könnte

Ich versuche ein Registrierungsformular zu erstellen. Ich könnte das ZfcUser-Modul verwenden, aber ich möchte dies alleine tun. Ich verwende ZF2 auch mit Doctrine2, so dass ich ein bisschen von diesem Modul wegfahre.

Meine Strategie war dies,

Erstellen Sie eine Formularklasse namens Registrierungsformular

Erstellen Sie separate 'Element'-Klassen für jedes Element, wobei jedes Element eine Eingabespezifikation hat

Da jedes Element eine andere Klasse als das Formular ist, kann ich jedes Element einzeln testen.

Alles schien in Ordnung zu sein, bis ich meinem username-Element einen Validator hinzufügen wollte, der überprüft, ob der Benutzername noch NICHT verwendet wird.

Hier ist der Code soweit

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

    }

}

Ich habe eine Menge entfernt, die ich für nicht notwendig halte. Hier ist mein Benutzername-Element unten.

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

Jetzt ist hier mein Prüfer

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

}

Warum schien mir das eine gute Idee zu sein?

Es schien eine gute Wahl für die Testbarkeit / Wiederverwendung zu sein, da ich die Elemente bei Bedarf in meiner Anwendung separat wiederverwenden konnte.

Ich könnte jede Eingabe, die von jedem Element generiert wird, einzeln testen, um sicherzustellen, dass sie Eingaben korrekt akzeptiert / ablehnt.

Dies ist das Beispiel meines Komponententests für das Element

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

}

Das Problem, das ich habe, ist, dass der Validator den UserHelper nicht richtig abrufen kann, was wirklich ein UserRepository aus der Doktrin ist. Der Grund dafür ist, dass die Validatoren nur als ServiceManager auf den ValidatorPluginManager zugreifen können, anstatt auf den anwendungsweiten ServiceManager zuzugreifen.

Ich erhalte diesen Fehler für den Validator-Teil, obwohl er ohne Probleme funktioniert, wenn ich dieselbe get-Methode für den General Service Manager aufrufe.

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

Der var_dump ($ serviceManager) im Validator zeigt an, dass es sich um die Klasse ValidatorPluginManager handelt.

Ich habe versucht, eine Factory wie folgt in den service_manager-Eintrag einzufügen

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

Dies hat jedoch nicht funktioniert, da der Service Manager auf Anwendungsebene nicht konsultiert wird.

Insgesamt sind hier meine Fragen:

Ist diese Strategie der Trennung von Form und Elementen eine gute? Soll ich so weitermachen? Was sind Alternativen? Ich wollte NUR das Formular selbst mit einer Kombination ALLER Eingaben testen, aber es schien, als würde ich versuchen, zu viel zu tun.

Wie löse ich das oben genannte Problem?

Sollte ich die Form / Element / Input-Teile von Zend auf eine andere Weise verwenden, die ich nicht sehe?

Antworten auf die Frage(1)

Ihre Antwort auf die Frage