2
votes

Has anyone encountered this problem when using doctrine embeddables and symfony forms?

if you don't know Doctrine Embeddables are, you can read about it here http://doctrine-orm.readthedocs.org/en/latest/tutorials/embeddables.html

When using value object (CategoryType in my case) with symfony form on form submission (during persisting to DB) I get the following warning

Warning: ReflectionProperty::getValue() expects parameter 1 to be object, string given

This happens because symfony form returns string instead of embeddable object.

The only workaround I have right now is to use mapped => false on type field and create valid embeddable object inside controller action just before persist. But that's far from "nice" solution and I want to avoid that.

My Entity, Value Object and form (simplified for the sake of question)

Foo\BarBundle\Entity\CategoryType

<?php

namespace Foo\BarBundle\Entity;

class CategoryType
{
    /**
     * @var string
     */
    protected $value;

    public function __toString()
    {
        return (string) $this->getValue();
    }

    /**
     * @return string
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * @param string $value
     *
     * @return $this
     */
    public function setValue($value)
    {
        $this->value = $value;

        return $this;
    }
}

Foo\BarBundle\Resources\config\doctrine\CategoryType.orm.yml

CategoryType.orm.yml
    Foo\BarBundle\Entity\CategoryType:
        type: embeddable
        fields:
            value:
                type: string

Foo\BarBundle\Entity\Category

<?php

namespace Foo\BarBundle\Entity;

class Category
{
    /**
     * @var integer
     */
    protected $id;
    /**
     * @var string
     */
    protected $name;
    /**
     * @var CategoryType
     */
    protected $type;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return \Foo\BarBundle\Entity\CategoryType
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @param \Foo\BarBundle\Entity\CategoryType $type
     *
     * @return $this
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return (string) $this->getName();
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param string $name
     *
     * @return $this
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }
}

Foo\BarBundle\Resources\config\doctrine\Category.orm.yml

Foo\BarBundle\Entity\Category:
    type: entity
    fields:
        id:
            type: integer
            id: true
            generator:
                strategy: AUTO
        name:
            type: string
    embedded:
        type:
            class: CategoryType

Foo\BarBundle\Form\CategoryType

<?php

namespace Foo\BarBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class CategoryType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('type')
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(
            array(
                'data_class' => 'Foo\BarBundle\Entity\Category'
            )
        );
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'category_form';
    }
}
1

1 Answers

4
votes

The doctrine embedabble behavior is just a persistence mechanism, so, it can not break the form component which is independant from it (and works with all objects graph).

The issue is your form is not well designed (it does not follow your object graph). Here, you have a category which wraps a category type. So, your form must follow the same structure at the definition level.

You must create a CategoryTypeType form (mapped to your CategoryType class) where your add a value field. Then, in your CategoryType (the form one), you must embed the CategoryTypeType for the type field.

Then, the form component will automatically creates a Category which wraps a CategoryType. Then, Doctrine will simply persists your object as embeddable and everything will work :)