Using entity field type in symfony2.1 form

Alan Scott picture Alan Scott · Sep 22, 2012 · Viewed 20k times · Source

Using Symfony 2.1.3-dev and Doctrine 2.3

I am attempting to build a form which provide multiple options for a user to filter a returned set of data (Entity\EngineCodes). The form is composed of 1 text input field (id) and 3 select fields (module, type, status). I am attempting to use the Symfony2 entity form_type to generate values for the 3 select fields from the EngineCodes entity.

Since I am wanting to filter the table using a combination of any 3 of the select fields. Based on the 2.1 docs, I decided to create a FormType (EngineCodesFilterType) and set three of the form fields to entity type with query_builder statements to return a set of unique values for each of the fields.

Unfortunately, I am receiving the follow error, and I'm not exactly sure why it's returning an array instead of an object.

    The form's view data is expected to be an instance of class
    Vendor\IndexBundle\Entity\EngineCodes, but is a(n) array.
    You can avoid this error by setting the "data_class" option
    to null or by adding a view transformer that transforms a(n)
    array to an instance of Vendor\IndexBundle\Entity\EngineCodes.

If I set data_class to null, I receive this error:

    A "__toString()" method was not found on the objects of type
    "Vendor\IndexBundle\Entity\EngineCodes" passed to the choice
    field. To read a custom getter instead, set the option
    "property" to the desired property path.

Since I'm still learning these Symfony2 features, my goal was to match the 2.1 docs in terms of construction and format as much as possible.

Here is the function within the Controller:

public function displayAction() {

    // ...

    $entity = $this->getDoctrine()->getEntityManager()
        ->getRepository('VendorIndexBundle:EngineCodes')
        ->findAll();

    // ...

    $form = $this->createForm(new EngineCodesFilterType(), $entity);

    // ...

    return $this->render(
        'VendorIndexBundle::layout.html.twig',
        array(
            'entity'  => $entity,
            'form'    => $form->createView(),));

Here is the form type:

class EngineCodesFilterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add(
            'id',
            'integer',
            array(
                'label' => 'Code ID',));
        $builder->add(
            'status',
            'entity',
            array(
                'label' => 'Code Status',
                'class' => 'VendorIndexBundle:EngineCodes',
                'query_builder' => function(EntityRepository $er)
                    {
                        return $er->createQueryBuilder('u')
                            ->select('u.status')
                            ->add('groupBy', 'u.status');
                    },
                'multiple' => true,));
        $builder->add(
            'type',
            'entity',
            array(
                'label' => 'Code Type',
                'class' => 'VendorIndexBundle:EngineCodes',
                'query_builder' => function(EntityRepository $er)
                    {
                        return $er->createQueryBuilder('u')
                            ->select('u.type')
                            ->add('groupBy' ,'u.type');
                    },
                'multiple' => true,));
        $builder->add(
            'module',
            'entity',
            array(
                'label' => 'Code Module',
                'class' => 'VendorIndexBundle:EngineCodes',
                'query_builder' => function(EntityRepository $er)
                    {
                        return $er->createQueryBuilder('u')
                            ->select('u.module')
                            ->add('groupBy', 'u.module');
                    },
                'multiple' => true,));
    }

    public function getName()
    {
        return 'EngineCodesFilter';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(
            array(
                'data_class'        => 'Vendor\IndexBundle\Entity\EngineCodes',
              /*'data_class'        => null,*/
                'validation_groups' => 'filter',));
    }
}

And here is the Vendor\Entity\EngineCodes class:

/**
 * Vendor\IndexBundle\Entity\EngineCodes
 *
 * @ORM\Table(name="engine_codes")
 * @ORM\Entity(repositoryClass="Vendor\IndexBundle\Entity\EngineCodesRepository")
 * @UniqueEntity(fields="id", message="ID already in use! Enter a unique ID for the code.")
 */
