In a symfony4 project, I have an entity Order
that I want to relate to another entity ChecklistItem
through a ManyToMany relationship but with extra information. This is straightforward enough to do by creating an intermediate entity OrderChecklist
as in this approach.
In my order entry/edit form, I want a full "checklist" to show for every order so I can track when various parts of the order fulfillment are completed. I can't figure out how to implement this.
One option would be to create a full linked checklist containing all checklist items for every order along with a "completed" parameter, but this seems wasteful. Instead, I'd like to show a checkbox of all checklist items and let the user check off items as they get completed and save that in the OrderChecklist
entity. This is what I can't figure out to do.
Here's what I have.
Checklist item:
/**
* @ORM\Entity()
*/
class ChecklistItem
{
/**
* @ORM\OneToMany(targetEntity="OrderChecklist", mappedBy="checklistItem")
*/
private $orderChecklistItems;
// ... other parameters ...
}
Order:
/**
* @ORM\Entity()
*/
class Order
{
/**
* @ORM\OneToMany(targetEntity="OrderChecklist", mappedBy="order")
*/
private $orderChecklistItems;
// ... other parameters ...
}
Linker table:
/**
* @ORM\Entity()
*/
class OrderChecklist
{
/**
* @ORM\ManyToOne(targetEntity="Order", inversedBy="orderChecklistItems")
*/
private $order;
/**
* @ORM\ManyToOne(targetEntity="ChecklistItem", inversedBy="orderChecklistItems")
*/
private $checklistItem;
/**
* @ORM\Column(type="datetime")
*/
private $completionDate;
// ... other parameters ...
}
When rendering the order form with a CollectionType, I have this:
class OrderFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('orderChecklistItems', CollectionType::class, [
'entry_type' => ChecklistItemEmbeddedForm::class,
])
// ... add other parameters ...
;
}
}
with my embeded form:
class ChecklistItemEmbeddedForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('checklistItem', EntityType::class, [
'class' => ChecklistItem::class,
'multiple' => true,
'expanded' => true,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => OrderChecklist::class]);
}
}
The form can be rendered from my controller:
class OrderController extends AbstractController
{
public function edit(Request $request, Order $order)
{
$form_order = $this->createForm(OrderFormType::class, $order);
$form_order->handleRequest($request);
if ($form_order->isSubmitted() && $form_order->isValid()) {
$order = $form_order->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush();
return $this->redirectToRoute('order_edit', ['id'=>$order->getId()]);
}
return $this->render('order/main.html.twig', [
'form_order' => $form_order->createView(),
]);
}
}
and then from twig:
{{ form_start(form_order) }}
{{ form_row(form_order.orderChecklistItems) }}
{{ form_end(form_order) }}
I know that I can use prototypes to offer a single ChecklistItem
at a time per the symfony documentation, but that is not the behavior I am after.
It would seem like I'm missing something simple and obvious in my form. What is it?