eagleoneraptor
eagleoneraptor

Reputation: 1227

Optional embed form in Symfony 2

I have two entities in my system: Person and Phone as the following code.

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;
};

I also have a form to create and update a person (with the phone), so I have a PersonType that have a embed form representing a phone (PhoneType).

My problem is that a person can has optionally a phone, but if the person has a phone, all the phone fields are required. So, if user write nothing on all phone fields, this represent a person without phone and this case is valid. But if the user fills at least one form field, all other fields are required.

I try to take an approach by setting the phone on null if all phone fields are not filled, this was implemented on setPhone in Person entity. But having a null phone, Symfony tell me that all phone fields are required but are not filled. I believe that Symfony will not validate the phone because I suppose that Symfony will apply the validation directly on person entity. Having a null phone, why tell me that all phone fields are not filled?

Is there a way to do what I want (preferably without modify all my controllers and form types, that is, at entity or validation component level)?

EDIT: Sorry, there is something not mentioned, if user fill a phone field, all phone fields need to be validated separately with different validators (to check if a field is a well formed number, to check correct length, etc). But if user leaves empty all phone fields, the per-field validation should be ignored.

Thanks!

Upvotes: 1

Views: 3204

Answers (5)

Esteban Filardi
Esteban Filardi

Reputation: 736

frankie567's answer was useful to me. For 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'));   
    }
}

Upvotes: 0

frankie567
frankie567

Reputation: 1760

For those passing by, with newer versions of Symfony you can do :

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

Upvotes: 2

edn
edn

Reputation: 1

It is also possible to use a very simple data Transformer to solve this problem.

  1. Create your Phone data transformer :

    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. Then simply prepend this transformer in your PhoneType form :

    class PhoneType extends AbstractType
    {
        public function buildForm(FormBuilder $builder, array $options)
        {
            $builder
                /* Add fields here :
                ->add( ... )
                */
                ->prependNormTransformer(new PhoneTransformer())
            ;
        }
    }
    

See http://symfony.com/doc/2.0/cookbook/form/data_transformers.html for more details on how implement data transformers.

Upvotes: 0

Cyprian
Cyprian

Reputation: 11364

I would try this method:

create additional method in your Phone entity which validates if all fields are null or all fields are not null (these two cases are correct) - and then return true. If some fields are null and some aren't return false. Add an assert annotation to your new method - this will be your new constraint.

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

And this should work.

For more information look here: http://symfony.com/doc/master/book/validation.html#getters

edit:

Try this one:

define your custom validation method (this one which check if any of phone fields is filled) as Callback (at the top of the class):

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

Next you mark field wich have to be validated with validation group, eg.

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

And the last thing, in your custom constraint method you need to switch on "phone_validation" group if any of field isn't empty:

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

And that should work.

Upvotes: 3

Carlos Granados
Carlos Granados

Reputation: 11351

Do this:

First on your phone entity, declare the fields as "nullable" like this:

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

This will allow these fields to have a null value.

Then you need to declare a validator that will check if either none of the fields or all of them are filled

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
}

Upvotes: 0

Related Questions