Search code examples
formssymfonysymfony-3.1

Symfony form hidden relation to foreign entity


I am trying to create a form in symfony 3. It has a hidden field for a relation to another entity. It is defined as

$builder->add('course', HiddenType::class, array('property_path' => 'course.id'))

Doing so when i submit the form i get an error, because the id property is not visible and there's no setter obviously.

Could not determine access type for property "id".

What should i do? Adding a setter for id would probably be a bad idea. There's a reason why there is none by default. Other fields than the id are not unique so i cannot use any of those.

I guess one option might be a custom type, that queries the database for the referring entity. But is there a simpler way for this pretty standard use case?

Edit

Okay obviously my question was misunderstood. So i'll explain better. It's not about Doctrine. My Datamodel is fine, the field is a proper relation and not something else. The problem is only about the Symfony Form component, specifically that HiddenField seemingly cannot handle a relation by default. First it would result in an error upon display of the Form Cannot serialize type Model to string, which i can solve with the property_path directive for the display of the form. But once it is submitted it cannot create the related entity because it cannot set the id property (see the property_path) of the related doctrine entity.

A working solution is to use a DataTransformerInterface class instead of something like the property path. But one must implement one for every entity needed. So i was wondering if there was a easier solution because it seemed to me to be a pretty standard usecase.


Solution

  • Here is what i did:

    I used a Symfony\Component\Form\DataTransformerInterface on the field, to serialize the model and then upon form transmission load it again from the database via doctrine. Probably not what everybody wants in every case and also means the form needs the entity manager as an option.

    In the form

    /**
     * Some form
     */
    class SomeType extends AbstractType
    {
        // Configure 
        public function configureOptions(OptionsResolver $resolver) {
            [...]
            $resolver->setRequired('entity_manager');
        }
    
        public function buildForm(FormBuilderInterface $builder, array $options) {
            $builder->add('course', HiddenType::class);
    
            [...]
    
            $builder->get('course')
               ->addModelTransformer(new CourseTransformer($options['entity_manager']));
        }
    }
    

    And here is the CourseTransformer

    class CourseTransformer implements DataTransformerInterface
    {
        // The object manager
        private $manager;
    
        //Constructor
        public function __construct(ObjectManager $manager) {
            $this->manager = $manager;
        }
    
        /**
         * Transforms an object (course) to a string (id).
         */
        public function transform($course)
        {
            if (null === $course) return '';
            return $course->getId();
        }
    
        /**
         * Transforms a string (id) to an object (course).
         */
        public function reverseTransform($courseId)
        {
            // no course id? It's optional, so that's ok
            if (!$courseId) {
                return;
            }
    
            $course = $this->manager
                ->getRepository('MyBundle:Course')
                // query for the issue with this id
                ->find($courseId)
            ;
    
            if (null === $course) {
                // causes a validation error
                // this message is not shown to the user
                throw new TransformationFailedException(sprintf(
                    'An course with id "%s" does not exist!',
                    $courseId
                ));
            }
    
            return $course;
        }
    }