3
votes

I'm new to symfony and still learning, my question is how do I populate a select drop-down in a form with an static array of choices. Say I have a class named Cake, I'd like to be able to fill a drop-down for the status of Cake from the array statuses created in the same CakeEntity:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\CakeRepository")
 */
class Cake
{
    /**
     * @ORM\Column(type="string", length=50)
     */

    private $status;

    private $statuses = array(
        'not_ready' => 'Not Ready',
        'almost_ready' => 'Almost Ready',
        'ready'=>'Ready',
        'too_late'=>'Too late'
    );
    public function getStatus(): ?string
    {
        return $this->status;
    }

    public function setStatus(string $status): self
    {
        $this->status = $status;
        return $this;
    }

    public function getStatuses()
    {
       return $this->statuses;
    }
}

My Controller looks like:

namespace App\Controller;

use App\Entity\Cake;
use App\Form\CakeType;
use App\Repository\CakeRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;


/**
 * @Route("/cake")
 */
class CakeController extends AbstractController
{
    /**
     * @Route("/new", name="cake_new", methods={"GET","POST"})
     */
    public function new(Request $request): Response
    {
        $cake = new Cake();
        $form = $this->createForm(CakeType::class, $cake);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $cake->setCreatedAt(\DateTime::createFromFormat('d-m-Y', date('d-m-Y')));
            $cake->setCreatedBy(1);
            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($cake);
            $entityManager->flush();

            return $this->redirectToRoute('cake_index');
        }

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

My CakeEntity:

<?php

namespace App\Form;

use App\Entity\cake;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class CakeType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        ->add('status', ChoiceType::class,
            [
                'choices'=>function(?Cake $cake) {
                    return $cake->getStatuses();
                }
            ]);
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Cake::class,
        ]);
    }
}

When trying to browse /cake/new I get the error:

An error has occurred resolving the options of the form "Symfony\Component\Form\Extension\Core\Type\ChoiceType": The option "choices" with value Closure is expected to be of type "null" or "array" or "\Traversable", but is of type "Closure".

1
The error literally tells you what's wrong, you're supplying a Closure where array or Traversable is expected. It is advisable to read Symfony docs as they can give you guidance, for example the FormType documentation about choices. So what you need to do is give the options directly, not as a result of a function.El_Vanja
I would also advise you to make the choices and their getter static so you don't need a specific instance of Cake to get them, since they're always the same regardless of the value of any instance of the class.El_Vanja

1 Answers

3
votes

You could declare getStatuses on Cake as static, or use public constants. E.g.:

class Cake
{
    // with static variables

    private static $statuses = [
        'not_ready'    => 'Not Ready',
        'almost_ready' => 'Almost Ready',
        'ready'        => 'Ready',
        'too_late'     => 'Too late',
    ];

    public static function getStatuses()
    {
        return self::$statuses;
    }

    // or with public const

    public const STATUSES = [
        'not_ready'    => 'Not Ready',
        'almost_ready' => 'Almost Ready',
        'ready'        => 'Ready',
        'too_late'     => 'Too late',
    ];
}

This seems reasonable, as the return value is not instance but class specific.


You could then use:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('status', ChoiceType::class, [
        'choices'=> Cake::getStatuses(),
    ]);

    // or

    $builder->add('status', ChoiceType::class, [
        'choices'=> Cake::STATUSES,
    ]);
}

If the choices actually depend on a given Cake instance, you could pass it via the options array or use form events.