ZF2/Doctrine2 - наборы полей не проверены, данные всегда действительны
Я создал структуру, используя Абстрактные классы для Forms, Fieldsets и InputFilters. Формы и Наборы полей имеют Фабрики, в то время как InputFilters создаются и устанавливаются на Наборы полей FieldsetFactory (использует MutableCreationOptionsInterface
передать варианты)
У меня проблема в том, что InputFilters загружаются для формы, но не используются для проверки данных. Все входные данные принимаются как действительные.
Например, у меня есть Country
Сущность с name
имущество. Название страны должно быть не менее 3 символов и не более 255. Если имя "ab", оно считается действительным.
Прежде чем кто-то спросит: не выдается никакой ошибки, данные просто принимаются как действительные.
Последние несколько дней я ломал голову над этим, пытаясь найти, где я совершил ошибку, но не могу ее найти.
Кроме того, есть довольно много кода. Я ограничил это тем, что считаю уместным, хотя: если вам нужно больше, будет больше. Я удалил много проверок типов, docblocks и подсказок типа, чтобы ограничить строки кода / время чтения;)
module.config.php
'form_elements' => [
'factories' => [
CountryForm::class => CountryFormFactory::class,
CountryFieldset::class => CountryFieldsetFactory::class,
],
],
Country.php
class Country extends AbstractEntity // Creates $id + getter/setter
{
/**
* @var string
* @ORM\Column(name="name", type="string", length=255, nullable=false)
*/
protected $name;
// Other properties
// Getters/setters
}
CountryController.php - расширяет AbstractActionController
public function editAction() // Here to show the params used to call function
{
return parent::editAction(
Country::class,
CountryForm::class,
[
'name' => 'country',
'options' => []
],
'id',
'admin/countries/view',
'admin/countries',
['id']
);
}
AbstractActionController.php - (здесь не так) - используется CountryController # editAction ()
public function editAction (
$emEntity,
$formName,
$formOptions,
$idProperty,
$route,
$errorRoute, array
$routeParams = []
) {
//Check if form is set
$id = $this->params()->fromRoute($idProperty, null);
/** @var AbstractEntity $entity */
$entity = $this->getEntityManager()->getRepository($emEntity)->find($id);
/** @var AbstractForm $form */
$form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions));
$form->bind($entity);
/** @var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) { // HERE IS WHERE IT GOES WRONG -> ALL IS TRUE
try {
$this->getEntityManager()->flush();
} catch (\Exception $e) {
//Print errors & return (removed, unnecessary)
}
return $this->redirectToRoute($route, $this->getRouteParams($entity, $routeParams));
}
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
CountryForm.php
class CountryForm extends AbstractForm
{
// This one added for SO, does nothing but call parent#__construct, which would happen anyway
public function __construct($name = null, array $options)
{
parent::__construct($name, $options);
}
public function init()
{
//Call parent initializer.
parent::init();
$this->add([
'name' => 'country',
'type' => CountryFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
}
}
CountryFormFactory.php
class CountryFormFactory extends AbstractFormFactory
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
/** @var EntityManager $entityManager */
$entityManager = $serviceManager->get('Doctrine\ORM\EntityManager');
$form = new CountryForm($this->name, $this->options);
$form->setObjectManager($entityManager);
$form->setTranslator($serviceManager->get('translator'));
return $form;
}
}
AbstractFormFactory.php - Использование MutableCreationOptionsInterface
получить опции от вызова функции Controller: $form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions))
abstract class AbstractFormFactory implements FactoryInterface, MutableCreationOptionsInterface
{
protected $name;
protected $options;
/**
* @param array $options
*/
public function setCreationOptions(array $options)
{
// Check presence of required "name" (string) parameter in $options
$this->name = $options['name'];
// Check presence of required "options" (array) parameter in $options
$this->options = $options['options'];
}
}
CountryFieldset.php - используется выше CountryForm.php в качестве базового набора полей
class CountryFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Name'),
],
]);
// Other properties
}
}
AbstractFieldset.php
abstract class AbstractFieldset extends Fieldset
{
use InputFilterAwareTrait;
use TranslatorAwareTrait;
protected $entityManager;
public function __construct(EntityManager $entityManager, $name)
{
parent::__construct($name);
$this->setEntityManager($entityManager);
}
public function init()
{
$this->add([
'name' => 'id',
'type' => Hidden::class,
]);
}
// Getters/setters for $entityManager
}
CountryFieldsetFactory.php - ЗДЕСЬ ВХОДНОЙ ФИЛЬТР НАСТРОЕН НА ПОЛЕ
class CountryFieldsetFactory extends AbstractFieldsetFactory
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
parent::createService($serviceLocator);
/** @var CountryRepository $entityRepository */
$entityRepository = $this->getEntityManager()->getRepository(Country::class);
$fieldset = new CountryFieldset($this->getEntityManager(), $this->name);
$fieldset->setHydrator(new DoctrineObject($this->getServiceManager()->get('doctrine.entitymanager.orm_default'), false));
$fieldset->setObject(new Country());
$fieldset->setTranslator($this->getTranslator());
// HERE THE INPUTFILTER IS SET ONTO THE FIELDSET THAT WAS JUST CREATED
$fieldset->setInputFilter(
$this->getServiceManager()->get('InputFilterManager')->get(
CountryInputFilter::class,
[ // These are the options read by the MutableCreationOptionsInterface
'object_manager' => $this->getEntityManager(),
'object_repository' => $entityRepository,
'translator' => $this->getTranslator(),
]
)
);
return $fieldset;
}
}
AbstractFieldsetFactory.php
abstract class AbstractFieldsetFactory implements FactoryInterface, MutableCreationOptionsInterface
{
protected $serviceManager;
protected $entityManager;
protected $translator;
protected $name;
public function setCreationOptions(array $options)
{
$this->name = $options['name'];
}
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** @var ServiceLocator $serviceManager */
$this->serviceManager = $serviceLocator->getServiceLocator();
/** @var EntityManager $entityManager */
$this->entityManager = $this->getServiceManager()->get('Doctrine\ORM\EntityManager');
/** @var Translator $translator */
$this->translator = $this->getServiceManager()->get('translator');
}
// Getters/setters for properties
}
CountryFieldsetInputFilter.php
class CountryInputFilter extends AbstractInputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3, // This is just completely ignored
'max' => 255,
],
],
],
]);
// More adding
}
}
AbstractFieldsetInputFilter.php - Последний!:)
abstract class AbstractInputFilter extends InputFilter
{
use TranslatorAwareTrait;
protected $repository;
protected $objectManager;
public function __construct(array $options)
{
// Check if ObjectManager|EntityManager for InputFilter is set
$this->setObjectManager($options['object_manager']);
// Check if EntityRepository instance for InputFilter is set
$this->setRepository($options['object_repository']);
// Check for presence of translator so as to translate return messages
$this->setTranslator($options['translator']);
}
public function init()
{
$this->add([
'name' => 'id',
'required' => false,
'filters' => [
['name' => ToInt::class],
],
'validators' => [
['name' => IsInt::class],
],
]);
}
//Getters/setters for properties
}
Любая помощь будет очень цениться. Надеюсь, вы не слишком перегружены приведенным выше кодом. Но я занимался этой проблемой около 3-4 дней назад и не сталкивался с тем, что происходит не так.
Подводить итоги:
В приведенном выше CountryForm
создано. Он использует CountryFieldset
который загружается предварительно (в CountryFieldsetFactory
) с CountryInputFilter
,
Когда дело доходит до проверки данных, все считается действительным. Например, название страны "ab" действительно, хотя StringLength
валидатор имеет 'min' => 3,
определяется как опция.
2 ответа
Поскольку у вас уже настроены все классы, существует другой подход (из @AlexP) - создание и добавление InputFilters наборов полей в Forms InputFilter. Вместо использования InputFilterSpecifications.
Так что добавьте входные фильтры к вашему input_filters
ключ конфигурации:
'form_elements' => [
'factories' => [
CountryForm::class => CountryFormFactory::class,
CountryFieldset::class => CountryFieldsetFactory::class,
],
],
'input_filters' => [
'factories' => [
CountryInputFilter::class => CountryInputFilterFactory::class,
CountryFieldsetInputFilter::class => CountryFieldsetInputFilterFactory::class,
],
],
Фабричные классы:
class CountryInputFilterFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$inputFilter = new CountryInputFilter(
$serviceLocator->get(CountryFieldsetInputFilter::class),
$serviceManager()->get('Doctrine\ORM\EntityManager'),
$serviceManager()->get('translator')
);
return $inputFilter;
}
}
class CountryFieldsetInputFilterFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
return new CountryFieldsetInputFilter(
$serviceManager()->get('Doctrine\ORM\EntityManager'),
$serviceManager()->get('translator')
);
}
}
class CountryFormFactory implements AbstractFormFactory
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$form = new CountryForm($this->name, $this->options);
$form->setObjectManager($serviceManager->get('Doctrine\ORM\EntityManager'));
$form->setTranslator($serviceManager->get('translator'));
$form->setInputFilter($serviceManager->get('InputFilterManager')->get(CountryInputFilterFactory::class));
return $form;
}
}
Форма:
class CountryForm extends AbstractForm
{
const COUNTRY_FIELDSET_NAME = 'country';
// This one added for SO, does nothing but call parent#__construct, which would happen anyway
public function __construct($name = null, array $options)
{
parent::__construct($name, $options);
}
public function init()
{
//Call parent initializer.
parent::init();
$this->add([
'name' => self::COUNTRY_FIELDSET_NAME,
'type' => CountryFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
}
}
InputFilters:
class CountryInputFilter extends AbstractInputFilter
{
/** @var CountryFieldsetInputFilter */
protected $countryFieldsetInputFilter;
public function __construct(CountryFieldsetInputFilter $filter, $objectManager, $translator)
{
$this->countryFieldsetInputFilter = $filter;
// other dependencies
}
public function init()
{
$this->add($this->countryFieldsetInputFilter, CountryForm::COUNTRY_FIELDSET_NAME);
}
}
class CountryFieldsetInputFilter extends AbstractInputFilter
{
public function __construct($objectManager, $translator)
{
// dependencies
}
public function init()
{
$this->add([
// configuration
]);
}
}
Обратите внимание, что я вставил зависимости в InputFilters для каждого аргумента по экземпляру, а не в массив, содержащий экземпляры.
$fieldset->setInputFilter($inputFilter);
При условии, что $fieldset
это пример Zend\Form\Fieldset
, метод не существует. Это потому, что вам нужно установить фильтр ввода в форме (Zend\Form\Form)
,
Если бы я изменил ваш код, я сделал бы это следующим образом. Изменить CountryForm
предоставить входной фильтр через форму. Это может быть сделано без необходимости определять пользовательский фильтр ввода для каждой формы, используя Zend\InputFilter\InputFilterProviderInterface
и ссылки на ваш пользовательский фильтр ввода под type
ключ.
Когда менеджер элемента формы создает форму, он также внедрит менеджер входного фильтра, который сможет найти пользовательский CountryInputFilter
,
Например:
use Zend\InputFilter\InputFilterProviderInterface;
class CountryForm extends AbstractForm implements InputFilterProviderInterface
{
public function init()
{
//.. add the country fieldset here
}
/**
* getInputFilterSpecification
*
* Return the form's input filter specification array.
*
* @return array
*/
public function getInputFilterSpecification()
{
return [
// Refers to the country element (happens to be a fieldset)
'country' => [
// Tell the input filter factory what type of input filter.
'type' => 'Country\\InputFilter\\CountryInputFilter',
// You could even overload/change the default defined in the input filter
// here. This is useful as sometimes the requirements of the FORM are
// different from what the FIELDSET expects.
// [
// 'name' => 'name',
// 'required' => false,
// 'allow_empty' => true,
// ],
],
];
}
}