Symfony 2: Adding A Selection List of Entities


The examples from the documentation on the Symfony 2 site did not explain how one could render a list of entities in the form of say a select html drop down for an object that has a one-to-many type of relationship (the sample showed a product to category relationship). Instead, what was shown was embedding the category form inside of the product form. However, if you have a similar situation and required a more complex form to manage the category aspect, how would you do it? This blog post will attempt to address the issue.

The main four classes you will be concerned with are the product, product type, category type and category classes. To keep it simple, we will limit the category object to have only three main properties (id, name and description) along with the collection that points to product while product will only contain a few properties (id, description, name and the category).

Here is the category object (Category.php):

namespace Ecommerce\ShoppingBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * 
 * @ORM\Entity(repositoryClass="Ecommerce\ShoppingBundle\Entity\CategoryRepository")
 * @ORM\Table(name="categories")
 */
class Category{
	/**
	 * @ORM\Id
	 * @ORM\Column(type="integer")
	 * @ORM\GeneratedValue(strategy="AUTO")
	 */
	protected $id;

	/**
	 * @Assert\NotBlank()
	 * @Assert\Length(
	 * 	min = "2",
	 *  max = "150"
	 * )
	 * @ORM\Column(type="string", length=150)
	 */
	protected $name;

	/**
	 * @Assert\NotBlank()
	 * @Assert\Length(
	 *  min = "1",
	 *  max = "300"
	 * )
	 * @ORM\Column(type="string", length=300)
	 */
	protected $description

	/**
	 * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
	 */
	protected $products;

	public function __construct()
	{
		$this->products = new ArrayCollection();
	}

        public function __toString()
        {
    	        return "{$this->getName()} {$this->getDescription()}";
        }

I’m going to skip all the getter/setter methods that are auto generated. One thing I want to mention in this class that’s important is the __toString() method. This method MUST be implemented when you do this in a choice type of form field type. The idea is that this is what the user will see for whatever choice they will be making. So you can customize what is outputted here.

Another important aspect is that the __construct() method is defined and that categories is assigned a new ArrayCollection. Also, take note of how the mapping is done on the actual categories data member. This provides the one-to-many mapping to the products entity. Thus, when you have a category, you can get all the related products.

Next, we will define the product class (Product.php):

namespace Ecommerce\ShoppingBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * 
 * @ORM\Entity(repositoryClass="Ecommerce\ShoppingBundle\Entity\ProductRepository")
 * @ORM\Table(name="products")
 */
class Product
{
	/**
	 * @ORM\Id
	 * @ORM\Column(type="integer")
	 * @ORM\GeneratedValue(strategy="AUTO")
	 */
	protected $id;

	/**
	 * @Assert\NotBlank()
	 * @Assert\Length(
	 * 	min = "2",
	 *  max = "150"
	 * )
	 * @ORM\Column(type="string", length=150)
	 * 
	 */
	protected $name;

	/**
	 * @Assert\NotBlank()
	 * @Assert\Length(
	 * 	min="1",
	 *  max="300"
	 * )
	 * @ORM\Column(type="string", length=300)
	 */
	protected $description;

	/**
	 * 
	 * @ORM\ManyToOne(targetEntity="category", inversedBy="products")
	 * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
	 */
	protected $category;

Again, I will skip all the getter/setter methods. The important aspect in this class is the $category data member. Here, we can see the mapping back to the category class/table as a many-to-one relationship and we can see that it’s the inverse as defined through its attributes. One key here though is that we can see the join column, which is category_id. Through annotations, this will become a column in the products table once the schema is updated (perhaps using migrations).

With these two entities defined, we can then go on to create their respective form/type objects. First, let’s start with the category type (CategoryType.php):

namespace Ecommerce\ShoppingBundle\Form;

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

class CategoryType extends AbstractType
{
	public function buildForm(FormBuilderInterface $b, array $options)
	{
		$b->add('name', 'text')
		$b->add('description', 'text')	
		   ->add('save', 'submit');
	}

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

	public function setDefaultOptions(OptionsResolverInterface $r)
	{
		$r->setDefaults(array(
			'data_class' => 'Ecommerce\ShoppingBundle\Entity\Category'
		));
	}
}

The buildForm method isn’t really special. However, the most important aspect to this class is the setDefaultOptions method. This method is required if you want to convert your entity into a list of options (like in a select drop down). It’s fairly straight forward in that you just have to map it to your corresponding entity class. That will set the stage for your product type class.

namespace Ecommerce\ShoppingBundle\Form;

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

class ProductType extends AbstractType
{
	public function buildForm(FormBuilderInterface $b, array $options)
	{
		$b->add('name', 'text')
		   ->add('description', 'text')	
		   ->add('category')
                   ->add('save', 'submit');
	}

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

Most of this class looks very similar to our CategoryType.php class, except that it’s missing the setDefaultOptions method because we don’t currently intend it to be used as a drop down list. Also, we see that category has been added as part of the form element. Notice though that it does not have a type. Here, Symfony 2 will attempt to make a guess at the form type and will try to render it in a certain manner. If we really wanted to make it more exciting, we could change it’s appearance like converting it to a list of radioboxes or checkboxes (actually only radioboxes would apply since we defined that a product can only belong to a single category).

When you go to render the product form with say:

{{ form_start(form) }}
	{{ form_errors(form) }}
{{ form_end(form) }}

You’ll see that you will have a basic drop down select style list of categories. This is part of the awesome auto-magic that is Symfony 2. In addition, Symfony 2 will handle the pre-population and saving of data for you. The only thing you might choose to do is focus on stylizing your form. So as you can see, you save a ton of effort by allowing Symfony 2 to handle the more tedious aspects of code generation while you focus on the customization of your application.

(Visited 3,172 times, 1 visits today)

Comments

comments