Skip to content
Advertisement

Dynamic embedded form with OneToOne relationship

I’m trying to create a form for an article with an addon (tender).

The article is in one-to-one relationship with the addon and the addon can be disabled (null).

If the checkbox with an addon is checked, the addon needs to be validated, then submitted together with the article.

The issue I’m having is, I’m unable to set the addon to null before the submission, what produces the errors – the form still tries to submit an addon tied to the article, while its values can’t be empty.

How can I disable the addon when the checkbox is unchecked and still submit the article to the database, but without the addon?

I’ve tried using form events to modify the event’s data, but it turned out that tender is not part of data during PRE_SUBMIT. If I try to modify the data during SUBMIT, then I have to alter the logic in the methods of an entity, but it doesn’t feel right and also causes some issues that I don’t remember and couldn’t work around. I’ve also tried using ’empty_data’ => null, thinking it would do exactly what I’ve wanted, but it turned out it has no effect on my form at all.

Article’s entity:

<?php

namespace AppEntity;

use AppRepositoryContentsRepository;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineCommonCollectionsCollection;
use DoctrineORMMapping as ORM;

/**
* @ORMEntity(repositoryClass=ContentsRepository::class)
*/
class Contents
{
    /**
    * @ORMId()
    * @ORMGeneratedValue()
    * @ORMColumn(type="integer")
    */
    private $id;

    /**
    * @ORMOneToOne(targetEntity=ContentsTenders::class, mappedBy="content", cascade={"persist", "remove"})
    */
    private $tender;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getPosition(): ?int
    {
        return $this->position;
    }

    public function getTender(): ?ContentsTenders
    {
        return $this->tender;
    }

    public function setTender(ContentsTenders $tender): self
    {
        $this->tender = $tender;

        // set the owning side of the relation if necessary
        if ($tender->getContent() !== $this) {
            $tender->setContent($this);
        }

        return $this;
    }
}

Addon’s entity:

<?php

namespace AppEntity;

use AppRepositoryContentsTendersRepository;
use DateTime;
use DoctrineORMMapping as ORM;

/**
* @ORMEntity(repositoryClass=ContentsTendersRepository::class)
*/
class ContentsTenders
{
    /**
    * @ORMId()
    * @ORMGeneratedValue()
    * @ORMColumn(type="integer")
    */
    private $id;

    /**
    * @ORMOneToOne(targetEntity=Contents::class, inversedBy="tender", cascade={"persist", "remove"})
    * @ORMJoinColumn(nullable=false)
    */
    private $content;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getContent(): ?Contents
    {
        return $this->content;
    }

    public function setContent(Contents $content): self
    {
        $this->content = $content;

        return $this;
    }
}

Article’s form:

class ContentsType extends AbstractType
{

    //public function __construct(EntityManagerInterface $em, UserInterface $user)
    //{
    //    $this->em = $em;
    //    $this->user = $user;
    //}

    /**
    * {@inheritdoc}
    */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('isTender', CheckboxType::class, [
                'mapped' => false,
                'required' => false,
                'label' => 'Przetarg',
            ])
            ->add('tender', NewTendersType::class, [
                'required' => false,
                'constraints' => array(new Valid()),
                'empty_data' => null
            ])
            ->add('save', SubmitType::class, [
                'label' => 'Zapisz',
                'attr' => ['class' => 'save'],
            ])  
        ;

        $builder->addEventListener(
            FormEvents::SUBMIT,
            function (FormEvent $event) {
                $form = $event->getForm();
                $data = $event->getData();
                if(!$form['isTender']->getData()){
                    //
                }
            }
        );
    }

    /**
    * @param OptionsResolver $resolver
    */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Contents::Class,
            'accessible_ids' => [],
        ]);
    }
}

@Edit I’ve used: Symfony Custom Field or Dynamic Form?

and added:

class NullableTenderTransformer implements DataTransformerInterface
{
    public function transform($value)
    {
        return $value;
    }

    public function reverseTransform($data)
    {
    if($data->getTender()->getTenContracting() === null 
    && $data->getTender()->getTenOrderFor() === null
    && $data->getTender()->getTenNumber() === null) {
        $data->resetTender();
    }

    return $data;
    }
}

combined with the answer below and $content->setTender(new ContentsTenders()); in the controller, +reset method in model, it works.

It might not be a perfect solution, though.

Advertisement

Answer

You can use a validation group

'constraints' => array(new Valid(['groups' => "withTender")


public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => Contents::Class,
        'accessible_ids' => [],
        'validation_groups' => function (FormInterface $form): array {
            $data = $form->getData();

            $groups = ['Default'];
            if ($data->isTender) {
                $groups[] = 'withTender';
            }
            return $groups;
        }
    ]);
}
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement