13

Short Story (Edit)

It is possible to store an array instead of a mapped association. In Symfony2, this is fairly easy using the collection Field Type. For example, using this technique, you could store an array of text fields that populate an array events field. However, to update an array, there is a trick, and this trick is beautifully explained by @Vadim Ashikhman in the accepted answer.

Long Story

Sometimes it is useful and more efficient to store an array instead of a mapped association. However, once created, it remains complicated to update this Array if the size of that array does not change?

Many people have a similar issue but nobody found a proper solution to this problem.

Store an array

A team can organise many events. These events are simply stored within an array using Doctrine instead of using a OneToMany association. Therefore, the entity Event is not mapped with Doctrine.

Entity Event (not mapped with Doctrine)

<?php

namespace Acme\TestBundle\Entity;

...

class Event
{

    /**
     * @Assert\NotBlank
     */
    private $name;

    public function setName($name)
    {
        $this->name = $name;
    }

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


}

Entity Team

<?php

namespace Acme\TestBundle\Entity;

...

/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks
 * @ORM\Table(name="teams")  
 */
class Team 
{

/**
     * @ORM\Column(type="array")
     * @var array
     */
    protected $events;


    public function addEvent($event)
    {
        if (!in_array($event, $this->events, true)) {
            $this->events[] = $event;
        }

        return $this;
    }

    public function removeEvent($event)
    {
        if (false !== $key = array_search($event, $this->events, true)) {
            unset($this->events[$key]);
            $this->events = array_values($this->events);
        }

        return $this;
    }

    public function getEvents()
    {
        return $this->events;
    }

    public function setEvents(array $events)
    {
        $this->events = array();

        foreach ($events as $event) {
            $this->addEvent($event);
        }

        return $this;
    }

Event Form

<?php
namespace Acme\TestBundle\Form\Type;

...

class EventType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);

        $builder->add('name', 'text');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\TestBundle\Entity\Event',
            'cascade_validation' => true,
        ));
    }

    ...
}

Team Form

<?php

namespace Acme\TestBundle\Form\Type;

...

class TeamType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);

        $builder->add('events','collection', array(
            'type' => new EventType(),
            'allow_add'   => true,
            'allow_delete' => true,
            'prototype' => true,
            'by_reference' => false,
            'options' => array('data_class' => 'Acme\TestBundle\Entity\Event'),
            )
        );

    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\TestBundle\Entity\Team',
        ));
    }
    ...

}

The Controller

/**
 * Update a team
 *
 * @Route("update/{team_id}", name="updateTeamFromId")
 * @Template("AcmeTestBundle:Team:teamUpdate.html.twig")
 */
public function updateTeamAction($team_id, Request $request)
{

    $em = $this->getDoctrine()->getManager();

    $repository= $em->getRepository('AcmeTestBundle:Team');

    $team_to_update = $repository->find($team_id);

    $form = $this->createForm(new teamType(), $team_to_update);

    if ($request->getMethod() == 'POST')
    {
        $form->bind($request);

        if ($form->isValid()){

            $em->persist($team_to_update);
            $em->flush();

            return $this->redirect($this->generateUrl('homepage'))  ;
        }
    }

    return array(
    'form' => $form->createView(),
    'team_id' => $team_id,
    );

}
Community
  • 1
  • 1
Mick
  • 30,759
  • 16
  • 111
  • 130
  • 1
    It could be an error of "owning side" and "inversed side". Could you paste us the associations between those entities? – DonCallisto Nov 05 '12 at 09:01
  • Thanks @DonCallisto, but I am storing the events in an array. There is no association. The entities are entirely posted here. Thanks fery much for your help – Mick Nov 05 '12 at 10:57
  • Can you add a `var_dump($team_to_update->getEvents());` and a `var_dump($form->getData()->getEvents());`inside the `if ($form->isValid()) { .. }` block and show the output of both – Mats Rietdijk Nov 05 '12 at 11:35
  • Yep, the crazy thing is that they are the same and contain the updated values! `$team_to_update` contains the correct values before being persisted, but because the size of the array does not change, the values are not modified by doctrine. This [post](http://stackoverflow.com/questions/13227658/doctrine-does-not-update-a-simple-array-type-field) recommends to change the tracking policies of Doctrine, but does not seem to work. – Mick Nov 05 '12 at 11:46

1 Answers1

2

You can try this inside the controller:

public function updateTeamAction($team_id, Request $request)
{

    $em = $this->getDoctrine()->getManager();

    $repository= $em->getRepository('AcmeTestBundle:Team');

    $team_to_update = $repository->find($team_id);

    $form = $this->createForm(new teamType(), $team_to_update);

    if ($request->getMethod() == 'POST')
    {
        $form->bind($request);

        if ($form->isValid()){
            $events = $team_to_update->getEvents();
            foreach($events as $key => $value){
                $team_to_update->removeEvent($key);
            }
            $em->flush();
            $team_to_update->setEvents($events);
            $em->persist($team_to_update);
            $em->flush();

            return $this->redirect($this->generateUrl('homepage'))  ;
        }
    }

    return array(
    'form' => $form->createView(),
    'team_id' => $team_id,
    );

}

There is probably a beter way to do this and i know this isnt a nice way to do it but till you (or someone else) finds that solution you can use this as a temporary fix.

Mats Rietdijk
  • 2,576
  • 3
  • 20
  • 25
  • I see, but with your issue your not updating and object in an array but a string value so I guess `clone` doesn't work for you as it can't be used for strings (asfar as I know)? – Mats Rietdijk Nov 05 '12 at 12:49
  • It is possible like this: `$i=0; foreach ($events as $event){ $events[$i] = clone $event; $i++;}` – Mick Nov 05 '12 at 12:52