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
}
正如在上面的实体中看到的那样。 Address
与 Coordinates
具有 OneToOne
单向关系。 Coordinates
实体需要 latitude
和 longitude
属性,如 nullable=false
.
所示
就是那里出了问题。如果创建了 Address
,但没有在表单中设置 Coordinates
的属性,它仍然会创建一个 Coordinates
实体,但会留下 latitude
和 longitude
属性为空,即使它们是必需的。
所以,简而言之:
- 一个
Coordinates
实体创建在不应该存在的地方
- link 到
Coordinates
是从 Address
创建的,其中不应该存在
在 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 扩展我自己的 AbstractFormInputFilter
和 AbstractFieldsetInputFilter
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 的实体的原始问题,如屏幕截图所示在问题中。
关于
我的表单正确验证。通过 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
}
正如在上面的实体中看到的那样。 Address
与 Coordinates
具有 OneToOne
单向关系。 Coordinates
实体需要 latitude
和 longitude
属性,如 nullable=false
.
就是那里出了问题。如果创建了 Address
,但没有在表单中设置 Coordinates
的属性,它仍然会创建一个 Coordinates
实体,但会留下 latitude
和 longitude
属性为空,即使它们是必需的。
所以,简而言之:
- 一个
Coordinates
实体创建在不应该存在的地方 - link 到
Coordinates
是从Address
创建的,其中不应该存在
在 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 扩展我自己的 AbstractFormInputFilter
和 AbstractFieldsetInputFilter
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 的实体的原始问题,如屏幕截图所示在问题中。