ZF2 + Doctrine2 - Fieldset中Collection集合的Fieldset中的Fieldset无法正确验证

时间:2017-07-12 12:19:57

标签: php doctrine-orm zend-framework2 zend-form zend-validate

前一段时间我问了一个similar question,这归结为Forms,Fieldsets和InputFilters的结构。

我一直在彻底应用关注点分离的原则来分割来自InputFilters的Fieldsets,因为它们创建的模块也将用于API(基于Apigility),因此我只需要Entities和InputFilters。

但是,我现在有一个问题,当我有一个Fieldset使用的Fieldset,在Fieldset的Collection中使用时,最内层的Fieldset不会验证。

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

情况是我希望能够创建LocationLocation由属性nameOneToMany 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),
            ],
        ]);
    }
}

在下面的课程中,您可能会注意到一堆已注释掉的行,这些行是修改DI和/或设置InputFilter的不同尝试,以便它可以正常工作。

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。

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

但出了什么问题?

好吧,要创建Location,似乎始终需要输入Coordinates。如果您查看AddressFieldset(位于顶部),您会发现'required' => false,,所以这没有任何意义。

但是,当我在输入中输入值时,它们不会被验证。调试时,我进入\Zend\InputFilter\BaseInputFilter第262行专门验证输入的地方,我注意到它在验证过程中丢失了数据。

我已经在开始时和验证期间确认了数据的存在,直到它尝试验证Coordinates实体,它似乎失去了它(没有找到原因)。

如果有人能指出我正确的方向来清理它,那将非常感谢你的帮助。现在已经花了太多时间打击这个问题。

修改

添加视图部分代码以显示打印方法,以防应该/可以提供帮助:

地址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);

以防万一更清楚:调试图片帮助 isEmpty errors, when clearly set

1 个答案:

答案 0 :(得分:0)

经过另一天的调试(和咒骂),我找到了答案!

This SO question通过指向Zend CollectionInputFilter帮助我。

由于AddressFieldset已添加到LocationFieldset中的Collection,因此必须使用CollectionInputFilterInputFilter进行验证,该Fieldset具有LocationFieldsetInputFilter的{​​{1}} {1}}已指定。

要修复我的应用,我必须同时修改LocationFieldsetInputFilterFactoryLocationFieldsetInputFilterFactory.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, ], ], ], ]); } }

AddressFieldsetInputFilter

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

现在我已经明白了,它确实非常有意义。