1
votes

I have a Symfony 2 application, with a form that needs to store a reference to another entity (project) in a hidden field. The project entity is passed in via the form options, my plan was to have a field of the type 'hidden' that simply contains the entity id, this should then be transformed into a project entity on when the form is submitted.

I'm going about this by using a model transformer to transform between the entity to a string (it's ID). However when I try to view the form, I get the following error:

The form's view data is expected to be an instance of class Foo\BarBundle\Entity\Project, 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 Foo\BarBundle\Entity\Project.

Here is my form class:

<?php

namespace Foo\BarBundle\Form\SED\Waste;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Foo\BarBundle\Form\DataTransformer\EntityToEntityIdTransformer;

/**
 * Class WasteContractorEntryType
 * @package Foo\BarBundle\Form\CommunityInvestment\Base
 */
class WasteContractorEntryType extends AbstractType
{

    protected $name;

    protected $type;

    protected $phase;

    protected $wasteComponent;

    public function __construct($formName, $type, $phase, $wasteComponent)
    {
        $this->name = $formName;
        $this->type = $type;
        $this->phase = $phase;
        $this->wasteComponent = $wasteComponent;
    }

    /**
     * @return mixed
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @return mixed
     */
    public function getPhase()
    {
        return $this->phase;
    }

    /**
     * @return mixed
     */
    public function getProject()
    {
        return $this->project;
    }

    /**
     * @return mixed
     */
    public function getWasteComponent()
    {
        return $this->wasteComponent;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $em = $options['em'];

        $wasteComponentTransformer = new EntityToEntityIdTransformer($em,
            'Foo\BarBundle\Entity\SED\Waste\WasteComponent');

        $projectTransformer = new EntityToEntityIdTransformer($em, 'Foo\BarBundle\Entity\Project');

        $builder->add('id', 'hidden');

        $builder->add(
            $builder->create('project', 'hidden', array(
                'data' => $options['project'],
                'by_reference' => false
            ))
                ->addModelTransformer($projectTransformer)
        );

        $builder->add(
            $builder->create('wasteComponent', 'hidden', array(
                'data' => $this->getWasteComponent()
            ))
                ->addModelTransformer($wasteComponentTransformer)
        );

        $builder->add('phase', 'hidden', array(
            'data' => $this->getPhase()
        ));

        $builder->add('type', 'hidden', array(
            'data' => $this->getType()
        ));

        $builder->add('percentDivertedFromLandfill', 'text', array());

        $builder->add('wasteContractor', 'entity', array(
            'class' => 'Foo\BazBundle\Entity\Contractor',
            'property' => 'name',
            'attr' => array(
                'class' => 'js-select2'
            )
        ));
    }


    public function getName()
    {
        return $this->name;
    }

    /**
     * {@inheritDoc}
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'csrf_protection' => true,
            'data_class' => 'Foo\BarBundle\Entity\SED\Waste\WasteContractorEntry'
        ))
            ->setRequired(array(
                'em',
                'project'
            ))
            ->setAllowedTypes(array(
                'em' => 'Doctrine\Common\Persistence\ObjectManager',
                'project' => 'Foo\BarBundle\Entity\Project'
            ));

    }

} 

And my model transformer class:

<?php

namespace Foo\BarBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;

class EntityToEntityIdTransformer implements DataTransformerInterface
{
    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @var Entity class
     */
    protected $entityClass;

    /**
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om, $className)
    {
        $this->om = $om;
        $this->entityClass = $className;
    }

    protected function getEntityClass()
    {
        return $this->entityClass;
    }

    /**
     * Transforms an object (project) to a string (id).
     *
     * @param  Project|null $issue
     * @return string
     */
    public function transform($entity)
    {
        if (null === $entity) {
            return "";
        }

        return $entity->getId();
    }

    /**
     * Transforms a string (id) to an object (project).
     *
     * @param  string $id
     *
     * @return Issue|null
     *
     * @throws TransformationFailedException if object (project) is not found.
     */
    public function reverseTransform($id)
    {
        if (!$id) {
            return null;
        }

        $entity = $this->om
            ->getRepository($this->getEntityClass())
            ->find($id);

        if (null === $entity) {
            throw new TransformationFailedException(sprintf(
                'An entity of class %s with id "%s" does not exist!',
                $this->getEntityClass(),
                $id
            ));
        }

        return $entity;
    }
}

I've tried using a adding the transformer as a view transformer instead of a model transformer, however then I just get a slightly different error:

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

1
It seems that doing as suggested in the exception message and setting the data_class option to null does allow the form to render. I haven't got as far as processing the submission yet so not sure if the model transformer will do it's thing and leave me with an entity that correctly references the project entity, I will update on this later. - PiX06

1 Answers

0
votes

It seems that setting 'data_class' to null as suggested by the exception message above is the solution. I had previously rejected this as it seems counter-intuitive when we know that the purpose of the field is to reference a project entity.

With the 'data_class' option set to null, the hidden project field contains the project id, and upon submission, calling getProject() on the created entity returns the correct project object.