class EngineCodes
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer", nullable=false, unique=true)
     * @ORM\Id
     * @Assert\NotBlank(message="ID cannot be blank!")
     * @Assert\Regex(pattern="/^\d+$/", match=true, message="ID must be an integer!")
     * @Assert\MinLength(limit=8, message="ID must be 8 numbers in length!")
     * @Assert\MaxLength(limit=8, message="ID must be 8 numbers in length!")
     */
    private $id;

    /**
     * @var string $token
     *
     * @ORM\Column(name="token", type="string", length=255, nullable=false, unique=true)
     */
    private $token;

    /**
     * @var boolean $status
     *
     * @ORM\Column(name="status", type="integer", nullable=false)
     * @Assert\NotBlank(message="Status cannot be blank!")
     */
    private $status;

    /**
     * @var string $module
     *
     * @ORM\Column(name="module", type="string", length=255, nullable=false)
     * @Assert\NotBlank(message="Module cannot be blank!")
     */
    private $module;

    /**
     * @var string $submodule
     *
     * @ORM\Column(name="submodule", type="string", length=255, nullable=false)
     * @Assert\NotBlank(message="Submodule cannot be blank!")
     */
    private $submodule;

    /**
     * @var string $type
     *
     * @ORM\Column(name="type", type="string", length=255, nullable=false)
     * @Assert\NotBlank(message="Type cannot be blank!")
     */
    private $type;

    /**
     * @var string $description
     *
     * @ORM\Column(name="description", type="text", nullable=false)
     * @Assert\NotBlank(message="Description cannot be blank!")
     */
    private $description;

    /**
     * @var string $title
     *
     * @ORM\Column(name="title", type="string", length=255, nullable=false)
     * @Assert\NotBlank(message="Title cannot be blank!")
     */
    private $title;

    /**
     * @var string $definition
     *
     * @ORM\Column(name="definition", type="text", nullable=true)
     */
    private $definition;

    /**
     * @var string $color
     *
     * @ORM\Column(name="color", type="string", length=10, nullable=true)
     */
    private $color;

    /**
     * @var \DateTime $createTimestamp
     *
     * @ORM\Column(name="create_timestamp", type="datetime", nullable=false)
     */
    private $createTimestamp;

    /**
     * @var Accounts
     *
     * @ORM\ManyToOne(targetEntity="Accounts")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="create_account_fk", referencedColumnName="id")
     * })
     */
    private $createAccountFk;


    // getters and setters ...

    /**
     * Set createAccountFk
     *
     * @param Vendor\IndexBundle\Entity\Accounts $createAccountFk
     * @return EngineCodes
     */
    public function setCreateAccountFk(\Vendor\IndexBundle\Entity\Accounts $createAccountFk = null)
    {
        $this->createAccountFk = $createAccountFk;

        return $this;
    }

    /**
     * @ORM\PrePersist
     */
    public function setCreateTimestampValue()
    {
        $this->createTimestamp = new \DateTime();
    }
}

Answer

Carlos Granados picture Carlos Granados · Sep 22, 2012

Your first problem is that $entity is not a single entity, but rather an array of entities (which is what is returned by the findAll() method). When you defined the form type, you said that you expected to build the form from an entity (that is what the data_class option is for) and that is why you get the first error.

If you set data_class to null you are saying that you don't expect the form to be created from an entity, so it will accept your array of entities and not complain. But, why are you passing an array of entities to the form type? This is just a filter form which allows you to choose four possible values to filter your entities. This does not need an array of entities as its underlying data. If you think you need it to get the values for the code, type and status fields, this is not so as they are already fetched with your query builders. So your controller code should be just:

public function displayAction() {

// ...

$entity = $this->getDoctrine()->getEntityManager()
    ->getRepository('VendorIndexBundle:EngineCodes')
    ->findAll();

// ...

$form = $this->createForm(new EngineCodesFilterType());

// ...

return $this->render( // ...

Then you get the other error because you are adding three form fields and each one lets you choose from an entity list. But, how do you "show" this entity? Symfony does not know which field it should show you to represent the entity, so it throws this error.

This error could be fixed by adding a __toString() method to the EngineCodes class, which just says "hey, this how I want to show this class", but though the error will not be thrown it will not work as expected since each of the three fields wants to show a different property.

Another solution is to use the property option of the form field to say which property of the underlying object you want to use to display the data.

For example:

$builder->add(
        'status',
        'entity',
        array(
            'label' => 'Code Status',
            'class' => 'VendorIndexBundle:EngineCodes',
            'property' => 'status'
            'query_builder' => function(EntityRepository $er)
                {
                    return $er->createQueryBuilder('u')
                        ->select('u.status')
                        ->add('groupBy', 'u.status');
                },
            'multiple' => true,));