Garry
Garry

Reputation: 1485

ZF2 form removing nested collection after validation

I have three related Doctrine entities that I am trying to use with Zend Form collections.

The entities are InvoiceItems -> (one to many) -> InvoiceItemOptions -> (one to many) InvoiceItemOptionValues.

When I add a new item all options and values are populating the entity correctly. However when I edit a item the values are being removed from the options entity. The form displays correctly so the values are being pulled from the database and DoctrineObject hydrator is populating the form. The values are lost after the form is validated.

My entity code

InvoiceItems

/**
 * @var \Doctrine\ORM\PersistentCollection
 *
 * @ORM\OneToMany(targetEntity="Application\Entity\InvoiceItemOptions", cascade={"persist"}, orphanRemoval=true, mappedBy="invoiceItem")
 * })
 */
private $options;

InvoiceItemOptions

/**
 * @var \Application\Entity\InvoiceItems
 *
 * @ORM\ManyToOne(targetEntity="Application\Entity\InvoiceItems", cascade={"persist"}, inversedBy="options")
 * @ORM\JoinColumns({
 *   @ORM\JoinColumn(name="invoice_item_id", referencedColumnName="invoice_item_id")
 * })
 */
private $invoiceItem;

/**
 * @var \Doctrine\ORM\PersistentCollection $values
 *
 * @ORM\OneToMany(targetEntity="Application\Entity\InvoiceItemOptionValues", cascade={"persist"}, orphanRemoval=true, mappedBy="invoiceItemOption")
 * })
 */
private $values;

InvoiceItemOptionValues

/**
 * @var integer
 *
 * @ORM\Column(name="invoice_item_option_value_id", type="integer", nullable=false)
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
private $invoiceItemOptionValueId;

/**
 * @var \Application\Entity\InvoiceItemOptions
 *
 * @ORM\ManyToOne(targetEntity="Application\Entity\InvoiceItemOptions", cascade={"persist"}, inversedBy="values")
 * @ORM\JoinColumns({
 *   @ORM\JoinColumn(name="invoice_item_option_id", referencedColumnName="invoice_item_option_id")
 * })
 */
private $invoiceItemOption;

/**
 * @var string
 *
 * @ORM\Column(name="value", type="text", length=65536, nullable=false)
 */
private $value;

/**
 * @var string
 *
 * @ORM\Column(name="price", type="decimal", precision=9, scale=2, nullable=false)
 */
private $price;

And in my fieldsets I have

InvoiceItemFieldset

$this->add(array(
    'type' => 'Collection',
    'name' => 'options',
    'options' => array(
        'count' => 0,
        'should_create_template' => TRUE,
        'template_placeholder' => '__OPTION__',
        'allow_add' => TRUE,
        'allow_remove' => TRUE,
        'target_element' => $this->invoiceItemOptionFieldset,
    ),
));

InvoiceItemOptionFieldset

$this->add(array(
    'type' => 'Collection',
    'name' => 'values',
    'options' => array(
        'count' => 0,
        'should_create_template' => TRUE,
        'template_placeholder' => '__VALUE__',
        'target_element' => $this->invoiceItemOptionValuesFieldset,
    ),
));

InvoiceItemOptionValuesFieldset

public function init()
{
    $this->add(array(
        'name' => 'invoiceItemOptionValueId',
        'type' => 'Hidden'
    ));

    $this->add(array(
        'name' => 'value',
        'type' => 'Textarea',
        'options' => array(
            'label' => _('Value:'),
            'label_attributes' => array('class' => 'required'),
        ),
        'attributes' => array(
            'cols' => 30,
            'rows' => 5,
        ),
    ));

    $this->add(array(
        'name' => 'price',
        'type' => 'Text',
        'options' => array(
            'label' => _('Price Inc. VAT:'),
        ),
        'attributes' => array(
            'size' => 10,
            'maxlength' => 10,
        ),
    ));
}

EditItemModel

use Zend\Stdlib\Parameters;

...

public function processForm(Parameters $postData)
{
    $entityManager = $this->getEntityManager();
    $this->form->setData($postData);

    if ($this->form->isValid()) {
// values lost here
        $this->form->bind($this->entity);
        $this->setFormValid(TRUE);
        if ($this->entity->getQuantity() < 1) {
            $this->entity->setQuantity(1);
        }

        $goodsTotal = $this->entity->getPriceEachIncVat() * $this->entity->getQuantity();
        $optionsTotal = 0.00;
        foreach ($this->entity->getOptions() as $option) {
            foreach ($option->getValues() as $value) { // entity has no values
                if ($value->getPrice() > 0) {
                    $optionsTotal = $optionsTotal + ($value->getPrice() * $this->entity->getQuantity());
                }
            }
        }

        $this->invoiceModel->calculateItemsVat($this->entity, $goodsTotal, $optionsTotal);

        $entityManager->persist($this->entity);

        if ($this->flushEntityManager($entityManager)) {
            return $this->invoiceModel->calculateInvoiceTotals($this->getInvoice()->getInvoiceId());
        }
    }
    return FALSE;
}

Finally my post data

Array
        (
            [items] => Array
                (
                    [options] => Array
                        (
                            [0] => Array
                                (
                                    [name] => Test Option 0
                                    [invoiceItemOptionId] => 37
                                    [values] => Array
                                        (
                                            [0] => Array
                                                (
                                                    [value] => Test Option 0 Value 0
                                                    [price] => 0.00
                                                    [invoiceItemOptionValueId] => 37
                                                )

                                            [1] => Array
                                                (
                                                    [value] => Test Option 0 Value 1
                                                    [price] => 29.99
                                                    [invoiceItemOptionValueId] => 38
                                                )

                                        )

                                )

                        )

                    [title] => Title
                    [sku] => 
                    [quantity] => 2
                    [priceEachIncVat] => 1000.00
                    [vatRate] => 1
                    [invoiceItemId] => 20
                )

        )

I am using Zend Framework version 2.5.1 and Doctrine ORM Module version 1.0.0

Hopefully someone knows what is going on here, many thanks in advance.

Upvotes: 0

Views: 86

Answers (1)

Garry
Garry

Reputation: 1485

I have found a solution.

Adding the line $this->form->setBindOnValidate(FormInterface::BIND_MANUAL); before form validation solved the problem. I also added $this->form->bind($this->entity) after validation;

Hope this helps someone.

EDIT

I also found that the array collection for values in InvoiceItemOptions entity was not being cloned during hydration so the InvoiceItemOptionValues were wrong. I solved this issue by adding the magic method __clone() to my InvoiceItemOptions entity.

public function __clone()
{
    $this->values = new ArrayCollection();
}

Upvotes: 1

Related Questions