8000 Forms with multiple and extended entity type throw an error on Symfony 2.7 · Issue #17736 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

Forms with multiple and extended entity type throw an error on Symfony 2.7 #17736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
adampastusiak opened this issue Feb 9, 2016 · 8 comments

Comments

@adampastusiak
Copy link

I'm on Symfony 2.7.9 and I have form like this:

$form = $this->get('form.factory')->createNamedBuilder('', 'form')
            ->add('entities', 'entity', [
                'class' => 'Namespace\SomeBundle\Entity\SomeEntity',
                'choice_label' => 'name',
                'label' => 'Label',
                'multiple' => true,
                'expanded' => true,
                'read_only' => true,
                'query_builder' => function (EntityRepository $repository) use ($ids) {
                    return $repository->createQueryBuilder('e')
                        ->where('e.id IN(:ids)')
                        ->setParameter(':ids', array_values($ids))
                        ->orderBy('e.name', 'ASC');
                }
            ])
            ->getForm();

When I try to create view from that form I got 2 exeptions in stack:

TransformationFailedException: Can not read the choices from the choice list.

in
vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataMapper/CheckboxListMapper.php at line 55

        try {
            $valueMap = array_flip($this->choiceList->getValuesForChoices($choices));
        } catch (\Exception $e) {
            throw new TransformationFailedException(
                'Can not read the choices from the choice list.',
                $e->getCode(),
                $e

and

ContextErrorException: Warning: spl_object_hash() expects parameter 1 to be object, string given

in
vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php at line 1243

     */
    public function isScheduledForInsert($entity)
    {
        return isset($this->entityInsertions[spl_object_hash($entity)]);
    }
    /**

Of course when you'll try to set data_class you got

The form's view data is expected to be an instance of class Namespace\SomeBundle\Entity\SomeEntity, but is a(n) array.

exactly like @adsc said in issue #14877

When I'll remove multiple => true option it works as it should.

I didn't found workaround for that yet. This is really pain in the ass when you have widget where user can pick specific entities in form (with checkboxes, not multiple select).

Any ideas @webmozart ?

@adampastusiak adampastusiak changed the title Forms with multiple and extended radiobuttons throw an error on Symfony 2.7 Forms with multiple and extended entity type throw an error on Symfony 2.7 Feb 9, 2016
@HeahDude
Copy link
Contributor
HeahDude commented Feb 9, 2016

Hi @adampastusiak, I could not reproduce your issue in 2.7.9.

Could you please use the code below to help debuging :

$form = $this->get('form.factory')->createNamedBuilder('', 'form')
                     ->add('entities', 'entity', [
                         'class' => 'Namespace\SomeBundle\Entity\SomeEntity',
                         'choice_label' => 'name',
                         'choice_value' => function ($choice) {

                             dump($choice); //  expect an entity with id from ids

                             return $choice;
                         },
                         'label' => 'Label',
                         'multiple' => true,
                         'expanded' => true,
                         'read_only' => true,
                         'query_builder' => function (EntityRepository $repository) use ($ids) {

                             dump($ids); // just to be sure,
                                         // and confirm they match the dumped choices

                             return $repository->createQueryBuilder('e')
                                               ->where('e.id IN(:ids)')
                                               ->setParameter(':ids', array_values($ids))
                                               ->orderBy('e.name', 'ASC');
                         }
                     ])
                     ->getForm();

The dumps may be useful to see il they query and the form mapping succeded before the exception.

Also you may want to provide some default data for the field 'entities' as multiple is true the form is expecting an array of entities.
There is two ways to do this easily :

$form = $this->get('form.factory')->createNamedBuilder('', 'form', array('entities' => array(new SomeEntity())

// or

    ->add('entities', 'entity', [
                         'class' => 'Namespace\SomeBundle\Entity\SomeEntity',
                         // ...
                         'data' => array(new Entity()),

@adampastusiak
Copy link
Author

Hi @HeahDude, thank you for your response.

I've used debug code you provide and everything seems to be ok in that dumps.
in

dump($choice);

i got first entity from query builder result (which is fine, this is entity what I expect). But I got error

Catchable Fatal Error: Object of class Namespace\SomeBundle\Entity\SomeEntityy could not be converted to string

which is also fine, because I don't have __toString method implemented in that entity. But what is surprising for me (maybe I don't know about something of how exactly the mechanism of forms works) but when I added getId() in dump

dump($choice->getId());

i got proper entity id in debug bar, but when I added that to return statement

return $choice->getId();

i got error:

Error: Call to a member function getId() on string

Of course that error is gone when I added

'data' => array(new Entity()),

like you told, but it isn't obvious - I didn't find such thing in any documentation I read.

And second dump gave me array of ids i want to pass to query builder. So also ok.

So to summarize:
after using your advices I got that working, but way of doing it isn't as clear and straightforward as it should be IMHO. Code which works is:

$form = $this->get('form.factory')->createNamedBuilder('', 'form')
->add('entities', 'entity', [
                'class' => 'Namespace\SomeBundle\Entity\SomeEntity',
                'choice_label' => 'name',
                'choice_value' => function ($choice) {
                    return $choice->getId();
                },
                'data' => [new Entity()],
                'label' => 'Label',
                'multiple' => true,
                'expanded' => true,
                'read_only' => true,
                'query_builder' => function (EntityRepository $repository) use ($ids) {
                    return $repository->createQueryBuilder('e')
                                               ->where('e.id IN(:ids)')
                                               ->setParameter(':ids', array_values($ids))
                                               ->orderBy('e.name', 'ASC');
                }
            ])
            ->getForm();

Of course

'data' => [new Entity()]

can be passed as createNamedBuilder argument as well, then code will look like this:

$form = $this->get('form.factory')->createNamedBuilder('', 'form', ['entities' => [new Entity()]])
->add('entities', 'entity', [
                'class' => 'Namespace\SomeBundle\Entity\SomeEntity',
                'choice_label' => 'name',
                'choice_value' => function ($choice) {
                    return $choice->getId();
                },
                'label' => 'Label',
                'multiple' => true,
                'expanded' => true,
                'read_only' => true,
                'query_builder' => function (EntityRepository $repository) use ($ids) {
                    return $repository->createQueryBuilder('e')
                                               ->where('e.id IN(:ids)')
                                               ->setParameter(':ids', array_values($ids))
                                               ->orderBy('e.name', 'ASC');
                }
            ])
            ->getForm();

Thank you very much for your help!

@HeahDude
Copy link
Contributor

@adampastusiak glad that works.

In fact when you define choice_value option in EntityType, behind the scene the callable is used for every choice "loaded" from db but also for the data default value provided, in that case null treated as an empty string ''. That's why I advised you to initalise the data with a new entity.

I know this is not obvious, but this is a best practice you can see in the first example of form builder usage from a controller in the book :

// src/AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;

use AppBundle\Entity\Task;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class DefaultController extends Controller
{
    public function newAction(Request $request)
    {
        // create a task and give it some dummy data for this example
        $task = new Task();
        $task->setTask('Write a blog post');
        $task->setDueDate(new \DateTime('tomorrow'));

        $form = $this->createFormBuilder($task) // See here the new Task() as default value
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->add('save', SubmitType::class, array('label' => 'Create Task'))
            ->getForm();

        return $this->render('default/new.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

Passing an entity as default value also automatically set the data_class option.

However what I don't know if it can be considered an issue is the fact that you may pass a null or empty array as default data and expect choice_value callable to not throw an exception.

I'm going to open an other issue to discuss this, you should close this one.

What you can do is open an issue in the symfony docs to remind to be more explicit about initialising data in forms.

Cheers

@HeahDude
Copy link
Contributor

Of courses this example of the book missing $form->handleRequest($request) which updates the state of the data in the form by synchronizing submitted data on top of default data. But you could do it in another controller.

@HeahDude
Copy link
Contributor

Status: Reviewed

@javiereguiluz
Copy link
Member

Closing it as solved thanks to the excellent help and guidance provided by @HeahDude. Thank you!

@xabbuh
Copy link
Member
xabbuh commented Feb 15, 2016

@adampastusiak By the way, having to pass an array for the data option should only be necessary when the multiple option is set to true.

@adampastusiak
Copy link
Author

@xabbuh To be honest I wasn't even aware, that I can pass array with object inside as data to FormType. When you provided that solution it has perfectly sense, but I didn't found anything about that in documentantion (maybe this is somewhere but I didn't ran into that).
Nevertheless, @HeahDude solved my problem very fast and I'm very grateful for that!

< 6778 /div>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants
0