I'm trying to figure out how to handle forms when using inherited class types with Symfony (2.8.6).
I have created a [very] simple example case of what I'm trying to do below. There are problems with it, but it's only to illustrate my question.
If someone could even point me to a Github repo or something that shows something like this happening, I'd be very grateful. Thanks for your time.
If these are my classes:
/**
* @ORM\Entity
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="discr", type="string")
* @ORM\DiscriminatorMap("truck" = "Truck", "Car" = "Car", "suv" = "SUV")
*/
abstract class Vechicle {
private $make;
private $model;
private $numberOfDoors;
// getters and setters //
}
class Truck extends Vehicle {
private $offRoadPackage;
private $bedSize;
// getters and setters //
}
class Car extends Vehicle {
private $bodyType;
}
class SUV extends Vehicle {
// no additional fields //
}
then something like these would be my form types:
class VehicleType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('make')
->add('model')
->add('numberOfDoors');
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Vehicle'
));
}
}
class TruckType extends VehicleType {
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder
->add('offRoadPackage')
->add('bedSize');
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Truck'
));
}
}
class CarType extends VehicleType {
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder
->add('bodyType')
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Car'
));
}
}
class SUVType extends VehicleType {
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\SUV'
));
}
}
This is going to be a bit of a lengthy one but bear with me. The gist of this idea is that you deal with your forms in an array. You create a list of types which you iterate to build the actual form objects. This way the only thing you edit is the list of form types if you wish to add more.
In your template you iterate over all the forms to render them and wrap them in a div you can hide. Next you can add a select element that controls a piece of javascript showing/hiding the form of the type the user has selected.
After sbumission you can test if the action has been POSTed to and reiterate the forms to check which one of them has been submitted and handle it appropriately.
Below is a crude untested code snippet:
The controller/action:
class SomeController
{
public function addAction()
{
$types = [
'Form1' => Form1::class,
'Form2' => Form2::class,
'Form3' => Form3::class,
];
// create the forms based on the types indicated in the types arary
$forms = [];
foreach ($types as $type) {
$forms[] = $this->createForm($type);
}
if ($request->isMethod('POST')) {
foreach ($forms as $form) {
$form->handleRequest($request);
if (!$form->isSubmitted()) continue; // no need to validate a form that isn't submitted
if ($form->isValid()) {
// handle the form of your type
break; // stop processing as we found the form we have to deal with
}
}
}
$views = [];
foreach ($forms as $form) {
$views = $form->createView();
}
$this->render('template.html.twig', ['forms' => $views, 'types' => $types]);
}
}
The template:
<select id="types">
{% for type in types|keys %}
<option value="vehicle_type_{{ loop.index }}">{{ type }}</option>
{% endfor %}
</select>
{% for form in forms %}
<div class="form hidden" id="vehicle_type_{{ loop.index }}">
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
</div>
{% endfor %}
And finally the piece of javascript controlling what form is shown/hidden:
<script>
// On select change hide all forms except for the on that was just selected
$('#types').on('change', function () {
$('.form').addClass('hidden');
$('#' + $(this).val()).removeClass('hidden');
});
</script>