ZF2 + Doctrine2 - Fieldset 中集合的 Fieldset 未正确验证
ZF2 + Doctrine2 - Fieldset in Fieldset of a Collection in Fieldset does not validate properly
我刚才问了一个 ,这归结为表单、字段集和输入过滤器的结构。
我一直在彻底应用关注点分离原则将 Fieldsets 与 InputFilters 分开,因为创建它们的模块也将在 API(基于 Apigility)中使用,所以我会只需要实体和输入过滤器。
但是,我现在有一个问题,当我有一个由 Fieldset 使用的 Fieldset,用于 Fieldset 中的 Collection 时,最里面的 Fieldset 没有验证。
让我用例子和代码来详细说明!
情况是我希望能够创建一个Location
。 Location
由 属性 name
和 OneToMany ArrayCollection|Address[]
关联组成。这是因为 Location
可以有多个地址(例如访客地址和送货地址)。
Address
由几个属性(街道、号码、城市、Country
等)和一个 OneToOne Coordinates
关联组成。
现在,Address
具有以下字段集:
class AddressFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
// More properties, but you get the idea
$this->add([
'name' => 'street',
'required' => false,
'type' => Text::class,
'options' => [
'label' => _('Street'),
],
]);
$this->add([
'name' => 'country',
'required' => false,
'type' => ObjectSelect::class,
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Country::class,
'property' => 'id',
'is_method' => true,
'find_method' => [
'name' => 'getEnabledCountries',
],
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Country'),
'label_generator' => function ($targetEntity) {
return $targetEntity->getName();
},
],
]);
$this->add([
'type' => CoordinatesFieldset::class,
'required' => false,
'name' => 'coordinates',
'options' => [
'use_as_base_fieldset' => false,
],
]);
}
}
如您所见,必须输入 Address
实体的详细信息,必须选择 Country
并且可以(不需要)提供 Coordinates
。
以上是使用下面的 InputFilter 验证的。
class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */
protected $coordinatesFieldsetInputFilter;
public function __construct(
CoordinatesFieldsetInputFilter $filter,
EntityManager $objectManager,
Translator $translator
) {
$this->coordinatesFieldsetInputFilter = $filter;
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Address::class),
'translator' => $translator,
]);
}
/**
* Sets AddressFieldset Element validation
*/
public function init()
{
parent::init();
$this->add($this->coordinatesFieldsetInputFilter, 'coordinates');
$this->add([
'name' => 'street',
'required' => false,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
$this->add([
'name' => 'country',
'required' => false,
]);
}
}
如您所见,AddressFieldsetInputFilter
需要一些东西,其中之一就是 CoordinatesFieldsetInputFilter
。然后在 init()
函数中添加与 Fieldset 中给定的名称相对应的名称。
现在,以上都可以,没问题。到处都是带坐标的地址。太棒了。
当我们更进一步并得到 LocationFieldset
时,问题就出现了,如下所示,它是 LocationFieldsetInputFilter
。
class LocationFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Name'),
],
]);
$this->add([
'type' => Collection::class,
'name' => 'addresses',
'options' => [
'label' => _('Addresses'),
'count' => 1,
'allow_add' => true,
'allow_remove' => true,
'should_create_template' => true,
'target_element' => $this->getFormFactory()->getFormElementManager()->get(AddressFieldset::class),
],
]);
}
}
在下面的 class 中,您可能会注意到一堆注释掉的行,这些是修改 InputFilter 的 DI and/or 设置以使其工作的不同尝试。
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
protected $addressFieldsetInputFilter;
// /** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */
// protected $coordinatesFieldsetInputFilter;
public function __construct(
AddressFieldsetInputFilter $filter,
// CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter,
EntityManager $objectManager,
Translator $translator
) {
$this->addressFieldsetInputFilter = $filter;
// $this->coordinatesFieldsetInputFilter = $coordinatesFieldsetInputFilter;
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Location::class),
'translator' => $translator,
]);
}
/**
* Sets LocationFieldset Element validation
*/
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'addresses');
// $this->get('addresses')->add($this->coordinatesFieldsetInputFilter, 'coordinates');
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
您可能已经注意到 LocationFieldset
和 LocationFieldsetInputFilter
使用了现有的 AddressFieldset
和 `AddressFieldsetInputFilter。
看看它们是如何工作的,我不明白为什么会出错。
但是哪里出了问题?
好吧,要创建 Location
,似乎总是需要输入 Coordinates
。如果您查看 AddressFieldset
(在顶部),您会注意到一个 'required' => false,
,所以这没有意义。
但是,当我在输入中输入值时,它们没有得到验证。调试时,我进入 \Zend\InputFilter\BaseInputFilter
,第 262 行,它专门验证输入,我注意到它在验证过程中丢失了数据。
我已经确认数据在开始时存在,在验证过程中,直到它尝试验证 Coordinates
实体,它似乎丢失了它(还没有找到原因).
如果有人能指出正确的方向来清理它,我将不胜感激。已经在这个问题上讨论了太多个小时了。
编辑
在视图部分代码中添加以显示打印方法,以防should/could帮助:
地址-form.phtml
<?php
/** @var \Address\Form\AddressForm $form */
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('csrf'));
echo $this->formRow($form->get('address')->get('id'));
echo $this->formRow($form->get('address')->get('street'));
echo $this->formRow($form->get('address')->get('city'));
echo $this->formRow($form->get('address')->get('country'));
echo $this->formCollection($form->get('address')->get('coordinates'));
echo $this->formRow($form->get('submit'));
echo $this->form()->closeTag($form);
位置-form.phtml
<?php
/** @var \Location\Form\LocationForm $form */
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('csrf'));
echo $this->formRow($form->get('location')->get('id'));
echo $this->formRow($form->get('location')->get('name'));
//echo $this->formCollection($form->get('location')->get('addresses'));
$addresses = $form->get('location')->get('addresses');
foreach ($addresses as $address) {
echo $this->formCollection($address);
}
echo $this->formRow($form->get('submit'));
echo $this->form()->closeTag($form);
为了以防万一它使一切变得更加清晰:一张调试图片来帮忙
经过又一天的调试(和发誓),我找到了答案!
将我指向 Zend CollectionInputFilter
,从而帮助了我。
因为 AddressFieldset
添加到 Collection
中的 LocationFieldset
,所以必须使用具有特定 InputFilter
的 CollectionInputFilter
进行验证Fieldset
指定。
为了修复我的应用程序,我必须同时修改 LocationFieldsetInputFilter
和 LocationFieldsetInputFilterFactory
。在更新的代码下方,旧代码在注释中。
LocationFieldsetInputFilterFactory.php
class LocationFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
/**
* @param ServiceLocatorInterface|ControllerManager $serviceLocator
* @return InputFilter
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
parent::setupRequirements($serviceLocator, Location::class);
/** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
$addressFieldsetInputFilter = $this->getServiceManager()->get('InputFilterManager')
->get(AddressFieldsetInputFilter::class);
$collectionInputFilter = new CollectionInputFilter();
$collectionInputFilter->setInputFilter($addressFieldsetInputFilter); // Make sure to add the FieldsetInputFilter that is to be used for the Entities!
return new LocationFieldsetInputFilter(
$collectionInputFilter, // New
// $addressFieldsetInputFilter, // Removed
$this->getEntityManager(),
$this->getTranslator()
);
}
}
LocationFieldsetInputFilter.php
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
// Removed
// /** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
// protected $addressFieldsetInputFilter ;
// New
/** @var CollectionInputFilter $addressCollectionInputFilter */
protected $addressCollectionInputFilter;
public function __construct(
CollectionInputFilter $addressCollectionInputFilter, // New
// AddressFieldsetInputFilter $filter, // Removed
EntityManager $objectManager,
Translator $translator
) {
// $this->addressFieldsetInputFilter = $filter; // Removed
$this->addressCollectionInputFilter = $addressCollectionInputFilter; // New
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Location::class),
'translator' => $translator,
]);
}
/**
* Sets LocationFieldset Element validation
*/
public function init()
{
parent::init();
// $this->add($this->addressFieldsetInputFilter, 'addresses'); // Removed
$this->add($this->addressCollectionInputFilter, 'addresses'); // New
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
它的工作方式是,在数据验证期间,它将对从客户端收到的每个“element
”应用单数 AddressFieldsetInputFilter
。因为来自客户端的 Collection
可能是这些元素中的 0 个或多个(因为 adding/removing 它们是使用 JavaScript 完成的)。
现在我已经弄清楚了,它确实非常有道理。
我刚才问了一个
我一直在彻底应用关注点分离原则将 Fieldsets 与 InputFilters 分开,因为创建它们的模块也将在 API(基于 Apigility)中使用,所以我会只需要实体和输入过滤器。
但是,我现在有一个问题,当我有一个由 Fieldset 使用的 Fieldset,用于 Fieldset 中的 Collection 时,最里面的 Fieldset 没有验证。
让我用例子和代码来详细说明!
情况是我希望能够创建一个Location
。 Location
由 属性 name
和 OneToMany ArrayCollection|Address[]
关联组成。这是因为 Location
可以有多个地址(例如访客地址和送货地址)。
Address
由几个属性(街道、号码、城市、Country
等)和一个 OneToOne Coordinates
关联组成。
现在,Address
具有以下字段集:
class AddressFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
// More properties, but you get the idea
$this->add([
'name' => 'street',
'required' => false,
'type' => Text::class,
'options' => [
'label' => _('Street'),
],
]);
$this->add([
'name' => 'country',
'required' => false,
'type' => ObjectSelect::class,
'options' => [
'object_manager' => $this->getEntityManager(),
'target_class' => Country::class,
'property' => 'id',
'is_method' => true,
'find_method' => [
'name' => 'getEnabledCountries',
],
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Country'),
'label_generator' => function ($targetEntity) {
return $targetEntity->getName();
},
],
]);
$this->add([
'type' => CoordinatesFieldset::class,
'required' => false,
'name' => 'coordinates',
'options' => [
'use_as_base_fieldset' => false,
],
]);
}
}
如您所见,必须输入 Address
实体的详细信息,必须选择 Country
并且可以(不需要)提供 Coordinates
。
以上是使用下面的 InputFilter 验证的。
class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */
protected $coordinatesFieldsetInputFilter;
public function __construct(
CoordinatesFieldsetInputFilter $filter,
EntityManager $objectManager,
Translator $translator
) {
$this->coordinatesFieldsetInputFilter = $filter;
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Address::class),
'translator' => $translator,
]);
}
/**
* Sets AddressFieldset Element validation
*/
public function init()
{
parent::init();
$this->add($this->coordinatesFieldsetInputFilter, 'coordinates');
$this->add([
'name' => 'street',
'required' => false,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
$this->add([
'name' => 'country',
'required' => false,
]);
}
}
如您所见,AddressFieldsetInputFilter
需要一些东西,其中之一就是 CoordinatesFieldsetInputFilter
。然后在 init()
函数中添加与 Fieldset 中给定的名称相对应的名称。
现在,以上都可以,没问题。到处都是带坐标的地址。太棒了。
当我们更进一步并得到 LocationFieldset
时,问题就出现了,如下所示,它是 LocationFieldsetInputFilter
。
class LocationFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Name'),
],
]);
$this->add([
'type' => Collection::class,
'name' => 'addresses',
'options' => [
'label' => _('Addresses'),
'count' => 1,
'allow_add' => true,
'allow_remove' => true,
'should_create_template' => true,
'target_element' => $this->getFormFactory()->getFormElementManager()->get(AddressFieldset::class),
],
]);
}
}
在下面的 class 中,您可能会注意到一堆注释掉的行,这些是修改 InputFilter 的 DI and/or 设置以使其工作的不同尝试。
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
protected $addressFieldsetInputFilter;
// /** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */
// protected $coordinatesFieldsetInputFilter;
public function __construct(
AddressFieldsetInputFilter $filter,
// CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter,
EntityManager $objectManager,
Translator $translator
) {
$this->addressFieldsetInputFilter = $filter;
// $this->coordinatesFieldsetInputFilter = $coordinatesFieldsetInputFilter;
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Location::class),
'translator' => $translator,
]);
}
/**
* Sets LocationFieldset Element validation
*/
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'addresses');
// $this->get('addresses')->add($this->coordinatesFieldsetInputFilter, 'coordinates');
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
您可能已经注意到 LocationFieldset
和 LocationFieldsetInputFilter
使用了现有的 AddressFieldset
和 `AddressFieldsetInputFilter。
看看它们是如何工作的,我不明白为什么会出错。
但是哪里出了问题?
好吧,要创建 Location
,似乎总是需要输入 Coordinates
。如果您查看 AddressFieldset
(在顶部),您会注意到一个 'required' => false,
,所以这没有意义。
但是,当我在输入中输入值时,它们没有得到验证。调试时,我进入 \Zend\InputFilter\BaseInputFilter
,第 262 行,它专门验证输入,我注意到它在验证过程中丢失了数据。
我已经确认数据在开始时存在,在验证过程中,直到它尝试验证 Coordinates
实体,它似乎丢失了它(还没有找到原因).
如果有人能指出正确的方向来清理它,我将不胜感激。已经在这个问题上讨论了太多个小时了。
编辑
在视图部分代码中添加以显示打印方法,以防should/could帮助:
地址-form.phtml
<?php
/** @var \Address\Form\AddressForm $form */
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('csrf'));
echo $this->formRow($form->get('address')->get('id'));
echo $this->formRow($form->get('address')->get('street'));
echo $this->formRow($form->get('address')->get('city'));
echo $this->formRow($form->get('address')->get('country'));
echo $this->formCollection($form->get('address')->get('coordinates'));
echo $this->formRow($form->get('submit'));
echo $this->form()->closeTag($form);
位置-form.phtml
<?php
/** @var \Location\Form\LocationForm $form */
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('csrf'));
echo $this->formRow($form->get('location')->get('id'));
echo $this->formRow($form->get('location')->get('name'));
//echo $this->formCollection($form->get('location')->get('addresses'));
$addresses = $form->get('location')->get('addresses');
foreach ($addresses as $address) {
echo $this->formCollection($address);
}
echo $this->formRow($form->get('submit'));
echo $this->form()->closeTag($form);
为了以防万一它使一切变得更加清晰:一张调试图片来帮忙
经过又一天的调试(和发誓),我找到了答案!
CollectionInputFilter
,从而帮助了我。
因为 AddressFieldset
添加到 Collection
中的 LocationFieldset
,所以必须使用具有特定 InputFilter
的 CollectionInputFilter
进行验证Fieldset
指定。
为了修复我的应用程序,我必须同时修改 LocationFieldsetInputFilter
和 LocationFieldsetInputFilterFactory
。在更新的代码下方,旧代码在注释中。
LocationFieldsetInputFilterFactory.php
class LocationFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
/**
* @param ServiceLocatorInterface|ControllerManager $serviceLocator
* @return InputFilter
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
parent::setupRequirements($serviceLocator, Location::class);
/** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
$addressFieldsetInputFilter = $this->getServiceManager()->get('InputFilterManager')
->get(AddressFieldsetInputFilter::class);
$collectionInputFilter = new CollectionInputFilter();
$collectionInputFilter->setInputFilter($addressFieldsetInputFilter); // Make sure to add the FieldsetInputFilter that is to be used for the Entities!
return new LocationFieldsetInputFilter(
$collectionInputFilter, // New
// $addressFieldsetInputFilter, // Removed
$this->getEntityManager(),
$this->getTranslator()
);
}
}
LocationFieldsetInputFilter.php
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
// Removed
// /** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
// protected $addressFieldsetInputFilter ;
// New
/** @var CollectionInputFilter $addressCollectionInputFilter */
protected $addressCollectionInputFilter;
public function __construct(
CollectionInputFilter $addressCollectionInputFilter, // New
// AddressFieldsetInputFilter $filter, // Removed
EntityManager $objectManager,
Translator $translator
) {
// $this->addressFieldsetInputFilter = $filter; // Removed
$this->addressCollectionInputFilter = $addressCollectionInputFilter; // New
parent::__construct([
'object_manager' => $objectManager,
'object_repository' => $objectManager->getRepository(Location::class),
'translator' => $translator,
]);
}
/**
* Sets LocationFieldset Element validation
*/
public function init()
{
parent::init();
// $this->add($this->addressFieldsetInputFilter, 'addresses'); // Removed
$this->add($this->addressCollectionInputFilter, 'addresses'); // New
$this->add([
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
它的工作方式是,在数据验证期间,它将对从客户端收到的每个“element
”应用单数 AddressFieldsetInputFilter
。因为来自客户端的 Collection
可能是这些元素中的 0 个或多个(因为 adding/removing 它们是使用 JavaScript 完成的)。
现在我已经弄清楚了,它确实非常有道理。