Symfony 2.8: Cómo crear y procesar un formulario para múltiples registros de una misma entidad

Introdución

En numerosas ocasiones, surge la necesidad de editar múltiples registros de una misma entidad en un solo formulario. Este asunto tiene su dificultad, ya que Symfony2, está orientado a la idea de trabajar con un registro a la vez de cada entidad. O a lo sumo, con el registro de una entidad, y varios registros de otra entidad relacionada con la primera. Pero no se encuentran ejemplos de edición simultáneas de varios registros de una sola entidad.

El caso que desarrollaré, parte de una entidad llamada «Docentes» donde necesito editar varios registros a la vez, para hacer más ágil el trabajo. El campo a editar será uno solo, se llama «discrecional»  y es de tipo boolean. Los archivos involucrado del sistema Symfony2, son cuatro: docentes.yml, DocentesController.php, DocentesRepository.php y discrecional.html.twig

La ruta

Siempre en Symfony 2.8, debemos crear la ruta. Una de las opciones, que elijo yo, es usar el archivo de extensión yml que corresponda. En mi caso, la entidad sobre la que estoy trabajando se llama «Docentes», y el archivo que contiene todas las rutas relacionadas a esta entidad se llama docentes.yml. Allí procedo a definir la nueva ruta que llamaré «discrecional» y también queda definido el nombre y ubicación del controlador, como se muestra en la imagen siguiente:

form1

El Controlador

Una vez definida la ruta, el paso siguiente es crear el controlador, y aquí surgen varias novedades respecto a lo que conocemos, sobre como construir un formulario en Symfony2. Veamos primero el código y luego lo analizamos.

El archivo de controlador de la entidad «Docentes», se llama «DocentesController.php» y la función que maneja las acciones asociada al formulario que quiero construir, la llamé «discrecionalAction».

public function discrecionalAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $defaultData= $em->getRepository('BackendBundle:Docentes')->buscarDocentesActivos2();

// construimos un  formulario "vacío" sin campos definido
            $form = $this->createFormBuilder($defaultData);

            $form = $form->getForm();

            $form->handleRequest($request);  

   // después del submit              

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

                $i=0;
                    foreach ($defaultData as $value) {
                         $data2= array('id' =>$request->request->get($defaultData[$i]['id']),
                      'discrecional' =>$request->request->get('D'.$defaultData[$i]['id'])); 

                        if (($request->request->get('D'.$defaultData[$i]['id'])== '0' and $defaultData[$i]['discrecional']=='0') or
                            ($request->request->get('D'.$defaultData[$i]['id'])== NULL and $defaultData[$i]['discrecional']=='1'))
                        {
                         $em->getRepository('BackendBundle:Docentes')->findDocenteFiltId2($data2);
                        }
                        $i=$i+1;
                    }

        return $this->redirectToRoute('docentes_discrecional');
                   }        

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

Comentamos el código de más arriba. En primer lugar, tal como se muestra en las líneas de código 3 y 4, realizamos una consulta, para obtener el subconjunto de registros que nos interesa editar. Enseguida veremos como se compone esta consulta.

Una vez obtenido los registros que nos interesan, procedemos a construir el formulario. Esto se realiza en las líneas de código de 7 a 11. Lo más extraño, es que aquí no definimos ningún campo a mostrar en el formulario. Es como decirle a Symfony que construya un formulario «vacío». Esto es clave para que puedan editarse varios registros a la vez, ya que si pusiera aquí los campos que quiero visualizar y editar en el formulario, Symfony, solo permitiría aplicarlo a un registro por vez y no a varios registros simultáneamente, como es mi deseo. El trabajo de definir y mostrar los campos, queda reservado para la vista, es decir, el archivo discrecional.html.twig

Después del «submit», una vez que el controlador recibe la información del formulario, procedemos a procesar esa información y deberemos actualizar los registro de la entidad o tabla llamada Docentes. Observense con particular atención las líneas 18 a 26. Allí estamos iterando los datos recibidos del formulario, para actualizar cada registro. Es decir, debemos recorrer todos los registros recibidos del formulario, y actualizar la tabla Docentes con esos datos. Para realizar esa actualización, recurrimos a otra consulta, que será esta vez una consulta de actualización mediante el uso de la función UPDATE (ya lo explicaremos en detalle cuando tratemos las consultas contenidas en el repositorio). Para ganar velocidad y evitar una sobrecarga del servidor, selecciono los registros que realmente se han modificado en el formulario, y solo a esos registros, le aplico la consulta de actualización. Es decir, dentro del «foreach» hay un «if», y solo los registros que cumplen las condiciones, son actualizados.

De las líneas 33 a 35, procedemos a pasar los datos del array $defaultData a la vista que se mostrará a través del archivo dsicrecional.html.twig

La vista

En la vista, es donde hacemos explicíto los campos y registros a editar. Mostramos el código y después lo comentamos.

