0

I stumbled upon an odd behavior and I'm still not sure if my solution is the most appropriate one, even though it works now.

I have 2 entities:

class Recipe
{
    /** [...] */
    public $id;

    /** @ORM\Column(type="string", length=255) */
    public $name;

    /** @ORM\ManyToOne(targetEntity="App\Entity\Location") */
    public $location;
}

class Location
{
    /** [...] */
    public $id;

    /** @ORM\Column(type="string", length=255) */
    public $name;

    /** @ORM\OneToMany(targetEntity="App\Entity\Recipe", mappedBy="location") */
    protected $recipes;
}

Nothing fancy here. A location can hold several recipes, and a recipe can be in one location at most.

The recipe form is built as follows:

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add(
                'name',
                TextType::class,
                ['label' => 'Name',]
            )
            ->add(
                'location',
                EntityType::class,
                [
                    'label' => 'Location',
                    'class' => \App\Entity\Location::class,
                    'choice_value' => function ($location) {
                        // Why is this code necessary?
                        return is_object($location)
                            ? $location->getId()    // Object passed (when building choices)
                            : $location;            // int value passed (when checking for selection)
                    },
                    'choice_label' => 'name',
                ]
            )
        ;
    }

Then the controller creates the form and so on.

    /**
     * @ParamConverter("entity", class="App:Recipe", isOptional="true")
     */
    public function edit(Request $request, object $entity = null) {
        $form = $this->createForm(\App\Form\Recipe::class, $entity ?? new Recipe());
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            // ...
        }
        // ...
    }

My original implementation didn't have the choice_value callback above on the EntityType form element, and the option was never selected when I opened an existing location. But apart from that, everything was working as expected and selecting a value did save it correctly in the database, without more code but Symfony's magic.

Could you tell me why this choice_value is necessary here? Did I miss something? Why is the value passed as argument is sometimes an object, and sometimes an integer?

3 Answers 3

1

I don't know if it is the cause of the problem, but for EntityType field, you don't need to manually set the choice_value.

From Symfony docs:

In the EntityType, this is overridden to use the id by default. When the id is used, Doctrine only queries for the objects for the ids that were actually submitted.

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

Comments

0

Normally it's not necessary.. Maybe because your relation between entities is uni-directional ?

Example that works:

In entity Level

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\Level", inversedBy="steps")
 */
private $level;

In entity Step

/**
 * @ORM\OneToMany(targetEntity="App\Entity\Step", mappedBy="level")
 */
private $steps;

Then StepType :

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('level', EntityType::class, [
            'label' => 'Related level',
            'class' => Level::class,
            'choice_label' => 'position'
        ])
    ;
}

1 Comment

Well, that was my understanding too. About the uni-directional link, I may have over-simplified my code, because I do have it. I've edited my post accordingly.
0

Oh... Found it. Actually I had an old DataTransformer that was associated to this field and it was messing with the values. I didn't need it anymore anyway so just removing it magically fixed the bug.

Thanks for your help anyway, it did confirm that something wasn't right and forced me to dig deeper.

Comments

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.