I am using Symfony 5 and I am trying to make a form that would contain three forms. The first is a Collection of CpuSocket entity, the second is a Collection of ProcessorPlatformType entity based on the CpuSockets chosen by the user, the third is a Collection of Processor entity based on the ProcessorPlatformTypes chosen by the user. To summarize, it's a cascade of CpuSocket−>ProcessorPlatformType->Processors.
Basically I have an entity that's the representation of a motherboard in my database and I want to make it impossible for the user to add CPUs that aren't compatible with the socket(s) chosen and the "platform(s)" that belong to the socket (for instance, if the processor can physically fit in the socket but isn't logic compatible with the motherboard). A motherboard can also have more than one socket and they can be different and multiple different platforms supported (because some motherboards have different sockets on one PCB and some boards are compatible with more CPUs than others). That would also improve the user experience greatly because the CPU list is really long.
A more concrete example is for example: some Socket AM4 motherboard can run a Ryzen 1800x, but won't run a 5800x because they're too old, and some Socket AM4 motherboards won't run a 1800x but will run a 5800x and some may run both. Another example is: Asrock made years ago a motherboard with both a socket 478 and a socket 775 (https://www.asrock.com/mb/Intel/P4%20Combo/)
Reading symfony's documentation I can either get the CpuSocket entity and ProcessorPlatformType entity to work, or the ProcessorPlatformType entity and the Processor entity to work, but not all three together.
I think their examples are great, but they don't go deep enough for this kind of scenario where there is a chain of fields where each depend from another one in cascade
A similar thing (although less complex) would be something like selecting a Country from a list, which will then send you a list of Cities, and once you've chosen a city, you'll get a list of all the Streets in the city.
Here's my code
<?php
class AddMotherboard extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('cpuSockets', CollectionType::class, [
'entry_type' => CpuSocketType::class,
'allow_add' => true,
'allow_delete' => true,
'entry_options' => [
'choices' => $options['sockets'],
]
])
->add('save', SubmitType::class)
;
$formSocketModifier = function (FormInterface $form, Collection $cpuSockets = null) {
$platforms = array();
foreach ($cpuSockets as $socket) {
$platforms = array_merge($platforms,$socket->getPlatforms()->toArray());
}
$form->add('processorPlatformTypes', CollectionType::class, [
'entry_type' => ProcessorPlatformTypeForm::class,
'allow_add' => true,
'allow_delete' => true,
'entry_options' => [
'choices' => $platforms,
],
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formSocketModifier) {
$data = $event->getData();
$formSocketModifier($event->getForm(), $data->getCpuSockets());
}
);
$builder->get('cpuSockets')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formSocketModifier) {
$cpuSockets = $event->getForm()->getData();
$formSocketModifier($event->getForm()->getParent(), $cpuSockets);
}
);
$formPlatformModifier = function (FormInterface $form, Collection $processorPlatformTypes = null) {
$processors = array();
if (!$processorPlatformTypes->isEmpty()) {
foreach ($processorPlatformTypes as $platform) {
$processors = array_merge($processors, $platform->getCompatibleProcessors()->toArray());
}
}
$form->add('processors', CollectionType::class, [
'entry_type' => ProcessorType::class,
'allow_add' => true,
'allow_delete' => true,
'entry_options' => [
'choices' => $processors,
],
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formPlatformModifier) {
$data = $event->getData();
$formPlatformModifier($event->getForm(), $data->getProcessorPlatformTypes());
}
);
try {
$builder->get('processorPlatformTypes')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formPlatformModifier) {
$processorPlatformTypes = $event->getForm()->getData();
$formPlatformModifier($event->getForm()->getParent(), $processorPlatformTypes);
}
);
}
catch (InvalidArgumentException $exception) {}
}
}
Actually I think I know exactly what the problem is, but I have no clue on how to solve it. Basically, when the form is first created, the cpuSocket field is already existing and eventually has the user submitted content. However the ProcessorPlatformType field is generated by an event. That means that when the event for the Processors is processed, there's no known ProcessorPlatformType or it's just empty, even though the user has submitted data.
What can I do ?