{% block body %}
{{ form_start(form) }}
{% for docente in docentes %}
Id: <input type="integer" name="{{ docente.id }}" required="required" style="width: 30px" value="{{ docente.id }}" readonly>
Apellido: <input type="text" name="{{ docente.apellido }}" required="required" style="width: 80px" value="{{ docente.apellido }}" readonly>
Nombres: <input type="text" name="{{ docente.nombres }}" required="required" style="width: 80px" value="{{ docente.nombres }}" readonly>
Discrecional: <input type="checkbox" name="D{{ docente.id }}" value="{{ docente.discrecional }}" >
{% endfor %}

<input type="submit" value="Grabar" />
{{ form_end(form) }}

{% endblock %}

Aquí vemos que con los datos recibidos del controlador, Symfony construye la vista del formulario. Vemos que aquí también hacemos una iteración, donde cada «vuelta» de esa iteración me construye un registro. En este caso, los tres primeros campos de cada registro son de solo lectura («readonly») ya que no quiero que se editen aquí. Están para identificar el registro. El único campo que me interesa editar es el campo boolean «discrecional». Preste atención a como está construido el nombre de cada campo de manera de tener un nombre único por cada registro y campo. Esto es fundamental para poder luego procesar la información que el formulario devuelve al controlador, una vez que el usuario hace un clic en el botón «Grabar» («submit»).

Las funciones del repositorio

 Dije antes que usabámos dos funciones ubicadas en el repositorio «DocentesRepository.php». La primer función, es una consulta que me devuelve un subconjunto de la entidad Docentes y lo llamé «buscarDocentesActivos2()», su código es :

 public function buscarDocentesActivos2()
    {
        
        $fields = array('d.id', 'd.apellido', 'd.nombres', 'd.discrecional');

    $query = $this->getEntityManager()->createQueryBuilder();
        $query
            ->select($fields)
            ->from('BackendBundle:Docentes', 'd')
           ->where('d.activo=true')
           ->orderBy('d.apellido, d.nombres');     

        $consulta = $query->getQuery()->getResult();
        
        return $consulta;
    }

Obsérvese que definimos un array $fields con los cuatro campos que me interesan (la entidad es más grande y tiene 17 campos). Luego devolvemos los resultados al controlador, y con esos datos se construirá el formulario.
La segunda función que está en el repositorio, se llama «findDocenteFiltId2($filtro)». Aquí está su código:

public function findDocenteFiltId2($filtro)
    {

     if (is_null($filtro['discrecional'])){ 
         $discrec= '0';
     };
     
     if ($filtro['discrecional']=='0'){ 
         $discrec= '1';
     };
     
     $em = $this->getEntityManager();

        $consulta = $em->createQuery('
            UPDATE BackendBundle:Docentes d
            SET d.discrecional = :disc
            WHERE d.id = :idver
        ');
            
       $consulta->setParameters(array(
      
        'idver' => $filtro['id'],
        'disc'  => $discrec,     
            ));
          return $consulta->getArrayResult();
     
    }   

Esta función, la usamos para actualizar nuestra tabla Docentes. Recibe del controlador, un array llamado $filtro, que contiene los datos de dos campos. El valor del campo «id» que nos perimitirá encontrar de manera inequívoca el registro que queremos actualizar (UPDATE). El otro campo es «discrecional» con su nuevo valor. A esta función solo llegan los registros que efectivamente se han modificado. La consulta permite cargar el nuevo valor de «discrecional» de acuerdo a lo recibido del controlador. Las líneas 4 a 10, transforman los datos del campo «discrecional» en un valor equivalente, para que puedan ser interpretados por la consulta.
La lógica de la actualización se muestra en la tabla siguiente. Hay cuatro casos posibles:

Condición del registro Valor antes del «submit» Valor después del «submit»
No hay cambios en «discrecional» Si está marcado, tiene el valor «1» Al no cambiar, sigue teniendo el valor «1»
No hay cambios en «discrecional» Si está desmarcado, tiene el valor «cero» Aunque su valor no cambie, después del «submit» el formulario le asigna el valor NULL en lugar de «cero»
Registro editado El valor antes del «submit» era «desmarcado» o sea «cero» El valor después del «submit» aunque ahora sea «marcado», no devuelve «1» sino de nuevo «cero»!
Registro editado El valor antes del «submit», era «marcado» o sea «1» El valor después del «submit» será NULL y no «cero»

Conociendo estos comportamientos de Symfony al procesar el formulario, hacemos un filtro en el controlador, donde mediante el «if» determinamos si estamos en el caso de la tercera o cuarta fila de la tabla de más arriba. En ese caso, el controlador, envía la información con los cambios, al repositorio, más especifícamente a la función «findDocenteFiltId2($filtro)», los datos del registro que debe actualizarse con UPDATE.