1

So I have a base form:

public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('firstName')
            ->add('lastName')
            ->add('checklist);
}

Which has a specific field checklist. I created a model class, that describes all possible options in the checklist

ChecklistModel.php

class ChecklistModel {
    /** @var string **/
    protected $clientSatisfied;

    // ... getters and setters

}

Then, I created a form type specially for Checklist.

ChecklistFormType.php

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('clientSatisfied', ChoiceType::class, array(
            'choices' => array(
                'yes' => 'yes',
                'no' => 'no'
            ),
            'choices_as_values' => true,
    ))
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => ChecklistModel::class
    ));
}

I want to store checklist as a simple JSON string in database, but I want to use a ChecklistModel to make sure that all fields in the checklist are submitted correctly.

My question is how to tell Symfony use ChecklistFormType as a field type of base form checklist property?

I've tried something like

->add('checklist', ChecklistFormType::class);

But I'm getting the follwing error

The form's view data is expected to be an instance of class PT\MyBundle\Models\Invoice\ChecklistModel, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of PT\MyBundle\Models\Invoice\ChecklistModel
1
  • The property checklist on the persisted entity will be of type string then? Have you considered the built in type json? You could then do the transformations in the setter/getter of the encapsulating model, freeing the form type (and for that matter, all other code that would access the checklist) from knowing anything about how the data is persisted. Commented Oct 25, 2017 at 13:31

2 Answers 2

1

Building on my comment above, I'd suggest not doing the data transformation in the form type (though it's certainly possible), but rather in the encapsulating model using the json_array type.

This way only that model actually knows how the data will be persisted.

The two relevant models:

src/AppBundle/Entity/FooModel.php

<?php
declare(strict_types=1);

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */
class FooModel
{
    // other properties (firstName, lastName, ...)

    /**
     * @var array
     *
     * @ORM\Column(type="json_array")
     */
    private $checklist = [];

    /**
     * @param ChecklistModel $checklist
     */
    public function setChecklist(ChecklistModel $checklist)
    {
        $this->checklist = $checklist->toArray();
    }

    /**
     * @return ChecklistModel
     */
    public function getChecklist(): ChecklistModel
    {
        return ChecklistModel::fromArray($this->checklist);
    }
}

with ChecklistModel implementing the above mentioned methods:

src/AppBundle/Entity/ChecklistModel.php

<?php
declare(strict_types=1);

namespace AppBundle\Entity;

class ChecklistModel
{
    // properties and getters/setters

    /**
     * @param array $data
     *
     * @return ChecklistModel
     */
    public static function fromArray(array $data): ChecklistModel
    {
        $result = new self;

        foreach (get_class_vars(self::class) as $k => $v) {
            if (isset($data[$k])) {
                $result->$k = $data[$k];
            }
        }

        return $result;
    }

    /**
     * @return array
     */
    public function toArray()
    {
        return get_object_vars($this);
    }
}

The form types:

src/AppBundle/Form/FooFormType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use AppBundle\Entity\FooModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;

class FooFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstName', FormType\TextType::class)
            ->add('lastName', FormType\TextType::class)
            ->add('checklist', ChecklistFormType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => FooModel::class,
            'empty_data' => new FooModel(),
        ]);
    }
}

src/AppBundle/Form/ChecklistFormType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use AppBundle\Entity\ChecklistModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;

class ChecklistFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('clientSatisfied', FormType\ChoiceType::class, [
                'choices' => [
                    'yes' => 'yes',
                    'no' => 'no'
                ],
                'choices_as_values' => true,
            ])
            ->add('clientNewCustomer', FormType\ChoiceType::class, [
                'choices' => [
                    'yes' => 'yes',
                    'no' => 'no'
                ],
                'choices_as_values' => true,
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => ChecklistModel::class,
            'empty_data' => new ChecklistModel(),
        ]);
    }
}

example usage

public function indexAction(Http\Request $request)
{
    $em = $this->getDoctrine()->getManager();

    $data = new Entity\FooModel();

    $form = $this
        ->createForm(FooFormType::class, $data)
        ->handleRequest($request)
    ;

    if ($form->isSubmitted() && $form->isValid()) {
        $em->persist($data);
        $em->flush();
    }

    return $this->render('default/index.html.twig', [
        'form' => $form->createView(),
        'data' => $form->getData(),
    ]);
}

This way the ChecklistFormType dose not need to know anything about the data being json or something else. A ChecklistModel goes in and comes out, no surprises.


That said, embeddables might be a better choice here.

Sign up to request clarification or add additional context in comments.

6 Comments

I'm sorry, but my problem is how to submit the data class ChecklistModel. The goal is to validate different fields in a checklist, this can be choices, string, float types. Can you, please, extend your example and show me how to build a form, so the form will take care about checklist fields validation? Thank you!
@LuninRoman I will come back to you tomorrow, can't answer from here at the moment. ;) Until then, would you please extend your question by what you mean with "validation"? Currently all normal form validation would work. If that's all you need, I'll add an example tomorrow.
Hi, here is an official guide symfony.com/doc/current/validation.html, take a look at form builders. For example, I want to specify all possible choices, that can be submitted to ChecklistModel clientSatisfied variable symfony.com/doc/current/reference/forms/types/….
@LuninRoman That's more or less already the case, because the form won't accept values that are not in the choices array configured in ChecklistFormType.
@LuninRoman I added the form types I used, let me know if you have other questions.
|
1

You have to specify the data class as told in the error message and to define a data transformer in combination with a de/serializer. Symfony documentation

3 Comments

The link is broken
Sorry just changed
Your answer was also helpful! Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.