Form: OneToMany as property

Hello everyone,

i have the class “Order” with the property “OrderItems” in a OneToMany relationship.
I would like to pass the OrderItems as a property:

<f:form action="create" object="{order}" objectName="newOrder">
	<f:form.textfield property="orderItem[].name" value="item1" />
	<f:form.textfield property="orderItem[].quantity" value="10" />
	...
	<f:form.textfield property="orderItem[].name" value="item2" />
	<f:form.textfield property="orderItem[].quantity" value="20" />

I tried a few things:

orderItem[].name
orderItems[].name
orderItem.name[]
orderItems.name[]

but nothing really worked. At the moment I pass the OrderItem properties as a single array and in createAction I merge them into corresponding objects and append them to the Order object.

Is there a more elegant way?

Thanks a lot!

Best regards,

Tobias

You need to provide the array accessor, i.e. index of the element.

<f:form action="create" object="{order}" objectName="newOrder">
	<f:form.textfield property="orderItem.0.name" value="item1" />
	<f:form.textfield property="orderItem.0.quantity" value="10" />
	...
	<f:form.textfield property="orderItem.1.name" value="item2" />
	<f:form.textfield property="orderItem.1.quantity" value="20" />

If the amount of items is more than two or is variable, you can just iterate and put the iteration index variable there:

<f:form action="create" object="{order}" objectName="newOrder">
	<f:for each="items" as="item" iteration="it">
		<f:form.textfield property="orderItem.{it.index}.name" value="{item.name}" />
		<f:form.textfield property="orderItem.{it.index}.quantity" value="{item.quantity}" />
	</f:for>

Hello Alexander,
thank you for your quick help. I made a little progress. Just one small hint for other readers

<f:form.textfield property="orderItems.{it.index}.name" value="{item.name}" />

would be correct (see the “s” at the end of orderItem).

However Flow no longer throws error messages, but the OrderItems are not saved. I’m using:

 * @ORM\OneToMany(mappedBy="parentOrder", cascade={"persist", "remove"})
 * @ORM\ManyToOne(inversedBy="orderItems", cascade={"persist", "remove"})

Anything else I missed?!

Again thank you very much for your help.

Tobias

When I make a var_dump in createAction, the Order object contains all OrderItems. But I have seen that “parentOrder” is null. How can I solve this problem? Do I have to add something explicitly in the setter function of the Order class?

Ah, the neverending (bidirectional) *ToMany unintuitivity strikes again :smiley: Yes, this is not straightforward to get right (as of now) and I had dozens of occasions where this came up and haunted people. It’s a bit tricky to do correctly due to how doctrine works.
I have tried to somehow better document how to work with *ToMany relations and am still working on changes to make the implementation side more straightforward. Here’s a few references that hopefully help you understand the underlying “problem” that you see:

https://flowframework.readthedocs.io/en/5.3/TheDefinitiveGuide/PartII/ModelAndRepository.html#tags-and-comments
The PR for the above documentation, it contains a few more words on details:

A related issue that will come up, if you naively build a setter for your ToMany collection property:



and finally (coming with Flow 7) it will become a bit more “straightforward”:

Sorry, it’s quite a bit to chew through, but I’m greatly recommending sticking to it to understand the issue at hand, because it’s maybe the most critical one you will find when building a domain model.
If you’re left puzzled after reading the above, don’t hesitate to come back with questions - I’ll try my best to make things clearer then.

Hello Alexander,

thank you very much for your detailed answer - I have a lot to read :smiley: But I understand that it is not so trivial to get it to work.

I have found an easy way to get around the problem (Order.php):

/**
 * @param Collection $orderItems
 */
public function setOrderItems($orderItems)
{
    /** @var OrderItem $orderItem*/
    foreach ($orderItems as $orderItem) {
        $orderItem->setParentOrder($this);
    }

    $this->orderItems = $orderItems;
}

Best regards,

Tobias

Well, it’s not super hard, it’s just a lot of subtlety involved that one is better to understand first :slight_smile:

With your current implementation, it works now, but I suggest to still take a look at https://github.com/neos/flow-development-collection/pull/2185 - $this->orderItems = $orderItems might cause you some trouble later on and it’s highly recommended to never overwrite an existing doctrine Collection unless it’s intended. Only use the add/removeElement methods. Right now that means you need to “diff” the incoming collection against the existing yourself (see code example in the PR). With the other PR you could avoid that and just implement an add/remove method in your domain model and Flow will take care, but that is yet to be merged.

Making the relation unidirectional as explained in the documentation section might be a good idea for modelling reasons, but is not required to make things work. At least if you do find the bidrectionality causes troubles, you have something in the back of your head :slight_smile: