Question

Hello everybody (please excuse my English).

I want to do an application which needs to allow that the users must fill out on a form their personal data, their children, grandchildren and great-grandchildren (a little family tree).

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

/**
 * @var string
 *
 * @ORM\Column(type="string")
 */
private $firstname;

/**
 * @var string
 *
 * @ORM\Column(type="string")
 */
private $lastname;

/**
 * @var \DateTime
 *
 * @ORM\Column(type="datetime")
 */
private $dateOfBirth;

/**
 * @var Person
 *
 * @ORM\ManyToMany(targetEntity="Person")
 */
private $children;


public function __construct()
{
    $this->children = new ArrayCollection();
}

}
}

In the PersonType class, I do the following:

class PersonType extends AbstractType
{
/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('firstname');

    $builder->add('lastname');

    $builder->add('dateOfBirth');

    $builder->add('children', 'collection', array(
        'type'          => new PersonType(),
        'allow_add'     => true,
        'by_reference'  => false,)
    );
}

/**
 * @param OptionsResolverInterface $resolver
 */
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Anything\YourBundle\Entity\Person'
    ));
}

/**
 * @return string
 */
public function getName()
{
    return 'person';
}
}

In this way, I use the PersonType in the controller as below:

public function newAction()
{
    $entity = new Person();
    $form = $this->createForm(new PersonType(), $entity, array(
        'action' => $this->generateUrl('person_create'),
        'method' => 'POST',
    ));

    return array(
        'entity' => $entity,
        'form'   => $form->createView(),
    );
}

But the problem is when I request the url of this action, and the view of this action has to be rendered, there is a problem because doesn't give a response, because is in a infinite loop (I think that is the reason). I would like to know if is this possible to do using the Symfony forms, or if I have to look at other alternatives. If this was possible, how could I do that and how could I limit the form to only render the four levels that I need (me, my children, my grandchildren and my great-grandchildren)??

I hope that the problem has been understood.

Thanks in advance.

Était-ce utile?

La solution 3

Thanks for the answer Ferdynator!!

I didn't solve the problem in the way you proposed, but that approach helped me. I passed the recursion level in the constructor of the Person form, and thus, I could know when I had to stop:

class PersonType extends AbstractType
{
    private $recursionLevel;

    public function __construct( $recursionLevel ){
        $this->recursionLevel = $recursionLevel;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if($this->recursionLevel > 0)
        {
            $builder->add('children', 'collection', array(
                'type'          => new PersonType(--$this->recursionLevel),
                'allow_add'     => true,
                'by_reference'  => false,)
            );
        }
    }
}

Autres conseils

You could add a custom parameter to your form that indicates the current level of recursion. To archive this you first need to implement a new option:

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Anything\YourBundle\Entity\Person',
        'recursionLevel' => 4
    ));
}

Now you update this value in your buildForm method:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    // ...
    if (--$options['recursionLevel'] > 0) {
        $resolver = new OptionsResolver();
        $resolver->setDefaults(
            $options
        );
        $childType = new PersonType();
        $childType->setDefaultOptions($resolver);

        $builder->add('children', 'collection', array(
            'type'          => $childType,
            'allow_add'     => true,
            'by_reference'  => false
        ));
    }
}

This is not tested.

I had the same problem and tried the solutions provided here. They come with significant drawbacks like a depth limitation and performance overhead - you always create form objects even if there is no data submited.

What I did to overcome this problem was to add a listener for the FormEvents::PRE_SUBMIT event and add the collection type field dynamically if there is data to be parsed.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('content');

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
        $node = $event->getData();
        $form = $event->getForm();

        if (!$node) {
            return;
        }

        if(sizeof(@$node['children'])){
            $form->add('children', CollectionType::class,
                array(
                    'entry_type' => NodeType::class,
                    'allow_add' => true,
                    'allow_delete' => true
                ));
        }
    });

}

I hope this helps someone that has this issue in the future

Ferdynator, thanks for your answers. And I want to propose my decision based on yours:

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Anything\YourBundle\Entity\Person',
            'recursionLevel' => 4
        ));
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // ...
        if (--$options['recursionLevel'] > 0) {
            $builder->add('children', 'collection', array(
                'type' => $childType,
                'allow_add' => true,
                'by_reference' => false,
                'options' => [
                    'recursionLevel' => $options['recursionLevel']
                ],
            ));
        }
    }

It solves our problem.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top