Symfony 2中的可选嵌入表单

时间:2012-08-17 12:52:42

标签: symfony symfony-forms

我的系统中有两个实体:PersonPhone,如下代码所示。

class Person
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $lastName;

    /**
     * @ORM\OneToOne(targetEntity="Phone", cascade={"persist"})
     */
    private $phone;
};

class Phone
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="PhoneType")
     */
    private $type;

    /**
     * @ORM\Column(type="integer")
     */
    private $countryCode;

    /**
     * @ORM\Column(type="integer")
     */
    private $regionCode;

    /**
     * @ORM\Column(type="integer")
     */
    private $number;
};

我还有一个用于创建和更新某人(使用手机)的表单,因此我的PersonType有一个代表电话的嵌入表单(PhoneType)。

我的问题是,一个人可以选择手机,但如果此人有手机,则需要所有手机字段。因此,如果用户在所有电话字段上都没有写任何内容,则表示没有电话的人,这种情况有效。但是,如果用户填写至少一个表单字段,则所有其他字段都是必需的。

如果未填写所有电话字段,我会尝试将电话设置为空,这是在setPhone实体的Person上实施的。但是有一个空电话,Symfony告诉我所有电话领域都是必需的,但没有填写。 我相信Symfony不会验证手机,因为我认为Symfony会直接在人员实体上应用验证。有了一部空电话,为什么告诉我所有电话领域都没有填写?

有没有办法做我想要的事情(最好不要修改我的所有控制器和表单类型,即在实体或验证组件级别)?

编辑抱歉,有一些未提及的内容,如果用户填写手机字段,则需要使用不同的验证码单独验证所有电话字段(检查字段是否格式正确,检查正确的长度等)。但是如果用户将所有电话字段留空,则应忽略每个字段的验证。

谢谢!

5 个答案:

答案 0 :(得分:3)

我会尝试这种方法:

在您的Phone实体中创建另一个方法,该方法验证所有字段是否为空或所有字段都不为空(这两种情况都是正确的) - 然后返回true。如果某些字段为null,则某些字段不返回false。在新方法中添加一个断言注释 - 这将是您的新约束。

/**
 * @Assert\True(message = "Fill all fields or leave all them blank")
 */

这应该有用。

有关详细信息,请查看此处: http://symfony.com/doc/master/book/validation.html#getters

修改

试试这个:

定义您的自定义验证方法(检查是否填写了任何电话字段)作为回调(位于班级顶部):

 * @Assert\Callback(methods={"checkPhoneFields"})
 */
 class Phone {

接下来,您必须标记必须通过验证组验证的字段,例如

/**
 * @ORM\Column(type="string", length=16, groups={"phone_validation"})
 */
private $number;

最后一点,在自定义约束方法中,如果任何字段不为空,则需要打开“phone_validation”组:

public function checkPhoneFields(ExecutionContext $context) {
    if (/* fields are not empty */) {
        $context->getGraphWalker()->walkReference($this, 'phone_validation', $context->getPropertyPath(), true);
    }

这应该有用。

答案 1 :(得分:2)

对于那些路过的人,可以使用较新版本的Symfony:

/**
 *
 * @Assert\Callback()
 */
public function validatePhone(ExecutionContextInterface $context)
{
    if (/* Fields are not empty */)
    {
        $context->validate($this->getPhone(), 'phone', array("phone_validation"));
    }
}

答案 2 :(得分:0)

这样做:

首先在您的手机实体上,将字段声明为“可为空”,如下所示:

/**
 * @ORM\Column(type="integer", nullable=true)
 */
private $countryCode;

这将允许这些字段具有空值。

然后你需要声明一个验证器,它将检查是否有任何字段或所有字段都被填充

class Phone {

...
/**
 * @Assert\True(message = "Phone must be empty of fill all fields")
 */
public function isPhoneOK()
{
    // check if no field is null or all of them are
    // return false if these conditions are not met
}

答案 3 :(得分:0)

也可以使用非常简单的数据Transformer来解决这个问题。

  1. 创建手机数据转换器:

    class PhoneTransformer implements DataTransformerInterface
    {
        public function transform($phone)
        {
            if (is_null($phone))
                return new Phone();
    
            return $phone;
        }
    
        public function reverseTransform($phone)
        {
            if (is_null($phone))
                return null;
    
            if (!$phone->getType() && !$phone->getCountryCode() /* ... Test if phone is not valid here */)
                return null;
    
            return $phone;
    }
    
  2. 然后只需在PhoneType表单中添加此变换器:

    class PhoneType extends AbstractType
    {
        public function buildForm(FormBuilder $builder, array $options)
        {
            $builder
                /* Add fields here :
                ->add( ... )
                */
                ->prependNormTransformer(new PhoneTransformer())
            ;
        }
    }
    
  3. 有关如何实施数据转换器的更多详细信息,请参阅http://symfony.com/doc/2.0/cookbook/form/data_transformers.html

答案 4 :(得分:0)

frankie567的回答对我很有用。对于symfony> = 3.0:

/**
 *
 * @Assert\Callback()
 */
public function validatePhone(ExecutionContextInterface $context)
{
    if (/* Fields are not empty */)
    {
        $validator = $context->getValidator();

        $validator->inContext($context)
            ->atPath('phone')
            // you can pass custom validation instead of null, like new Assert\Valid()
            ->validate($this->getPhone(), null, array('phone_validation'));   
    }
}