Martin Abraham
Martin Abraham

Reputation: 762

symfony2 form builder - how to mix form data and textual information

in a legacy system selling digital goods...

products are organized in product categories (4). products may include other sub-products. customer can buy products and become owner of a product instances. each product instance has its' own configuration.

problem:

customers should be able to cancel their product instances. therefor i want to build a central cancellation form like the following:

form

the form contains all product instances of a customer. the products are organized in categories. each product instance should be described by a short textual block describing its' configuration (attributes + some subentity attributes). beside the description there should be a checkbox. if the box is checked the product instance should be canceled.

the entity field type (expanded, multiple) seems to be a good choice? but how can i display the textual information beside the checkbox?

how can i realize such a form using symfony2 form builder?

it would be enough if i could access the product object inside a custom form-type view object to build output like:

    <!-- how a product instance should be displayed -->
    <!-- description on the left -->
    <div class="left">
      {{ product.name }} {{ product.calcExpireDate() }}<br />
      {% for s in product.getSubentities() %} {{ s.name }}, {% endfor %}
    </div>
    <!-- checkbox on the right -->
    <div class="right">
      <input type="checkbox ..[] > <label>Cancel<label>
    </div>

Upvotes: 0

Views: 329

Answers (2)

sickelap
sickelap

Reputation: 897

Here's solution for you:

entities and form types are minimized (without getters/setters)

Suppose you have two entities Category and Product that will look similar to this:

/**
 * @ORM\Table()
 * @ORM\Entity()
 */
class Category
{
    /**
     * @var integer
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(name="name", type="string")
     */
    private $name;

    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
     */
    private $products;

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

/**
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\ProductRepository")
 */
class Product
{
    /**
     * @var integer
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(name="name", type="string")
     */
    private $name;

    /**
     * @var string
     * @ORM\Column(name="description", type="text")
     */
    private $description;

    /**
     * @var Category
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
     */
     private $category;

     /**
      * @var boolean
      */
     private $cancel;

     /**
      * @var boolean
      */
     private $asap;
}

Now, create meta-model for product cancelation:

class Cancel
{
    /**
     * @var string
     */
    private $name;

    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     */
    private $products;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->products = new \Doctrine\Common\Collections\ArrayCollection();
    }
}

NOTE: this is not Entity (Table) but just plain 'ol PHP object.

Generate CRUD for these Product and Category and create CancelType for Cancel model with following buildForm method:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('name');
    $builder->add('products', 'collection', array('type' => new ProductType()));
}

Create action that will render our new CancelType

public function cancelAction()
{
    $em = $this->getDoctrine()->getManager();
    $productsToCancel = $em->getRepository('AcmeDemoBundle:Product')->findAll();

    $cancel = new Cancel();
    foreach ($productsToCancel as $product) {
        $cancel->addProduct($product);
    }
    $form = $this->createForm(new CancelType(), $cancel);

    return $this->render('AcmeDemoBundle:Product:cancel.html.twig', array(
        'form' => $form->createView(),
    ));
}

and create cancel.html.twig with following content:

<div>
    {{ form_widget(form.name) }}
</div>

{% for product in form.products %}
    <fieldset>
        <legend>{{ product.vars.value.category.name }}</legend>
        <table>
            <tr>
                <td>{{ product.vars.value.name }}</td>
                <td>
                    {{ form_widget(product.cancel) }} cancel
                    {{ form_widget(product.asap) }} asap
                </td>
            </tr>
            <tr>
                <td>
                    {{ product.vars.value.description }}
                </td>
            </tr>
        </table>
    </fieldset>
{% endfor %}

With all that you will have what you want plus advantages by having standard Symfony2 form validation.

Last thing is to add action in the controller to handle form submission :)

Upvotes: 1

sickelap
sickelap

Reputation: 897

You should use form collection in this kind of situation. Example in symfony2 docs shows how to do that.

Upvotes: 0

Related Questions