ZF2 + Doctrine2-Fieldset中的集合的Fieldset中的Fieldset验证不正确 [英] ZF2 + Doctrine2 - Fieldset in Fieldset of a Collection in Fieldset does not validate properly

查看:100
本文介绍了ZF2 + Doctrine2-Fieldset中的集合的Fieldset中的Fieldset验证不正确的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我问了类似的问题之前,归结为Forms,Fieldsets和InputFilters的结构.

I asked a similar question a while ago, which came down to the structuring of the Forms, Fieldsets and InputFilters.

我一直在彻底采用关注点分离的原则,以将Fieldset从InputFilter分离出来,因为在API中创建它们的模块也将用于API(基于Apigility),因此我只需要Entities和InputFilters.

I've been thoroughly applying the principle of separation of concerns to split up Fieldsets from InputFilters as the modules they're created in will also be used in an API (Apigility based), so I would need only Entities and InputFilters.

但是,我现在遇到一个问题,当我有一个字段集(由一个字段集使用)在一个字段集的一个集合中使用时,最里面的字段集无法验证.

However, I now have a problem that when I have a Fieldset, used by a Fieldset, used in a Collection in a Fieldset, that the inner-most Fieldset does not validate.

让我详细说明示例和代码!

Let me elaborate with examples, and code!

这种情况是我希望能够创建一个Location. Location由属性nameOneToMany ArrayCollection|Address[]关联组成.这是因为Location可能具有多个地址(例如访客地址和送货地址).

The situation is that I want to be able to create a Location. A Location consists of a property name and a OneToMany ArrayCollection|Address[] association. This is because a Location could have multiple addresses (such as a visitors address and a delivery address).

Address由一些属性(街道,数字,城市,Country等)和OneToOne Coordinates关联组成.

An Address consists of a few properties (street, number, city, Country, etc.) and a OneToOne Coordinates association.

现在,Address具有以下字段集:

Now, Address has the below Fieldset:

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.

As you can see, details for the Address Entity must entered, a Country must be selected and Coordinates could (not required) be provided.

上面的内容通过下面的InputFilter进行了验证.

The above is validated using the InputFilter below.

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()函数中,然后添加与字段集中给定名称相对应的名称.

As you can see, the AddressFieldsetInputFilter required a few things, one of which is the CoordinatesFieldsetInputFilter. In the init() function this is then added with the name corresponding to that given in the Fieldset.

现在,以上所有方法都可以,没问题.地址随处可见.很好.

Now, all of the above works, no problem. Addresses with Coordinates everywhere. It's great.

当我们再上一层并且将LocationFieldset设置为LocationFieldsetInputFilter时,就会出现问题.

The problem arises when we go another level further and have the LocationFieldset, as below, with it's 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),
            ],
        ]);
    }
}

在下面的类中,您可能会注意到一堆注释掉的行,它们是修改DI和/或InputFilter的设置以使其起作用的不同尝试.

In the class below, you might notice a bunch of commented out lines, these have been different attempts to modify the DI and/or setup of the InputFilter so that it works.

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,
                    ],
                ],
            ],
        ]);
    }
}

您可能已经注意到,LocationFieldsetLocationFieldsetInputFilter使用了现有的AddressFieldset和`AddressFieldsetInputFilter.

You might have noticed that the LocationFieldset and LocationFieldsetInputFilter make use of the existing AddressFieldset and `AddressFieldsetInputFilter.

看到它们是如何工作的,我无法弄清楚它为什么出错了.

Seeing as how they work, I cannot figure out why it's going wrong.

但是出了什么问题?

好吧,要创建一个Location,似乎总是需要输入Coordinates.如果您查看AddressFieldset(在顶部),则会注意到'required' => false,,所以这没有任何意义.

Well, to create a Location, it appears that entering Coordinates is always required. If you look in the AddressFieldset (at the top), you'll notice a 'required' => false,, so this makes no sense.

但是,当我在输入中输入值时,它们不会得到验证.调试时,进入\Zend\InputFilter\BaseInputFilter行#262,它专门验证输入,并且我注意到它在验证过程中丢失了数据.

However, when I DO enter values in the inputs, they do not get validated. When debugging, I get into the \Zend\InputFilter\BaseInputFilter, line #262 where it specifically validates the input, and I notice that it has lost it's data along the way of validation.

我已经在开始时以及在验证过程中确认了数据的存在,直到它尝试验证Coordinates实体为止,在该实体中似乎丢失了它(尚未找到原因).

I've confirmed the presence of the data at the start, and during the validation, up until it tries to validate the Coordinates Entity, where it seems to lose it (haven't found out why).

如果有人可以向我指出正确的方向进行清理,那将非常感谢您的帮助.现在已经在这个问题上争论了太多小时了.

If someone could point me in the right direction to clean this up, that help would be greatly appreciated. Have been banging against this issue for way too many hours now.

编辑

在视图部分代码中添加了显示打印方法,以防万一:

Added in view partial code to show method for printing, in case that should/could help:

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

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

以防万一,这一切变得更加清晰:调试图片可以提供帮助

推荐答案

经过一天的调试(并宣誓就职),我找到了答案!

After another day of debugging (and swearing), I found the answer!

这个SO问题通过指向我帮助了我朝向Zend CollectionInputFilter.

This SO question helped me out by pointing me towards the Zend CollectionInputFilter.

由于AddressFieldset已添加到Collection中的LocationFieldset中,因此必须使用CollectionInputFilter对其进行验证,该CollectionInputFilter对于指定的Fieldset具有特定的InputFilter.

Because the AddressFieldset is added to the LocationFieldset within a Collection, it must be validated using a CollectionInputFilter which has the specific InputFilter for the Fieldset specified.

要修复我的应用程序,我必须同时修改LocationFieldsetInputFilterLocationFieldsetInputFilterFactory.在更新的代码下方,并在注释中包含旧代码.

To fix my application I had to modify both the LocationFieldsetInputFilter and the LocationFieldsetInputFilterFactory. Below the updated code, with the old code in comments.

LocationFieldsetInputFilterFactory.php

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

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,
                    ],
                ],
            ],
        ]);
    }
}

这种工作方式是在验证数据期间,它将单数AddressFieldsetInputFilter应用于从客户端收到的每个"element".因为来自客户端的Collection可能是0个或多个这些元素(因为添加/删除它们是使用JavaScript完成的).

The way this works is that, during the validation of the data, it will apply the singular AddressFieldsetInputFilter to every "element" received from the client-side. Because a Collection, from the client, may be 0 or more of these elements (as adding/removing them is done using JavaScript).

现在我已经弄清楚了,它实际上确实很合理.

Now that I've figured that out, it does actually make perfect sense.

这篇关于ZF2 + Doctrine2-Fieldset中的集合的Fieldset中的Fieldset验证不正确的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