ZF2/Doctrine2 - 未验证字段集,数据始终有效 [英] ZF2/Doctrine2 - Fieldsets not validated, data is always valid
问题描述
我已经使用抽象类为表单、字段集和输入过滤器设置了一个结构.表单和字段集有工厂,而 InputFilters 是由 FieldsetFactory 在字段集上创建和设置的(使用 MutableCreationOptionsInterface
传递选项)
I've set up a structure using Abstract classes for Forms, Fieldsets and InputFilters. Forms and Fieldsets have Factories while InputFilters are created and set on the Fieldsets by the FieldsetFactory (uses the MutableCreationOptionsInterface
to pass along options)
我遇到的问题是 InputFilters 为表单加载,但不用于验证数据.所有输入都被接受为有效.
The problem I have is that InputFilters are loaded for the Form, but are not used to validate the data. All input is accepted as valid.
例如我有一个带有 name
属性的 Country
实体.Country 的名称必须至少为 3 个字符,最多为 255.当名称为ab"时,它被认为是有效的.
E.g. I have a Country
Entity with a name
property. The name of the Country must be at least 3 chars and max 255. When the name is "ab", it's found to be valid.
在有人问之前:没有抛出错误,数据只是被接受为有效的.
Before someone asks: no error is thrown, the data is just accepted as valid.
过去几天我一直在为这个问题而苦恼,试图找出我犯错的地方,但找不到.
I've been breaking my head over this for the passed few days trying to find where I've made a mistake, but cannot find it.
此外,还有相当多的代码.我将其限制在我认为相关的内容上,尽管:如果您需要更多,就会有更多.我删除了很多类型检查、文档块和类型提示以限制代码行/阅读时间;)
Also, there's quite a bit of code. I've limited it to what I think is relevant, although: if you need more, there will be more. I've removed a lot of type checking, docblocks and type hints to limit code lines/reading time ;)
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
* @ORMColumn(name="name", type="string", length=255, nullable=false)
*/
protected $name;
// Other properties
// Getters/setters
}
CountryController.php - 扩展 AbstractActionController
CountryController.php - Extends the 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()
AbstractActionController.php - (goes wrong in here) - Used by 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('DoctrineORMEntityManager');
$form = new CountryForm($this->name, $this->options);
$form->setObjectManager($entityManager);
$form->setTranslator($serviceManager->get('translator'));
return $form;
}
}
AbstractFormFactory.php - 使用 MutableCreationOptionsInterface
从控制器函数调用接收选项:$form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions))
AbstractFormFactory.php - Uses MutableCreationOptionsInterface
to receive options from the Controller function call: $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 用作基本字段集
CountryFieldset.php - Used above by CountryForm.php as the base fieldset
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('DoctrineORMEntityManager');
/** @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 天,并没有偶然发现出了什么问题.
Any help would very much be appreciated. Hopefully you're not too overloaded with the code above. But I've been mashing this issue back'n'forth for about 3-4 days and haven't stumbled onto what's going wrong.
总结:
在上面创建了一个 CountryForm
.它使用 CountryFieldset
预加载(在 CountryFieldsetFactory
中)和 CountryInputFilter
.
In the above a CountryForm
is created. It uses the CountryFieldset
which gets preloaded (in CountryFieldsetFactory
) with the CountryInputFilter
.
在验证数据时,一切都被认为是有效的.例如.- 国家名称ab"是有效的,尽管 StringLength
验证器有 'min' =>3、
定义为选项.
When it comes to validating the data, everything is accepted as valid. E.g. - Country name "ab" is valid, though StringLength
validator has 'min' => 3,
defined as option.
推荐答案
既然你已经设置了所有的类,那么还有另一种方法(来自@AlexP),通过构造字段集的 InputFilters 并将其添加到表单中输入过滤器.而不是使用 InputFilterSpecifications.
Since you've got all the classes set up already there is another approach (from @AlexP), by constructing and adding the InputFilters of the Fieldsets to the Forms InputFilter. Instead of using the InputFilterSpecifications.
因此将输入过滤器添加到您的 input_filters
配置键:
So add the input filters to your input_filters
config key:
'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('DoctrineORMEntityManager'),
$serviceManager()->get('translator')
);
return $inputFilter;
}
}
class CountryFieldsetInputFilterFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
return new CountryFieldsetInputFilter(
$serviceManager()->get('DoctrineORMEntityManager'),
$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('DoctrineORMEntityManager'));
$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,
],
]);
}
}
输入过滤器:
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,而不是包含实例的数组.
Note that I injected the dependencies to the InputFilters per argument by instance instead of an array holding the instances.
这篇关于ZF2/Doctrine2 - 未验证字段集,数据始终有效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!