Programming

Creating embedded forms in Symfony 2.0

If you are looking to create a form from 2 or more entities, like i have been then you probably find the documentation somewhat lacking and confusing. If you follow the guides you can get your form working if you start from the least dependant entity making that the first entity you use to build your form from your controller. I find if you start from the most dependant you usually get all manner of errors.

What i have is a basic forum i am working on, in my current form situation i have 3 entities, Board entity, a Thread entity and a Post entity. If you start by creating your form with Board then chances are it won’t work, i created my forms in separate ‘Type’ classes (PostType.php ThreadType.php and BoardType.php) and then created my PostType form in my controller, which then sorts out its dependencies on its own.

My controller class is very basic and not very polished, but here is what it looks like:

// In your controller 
public function createAction($board_id)
{
	$board = $this->getDoctrine()
		->getRepository('CodeConsortiumForumBundle:Board')
		->find($board_id);

	$post = new post();

	$form = $this->createForm(new PostType(), $post);

	$formHandler = new PostFormHandler($form, $this->getRequest(), $post);

	if ($formHandler->process()) 
	{
		$em = $this->get('doctrine')->getEntityManager();
		$em->persist($form->getData());
		$em->flush();

		//return $this->redirect($this->generateUrl('thread_show', array( 'thread_id' => $thread_id ) ));
	}
	else
	{
		return $this->render('CodeConsortiumForumBundle:Thread:create.html.twig', array(
			'board' => $board,
			'form' => $form->createView(),
			));
		}

	}
}

It probably looks somewhat messy as i say, its a rough work in progress, but it works, and here are the FormTypes i used, starting with; PostType.php:

options = $options;
	}

	public function buildForm(FormBuilder $builder, array $options)
	{
		$builder->add('Thread', new ThreadType($this->options) );
		$builder->add('body');
	}

	// for creating and replying to threads
	public function getDefaultOptions(array $options)
	{
		return array(
			'data_class' => 'CodeConsortium\ForumBundle\Entity\Post',
		);
	}

	public function getName()
	{
		return 'Post';
	}

}

ThreadType.php

options = $options;
	}

	public function buildForm(FormBuilder $builder, array $options)
	{
		$builder->add('Thread', new ThreadType($this->options) );
		$builder->add('body');
	}

	// for creating and replying to threads
	public function getDefaultOptions(array $options)
	{
		return array(
			'data_class' => 'CodeConsortium\ForumBundle\Entity\Post',
		);
	}

	public function getName()
	{
		return 'Post';
	}

}

And lastly, the BoardType.php

options = $options;
	}

	public function buildForm(FormBuilder $builder, array $options)
	{

	}

	// for creating and replying to threads
	public function getDefaultOptions(array $options)
	{
		return array(
			'data_class' => 'CodeConsortium\ForumBundle\Entity\Board',
		);
	}

	public function getName()
	{
		return 'Board';
	}

}

Notice how i added a constructor, this is so we can easily chain feed in from the first object any changes to the form, such as wether we wish to bother including a field or not, we can opt to have any field removed if the form is used under different circumstances such as different access controls for different users with different permissions or if the form is used in another way on the site.

One more thing we need to make sure, is that all the references in our entities make use of the cascade={“persist”} option so that it maintains relations between the relevant forms as new ones get processed, so if say a thread does not exist then when processing the post, it should create one and do the association for us.

Here is an example of a snip from my Post entity class Post.php

 /**
 * @ORM\ManyToOne(targetEntity="Thread", inversedBy="posts", cascade={"persist"})
 * @ORM\JoinColumn(name="thread_id", referencedColumnName="id")
 */
 protected $thread;

Lastly, you will need a formHandler if your not working in your controller, if using my example code then the postFormHandler.php looks like this:

form = $form;
		$this->request = $request;
		$this->post = $post;
	}

	public function process()
	{
		//$form = $this->createForm(new PostType(), $this->post);
		//$this->form->setData($this->post);

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

			if ($this->form->isValid())
			{ 

				return true; 
			}

		}

		return false;
	}

}