1

Doctrine2 has a built-in type 'array' that I find useful for my project. It perfectly works with arrays of scalar types. But now I want to use an array of objects. Something like this:

/**
 * @ORM\Entity
 */
class MyEntity {

    /**
     * @var MyEntityParameter[] array of MyEntityParameter instances
     *
     * @ORM\Column(name="parameters", type="array", nullable=true)
     *
     */
    private $parameters;

}

Where MyEntityParameter is a class that can be serialized. I also use it in the Symfony's Form Builder.

My plan works perfectly, except that when the field in the MyEntityParameter instance gets changed, Doctrine doesn't detect it and thus doesn't update the record. If I delete or add array elements, Doctrine detects that. I realize that this happens because class instance object id doesn't change when I change its field, but then how can I make it so that Doctrine detects this change?

3
  • 1
    Just to confirm, is $parameters an instance of MyEntityParameter or is it an array of instances of MyEntityParameter ? Commented Nov 3, 2013 at 19:27
  • It is an array of instances of MyEntityParameter, I'll clarify this in the question. Commented Nov 3, 2013 at 19:31
  • Cool, I've edited my answer to address both cases Commented Nov 3, 2013 at 19:40

2 Answers 2

1

I found a working solution for me. I don't think it's that elegant, but in case if there are no good ways to solve this problem it can work for me and for others.

First of all, I decided not to keep objects in the array, but instead keep arrays. I, however, still want to use MyEntityParameter class in the Symfony's form builder. In this case, the idea is to disable mapping for our field:

In the form builder we do the following:

// Acme/Bundle/DemoBundle/Form/Type/MyEntityType.php

// ...

class MyEntityType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('parameters', 'collection', array(
            'mapped' => false, // do not map this field
            'type' => new MyEntityParameterType(),
            // ... other options ...
        ));
    }

}

In the child type (MyEntityParameterType) we set 'data_class' to MyEntityParameter (I don't show the code, as it's not related to the problem).

Now all we need is to manually fill and process the data for this not-mapped field.

In the controller:

public function editAction($id, Request $request)
{
    // ...

    // $object is an instance of MyEntity
    $form = $this->createForm(new MyEntityType(), $object);

    $parameters = $object->getParameters();
    if ($parameters) {
        foreach ($parameters as $key => $parameter)
        {
            $form->get('parameters')->add($key, new MyEntityParameterType(),
                array(
                   // here I assume that the constructor of MyEntityParameter
                   // accepts the field data in an array format
                  'data' => new MyEntityParameter($parameter),
                )
            );
        }
    }

    if ($request->isMethod('POST')) {
        $form->submit($request);
        $parameters = array();
        foreach ($form->get('parameters')->all() as $parameter) {
            // here first getData() gives MyEntityParameter instance
            // and the second getData() is just a method of MyEntityParameter
            // that returns all the fields in an array format
            $parameters[] = $parameter->getData()->getData();
        }
        $object->setParameters($parameters);
        // if the parameters were changed in the form,
        // this change will be detected by UnitOfWork
    }

    // ...
}
Sign up to request clarification or add additional context in comments.

Comments

0

I remember coming across this issue before, and a workaround that worked for me was to set a new object so Doctrine would recognise the entity property has been modified, then set the object that has the changes you want persisted.

$parameters = $entity->getParameters();
$parameters->foo = "bar";

$entity->setParameters(new MyEntityParameter());
$entity->setParameters($parameters);

$em->persist($entity);

If it's an array of MyEntityParameter instances then the following code might work.

$parameters = $entity->getParameters();
$parameters[3]->foo = "bar"; // Just an example

$entity->setParameters(array(new MyEntityParameter()));
$entity->setParameters($parameters);

$em->persist($entity);

3 Comments

I checked your solution, but unfortunately it doesn't work for me. And, honestly, I don't think Doctrine tracks this set call. Instead, UnitOfWork calculates the changeset, but it is not changed as the array elements didn't change (no new or deleted, no changes to the spl_object_hash() of the objects in the array).
This is a tricky one. You could call persist method twice so the UnitOfWork changeset is recalculated, or even call $em->getUnitOfWork()->recomputeSingleEntityChangeSet() directly, though this all sounds a little hackish.
Unfortunately, recompute will not work as well, because there is nothing to recompute (doctrine won't recognize object changes if its hash is the same and the object itself is not a trackable by Doctrine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.