Как создать входы/элементы формы в ZF2
EDIT: теперь мой главный вопрос: "Как я могу получить ServiceManager с менеджером сущностей доктрины в руки моего класса, элемента и классов ввода каким-то чистым способом?" Читайте дальше, чтобы увидеть полную запись.
Я собираюсь попробовать и попросить пример здесь, так что медведь со мной. Сообщите мне, где я ошибаюсь/прав, или где я мог бы улучшить
Я пытаюсь создать регистрационную форму. Я мог бы использовать модуль ZfcUser, но я хочу сделать это самостоятельно. Я также использую ZF2 с Doctrine2, чтобы немного отвести меня от этого модуля.
Моя стратегия была такой,
-
Создайте класс формы, называемый регистрационной формой
-
Создайте отдельные классы элементов для каждого элемента, где каждый элемент будет иметь спецификацию ввода
-
Поскольку каждый элемент является отдельным классом из формы, я могу unit test каждый отдельно.
Все казалось прекрасным, пока я не захотел добавить валидатор к элементу имени пользователя, который будет проверять, что имя пользователя НЕ используется.
Вот код до сих пор
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;
}
}
Почему это показалось мне хорошей идеей?
-
Это казалось хорошим выбором для проверки/повторного использования, поскольку я мог бы повторно использовать элементы отдельно в моем приложении, если это необходимо.
-
Я мог unit test каждый ввод, сгенерированный каждым элементом, чтобы убедиться, что он правильно принимает/отклоняет ввод.
Это пример моего unit test для элемента
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.
Я попытался помещать factory в запись 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
это мой валидатор, используя статический метод для ввода 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;
}
}