0
votes

I got an entity called 'Activity', that defines a relation between 2 more entities, 'Service' and 'Location'.

Both 'Service' and 'Location', use another entity called 'Allocation', to define what services can be used in a concrete location.

When I create a new Activity, after selecting a service I want the location choice field update with the values defined by allocation.

I have followed symfony documentation to create this 'location' dependent choice field in the form.

Dynamic Form Modification

All works great on create/new form, but when i try to edit service field value in an already created Activity, location field does not update and the symfony profiler shows me the following message:

Uncaught PHP Exception Symfony\Component\PropertyAccess\Exception\InvalidArgumentException: "Expected argument of type "AppBundle\Entity\Location", "NULL" given" at F:\xampp\htdocs\gcd\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php line 253 Context: { "exception": "Object(Symfony\Component\PropertyAccess\Exception\InvalidArgumentException)" }

This is a section of my Activity Entity

    /**
 * Activity
 *
 * @ORM\Table(name="activity")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ActivityRepository")
 */
class Activity
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var Service
     *
     * @ORM\ManyToOne(targetEntity="Service", fetch="EAGER")
     * @ORM\JoinColumn(name="service_id", referencedColumnName="id", nullable=false)
     */
    private $service;

    /**
     * @var Location
     *
     * @ORM\ManyToOne(targetEntity="Location", fetch="EAGER")
     * @ORM\JoinColumn(name="location_id", referencedColumnName="id", nullable=false)
     */
    private $location;

My Controller.

/**
 * Creates a new Activity entity.
 *
 * @Route("/new", name="core_admin_activity_new")
 * @Method({"GET", "POST"})
 */
public function newAction(Request $request)
{
    $activity = new Activity();
    $form = $this->createForm('AppBundle\Form\ActivityType', $activity);
    $form->handleRequest($request);

    if($form->isSubmitted() && $form->isValid()){

        $locationAvailable = $this->isLocationAvailable($activity);
        $activityOverlap = $this->hasOverlap($activity);

        if($locationAvailable && !$activityOverlap){
            $em = $this->getDoctrine()->getManager();
            $em->persist($activity);
            $em->flush();

            return $this->redirectToRoute('core_admin_activity_show', array('id' => $activity->getId()));
        }
    }

    return $this->render('activity/new.html.twig', array(
        'activity' => $activity,
        'form' => $form->createView(),
    ));
}


/**
 * Displays a form to edit an existing Activity entity.
 *
 * @Route("/{id}/edit", name="core_admin_activity_edit")
 * @Method({"GET", "POST"})
 */
public function editAction(Request $request, Activity $activity)
{
    $deleteForm = $this->createDeleteForm($activity);
    $editForm = $this->createForm('AppBundle\Form\ActivityType', $activity);
    $editForm->handleRequest($request);

    if ($editForm->isSubmitted() && $editForm->isValid()) {

        $locationAvailable = $this->isLocationAvailable($activity);
        $activityOverlap = $this->hasOverlap($activity);

        if($locationAvailable && !$activityOverlap){
            $em = $this->getDoctrine()->getManager();
            $em->persist($activity);
            $em->flush();

            return $this->redirectToRoute('core_admin_activity_show', array('id' => $activity->getId()));
        }
    }

    return $this->render('activity/edit.html.twig', array(
        'activity' => $activity,
        'edit_form' => $editForm->createView(),
        'delete_form' => $deleteForm->createView(),
    ));
}

My FormType

class ActivityType extends AbstractType

{

private $em;

public function __construct(EntityManager $entityManager)
{
    $this->em = $entityManager;
}

/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('service', EntityType::class, array(
            'class' => 'AppBundle:Service',
            'placeholder' => 'elige servicio',
        ))
        ->add('location', EntityType::class, array(
            'class' => 'AppBundle:Location',
            'choices' => array(),
                    ))
        ->add('name')
        ->add('virtual')
        ->add('customerSeats')
        ->add('customerVacants')
        ->add('employeeSeats')
        ->add('firstDate', 'date')
        ->add('lastDate', 'date')
        ->add('weekday')
        ->add('beginTime', 'time')
        ->add('endTime', 'time')
        ->add('admissionType')
        ->add('status');



    $formModifier = function (FormInterface $form, Service $service = null) {
        $locations = null === $service ? array() : $this->em->getRepository('AppBundle:Allocation')->findLocationsByService($service);

        $form->add('location', EntityType::class, array(
            'class' => 'AppBundle:Location',
            'choices' => $locations,
        ));
    };


    $builder->addEventListener(
        FormEvents::PRE_SET_DATA,
        function (FormEvent $event) use ($formModifier) {
            $data = $event->getData();
            $formModifier($event->getForm(), $data->getService());
        }
    );

    $builder->get('service')->addEventListener(
        FormEvents::POST_SUBMIT,
        function (FormEvent $event) use ($formModifier) {
            // It's important here to fetch $event->getForm()->getData(), as
            // $event->getData() will get you the client data (that is, the ID)
            $service = $event->getForm()->getData();

            // since we've added the listener to the child, we'll have to pass on
            // the parent to the callback functions!
            $formModifier($event->getForm()->getParent(), $service);
        }
    );
}

/**
 * @param OptionsResolver $resolver
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'AppBundle\Entity\Activity'
    ));
}

}

javaScript function

<script>
    var $service = $('#activity_service');
    // When sport gets selected ...
    $service.change(function() {
        // ... retrieve the corresponding form.
        var $form = $(this).closest('form');
        // Simulate form data, but only include the selected service value.
        var data = {};
        data[$service.attr('name')] = $service.val();
        // Submit data via AJAX to the form's action path.
        $.ajax({
            url : $form.attr('action'),
            type: $form.attr('method'),
            data : data,
            success: function(html) {
                // Replace current position field ...
                $('#activity_location').replaceWith(
                        // ... with the returned one from the AJAX response.
                        $(html).find('#activity_location')
                );
            }
        });
    });
</script>

Any help will be great, thanks.

2

2 Answers

1
votes
0
votes

I had also faced similar issue and on tracing found that it is the EntityType class on other dropdown that is causing the problem while editing the form

The solution is to submit the full form via ajax instead of only one field like in the case of new form.

So change

var data = {};
data[$service.attr('name')] = $service.val();

To

var data = $form.serializeArray()

That should fix the issue.