ZF2 + Doctrine 2 - 未满足要求且值为空时创建的实体

ZF2 + Doctrine 2 - Entity created when requirements not met and values empty

关于 and 的前 2 个问题的扩展我已经 运行 进入下一期。

我的表单正确验证。通过 Fieldsets 包括包含的集合。但是,如果未设置其值,最内层的 Fieldset 不应导致与父级的实体和 FK 关联。

Address 可能有也可能没有 linked Coordinates。可以在同一个表单中创建所有这些。

但是,如果没有在表单中提供坐标,则不应创建 Coordinates,也不应从 Address 编辑 link。它们在表单中不是必需的,实体 Coordinates 本身需要同时设置 Latitude 和 Longitude 属性。

下面,首先是实体。以下是用于 AddressForm 的字段集。我已经删除了与 Address -> Coordinates 链无关的内容。

Address.php

class Address extends AbstractEntity
{
    // Properties

    /**
     * @var Coordinates
     * @ORM\OneToOne(targetEntity="Country\Entity\Coordinates", cascade={"persist"}, fetch="EAGER", orphanRemoval=true)
     * @ORM\JoinColumn(name="coordinates_id", referencedColumnName="id", nullable=true)
     */
    protected $coordinates;

    // Getters/Setters
}

Coordinates.php

class Coordinates extends AbstractEntity
{
    /**
     * @var string
     * @ORM\Column(name="latitude", type="string", nullable=false)
     */
    protected $latitude;

    /**
     * @var string
     * @ORM\Column(name="longitude", type="string", nullable=false)
     */
    protected $longitude;

    // Getters/Setters
}

正如在上面的实体中看到的那样。 AddressCoordinates 具有 OneToOne 单向关系。 Coordinates 实体需要 latitudelongitude 属性,如 nullable=false.

所示

就是那里出了问题。如果创建了 Address,但没有在表单中设置 Coordinates 的属性,它仍然会创建一个 Coordinates 实体,但会留下 latitudelongitude 属性为空,即使它们是必需的。

所以,简而言之:

在 Fieldsets 和 InputFilters 下面进一步阐明。

AddressFieldset.php

class AddressFieldset extends AbstractFieldset
{
    public function init()
    {
        parent::init();

        // Other properties

        $this->add([
            'type' => CoordinatesFieldset::class,
            'required' => false,
            'name' => 'coordinates',
            'options' => [
                'use_as_base_fieldset' => false,
            ],
        ]);
    }
}

CoordinatesFieldset.php

class CoordinatesFieldset extends AbstractFieldset
{
    public function init()
    {
        parent::init();

        $this->add([
            'name' => 'latitude',
            'required' => true,
            'type' => Text::class,
            'options' => [
                'label' => _('Latitude'),
            ],
        ]);

        $this->add([
            'name' => 'longitude',
            'required' => true,
            'type' => Text::class,
            'options' => [
                'label' => _('Longitude'),
            ],
        ]);
    }
}

AddressFieldsetInputFilter.php

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

        // Other filters/validators
    }
}

CoordinatesFieldsetInputFilter.php

class CoordinatesFieldsetInputFilter extends AbstractFieldsetInputFilter
{
    public function init()
    {
        parent::init();

        $this->add([
            'name' => 'latitude',
            'required' => true,
            'allow_empty' => true,
            'filters' => [
                ['name' => StringTrim::class],
                ['name' => StripTags::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'min' => 2,
                        'max' => 255,
                    ],
                ],
                [
                    'name' => Callback::class,
                    'options' => [
                        'callback' => function($value, $context) {
                            //If longitude has a value, mark required
                            if(empty($context['longitude']) && strlen($value) > 0) {
                                $validatorChain = $this->getInputs()['longitude']->getValidatorChain();

                                $validatorChain->attach(new NotEmpty(['type' => NotEmpty::NULL]));
                                $this->getInputs()['longitude']->setValidatorChain($validatorChain);

                                return false;
                            }

                            return true;
                        },
                        'messages' => [
                            'callbackValue' => _('Longitude is required when setting Latitude. Give both or neither.'),
                        ],
                    ],
                ],
            ],
        ]);

        // Another, pretty much identical function for longitude (reverse some params and you're there...)
    }
}

编辑: 添加数据库转储图像。显示空纬度、经度。

EDIT2: 当我从 AddressFieldsetInputFilter 输入中删除 'allow_empty' => true, 并填充单个输入(纬度或经度)时,它会正确验证,除非你将两个输入都留空,然后它立即中断到 return 需要输入。 (Value is required and can't be empty).

我偶然发现了 this answer,它允许字段集为空,但如果至少有一个输入被填充则验证它。

通过从包含答案的 AbstractInputFilter class 扩展我自己的 AbstractFormInputFilterAbstractFieldsetInputFilter classes,我现在能够提供带有附加 ->setRequired(false) 的 FielsetInputFilters,例如 AddressFieldsetInputFilter。然后在 AbstractInputFilter 中对其进行验证,如果它实际上是空的。

链接的答案给出了这个代码:

<?php
namespace Application\InputFilter;

use Zend\InputFilter as ZFI;

class InputFilter extends ZFI\InputFilter
{
    private $required = true;

    /**
     * @return boolean
     */
    public function isRequired()
    {
        return $this->required;
    }

    /**
     * @param boolean $required
     *
     * @return $this
     */
    public function setRequired($required)
    {
        $this->required = (bool) $required;
        return $this;
    }

    /**
     * @return bool
     */
    public function isValid()
    {
        if (!$this->isRequired() && empty(array_filter($this->getRawValues()))) {
            return true;
        }

        return parent::isValid();
    }
}

正如我提到的,我使用此代码扩展了我自己的 AbstractInputFilter,允许在 *FieldsetInputFilterFactory classes 中进行小的更改。

AddressFieldsetInputFilterFactory.php

class AddressFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
    /**
     * @param ServiceLocatorInterface|ControllerManager $serviceLocator
     * @return InputFilter
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        parent::setupRequirements($serviceLocator, Address::class);

        /** @var CoordinatesFieldsetInputFilter $coordinatesFieldsetInputFilter */
        $coordinatesFieldsetInputFilter = $this->getServiceManager()->get('InputFilterManager')
            ->get(CoordinatesFieldsetInputFilter::class);
        $coordinatesFieldsetInputFilter->setRequired(false); // <-- Added option

        return new AddressFieldsetInputFilter(
            $coordinatesFieldsetInputFilter,
            $this->getEntityManager(),
            $this->getTranslator()
        );
    }
}

对于每个人的项目来说可能不是一个好主意,但它解决了我不总是想要验证 Fieldset 的问题,并且它确实解决了不创建仅具有 ID 的实体的原始问题,如屏幕截图所示在问题中。