Cómo crear y usar funciones en Symfony 2.8

Como en toda aplicación compleja, en Symfony es posible crear y usar funciones personalizadas. La cuestión clave, además de escribirlas bien, es ubicarlas en las carpetas apropiadas y llamarlas del modo correcto. Distinguiremos dos casos:

  1.  Funciones a utilizar en un controlador.
  2. Funciones a utilizar en una plantilla TWIG.

Veamos estos casos a través de ejemplos.

Funciones a utilizar en un controlador

Desarrollaré este punto a partir de un ejemplo concreto. Tengo una «acción» en un controlador con el siguiente código:

$mi_numero= number_format($contrat['id'], 0, ',', '.');
$mi_fecha_firma=ConverterHelper::obtenermiFecha($contrat['fechaFirma']);
$mi_fecha_inicio=ConverterHelper::obtenermiFecha($contrat['fechaInicio']);
$mi_fecha_final=ConverterHelper::obtenermiFecha($contrat['fechaFinal']);
$mi_nombres=$contrat['docentes']['nombres']; //funciona
$mi_apellido=$contrat['docentes']['apellido']; //funciona
$mi_dni=$contrat['docentes']['dni']; //funciona
$mi_cuit=$contrat['docentes']['cuit'];
$mi_direccion=$contrat['docentes']['direccion'];
$mi_direccion=  str_replace('Piso:   Depto:   Barrio:', 'Barrio:',    $mi_direccion); //depuracion 1
$mi_direccion=  str_replace('Depto:   Barrio:', 'Barrio:', $mi_direccion); //depuracion 2
$mi_materia=$contrat['materias']['descripcion']; //funciona
$mi_carrera = $contrat['materias']['carreras']['descripcion'];
$mi_institucion = $contrat['materias']['carreras']['institucion']['nombre'];
$mi_monto= number_format($contrat['monto'], 2, ',', '.');
$mi_monto_letras= ConverterHelper::numtoletras($contrat['monto']);
$mi_monto_adicional= number_format($contrat['montoAdicional'], 2, ',', '.');
$mi_monto_adicional_letras= ConverterHelper::numtoletras($contrat['montoAdicional']);
$mi_garantia = $contrat['garantiaMinAlumnos'];
$mi_marca_imprimir= ($contrat['marcaImprimir']) ? 'Sí' : 'No' ;

En las líneas de código de arriba, tenemos varios ejemplos de llamado a funciones. Algunas como $mi_monto= number_format($contrat[‘monto’], 2, ‘,’, ‘.’); llaman a una función predefinida de PHP. Como muestra el ejemplo, necesito que un número $contrat[‘monto’], sea formateado con dos decimales, y el separador decimal sea la coma y el separador de miles el punto.
Luego tengo un ejemplo del llamado a una función personalizada: $mi_monto_letras= ConverterHelper::numtoletras($contrat[‘monto’]); Se trata del mismo monto anterior, que quiero expresar en letras, ya que la información que estoy procesando, será utilizada en un contrato comercial. A través de la expresión, llamo a una función que hace esta tarea, a la que le envío un número, y me devuelve el mismo número pero en letras. Es decir, si el número enviado a la función «numtoletras» es «1510» el texto devuelto por la función será: «un mil quinientos diez».
Observese la sintaxis: estoy llamando a una clase ConverterHelper y dentro de la clase, a una función llamada numtoletras a la que le paso el argumento $contrat[‘monto’]. Lo expresamos como ConverterHelper::numtoletras($contrat[‘monto’])
Para que esta función pueda operar hay que tomar varios recaudos. Paso a explicar:

Primero que nada, debemos definir en el archivo de controlador donde vayamos a usar la o las funciones, la ruta donde Symfony podrá encontrar esas funciones.En la imagen siguiente, tenemos las primeras líneas de código del archivo ContratosController.php

contratos_controller

Podemos observar en el recuadro rojo de arriba, que hay dos funciones declaradas con su ruta, que son las que se utilizan en distintas partes del archivo de controlador, ContratosController.php
Yo he creado una carpeta especial para las funciones personalizadas a utilizar en los diferentes controladores, que he llamado Helper y está dentro de la carpeta Controller del bundle, BackendBundle, como se muestra en la imagen siguiente:

helper

Vemos que dentro la carpeta Helper hay tres archivos PHP. Cada archivo puede conterner más de una función, pero por claridad conviene crear un archivo para cada función. En cada archivo PHP procedo a crear una clase y dentro de esa clase, pongo las funciones a utilizar. En el ejemplo que venimos desarrollando, tengo la clase ConverterHelper y dentro de la clase la función numtoletras

Podemos ver las primeras líneas de código del archivo ConverterHelper.php en la imagen siguiente:

converter

Funciones a utilizar en una plantilla TWIG

Con las funciones personalizadas a utilizar dentro de archivos Twig, la filosofía es similar a las funciones utilizadas en los controladores: mantener separadas las funciones, respecto de las plantillas de Twig donde son llamadas. Solo cambia en algo la sintaxis. Recordemos que en los archivos Twig, no podemos incorporar en forma directa código PHP, de modo que si necesitamos usar código PHP, debemos llamar a una función externa a la plantilla, para ejecutar ese código.

Primero que nada, debemos decirle a Symfony donde estarán las funciones de Twig. Esto lo hacemos en el archivos services.yml que se encuentra en la carpeta app\config donde definimos los parámetros que se muestran en la figura siguiente:

services

Veamos el uso de las funciones, desarrollando dos ejemplos muy sencillos.

Empecemos por el primero. He creado una función en Twig para formatear los títulos de una manera determinada y siempre igual para todas las plantillas. El llamado a la función, que la he denominado poner_h1, se muestra en la imagen siguiente donde tengo una plantilla Twig:

h1ejemplo

A esta función, le paso el contenido del título, en este caso «Nuevo Docente». La función se ejecuta en un archivo denominado AppExtension.php que está ubicado en la carpeta AppBundle\Twig\AppExtension tal como lo definimos en el archivo services.yml

A continuación el contenido del archivo con las dos funciones de ejemplo que mostraré.

namespace AppBundle\Twig;

class AppExtension extends \Twig_Extension
{

public function getFunctions()
{
return array(
new \Twig_SimpleFunction('poner_h1',  array($this, 'ponerh1')),
new \Twig_SimpleFunction('obtener_fecha',  array($this, 'obtenerFecha')),

);
}

public function ponerh1($mititulo)
{
$mititulo_formateado='
<table width="100%">
<tbody>
<tr>
<td align="center">
<h1>'.$mititulo.'</h1>
</td>
</tr>
<tr></tr>
</tbody>
</table>
';

return $mititulo_formateado;
}

public function obtenerFecha()
{
setlocale(LC_ALL, "es_ES");
$timestamp=time();
$format='l j  F Y H:i';
$trans = array(
'Monday' => 'Lunes,',
'Tuesday' => 'Martes,',
'Wednesday' => 'Miércoles,',
'Thursday' => 'Jueves,',
'Friday' => 'Viernes,',
'Saturday' => 'Sábado,',
'Sunday' => 'Domingo,',
'January' => ' de Enero de ',
'February' => ' de Febrero de ',
'March' => ' de Marzo de ',
'April' => ' de Abril de ',
'May' => ' de Mayo de ',
'June' => ' de Junio de ',
'July' => ' de Julio de ',
'August' => ' de Agosto de ',
'September' => ' de Septiembre de ',
'October' => ' de Octubre de ',
'November' => ' de Noviembre de ',
'December' => ' de Diciembre de ', );

return strtr(date($format,$timestamp),$trans);

}

}

Observemos que dentro la clase AppExtension tenemo dos «zonas» bien diferenciadas. Por un lado la función public function getFunctions() donde tengo listado el conjunto de las funciones de Twig que voy a utilizar. En este primer ejemplo, vemos que la línea de código donde se declara esta función es:
new \Twig_SimpleFunction(‘poner_h1’, array($this, ‘ponerh1’)),
aquí queda definido un nuevo nombre interno, para ejecutar la función: ‘ponerh1’ Esta es la función propiamente dicha, que se localiza más abajo en este mismo archivo. Allí podemos ver que la función, toma el texto que le pasa la plantilla de Twig, y lo formatea tal como se ve en la función. Devolviendo el texto formateado a la plantilla de Twig. Como quiero que Twig ejecute este valor como código HTML, agrego el parámetro raw ala llamada desde Twig. Rcuérdese que la función completa tal como es llamada desde la plantilla de Twig era: {{ poner_h1 (‘Nuevo Docente’) | raw}}

El segundo ejemplo, es una función PHP que me permite poner una fecha detallada en una plantilla Twig, de manera que se muestre el nombre del día, el nombre del mes y el año, junto con la hora, todo eso en español, tal como se ve en la imagen de más abajo. Entonces debo convertir la función que hace esta tarea en PHP pero que muestra los nombres en inglés y pasar esos nombres al español. La función se llama obtener_fecha() y no lleva ningún parámetro, ya que la propia función, toma la fecha y hora actual del sistema y la formatea del modo que se muestra aquí:

fecha

El código que va en la plantilla Twig es:

<div align="center">
<h3>Fecha de impresión: {{ obtener_fecha() }}</h3>
</div>

Y la función está en el archivo AppExtension.php
Y esto es todo. Creo como ejemplos básicos son claros y muestran como se usan y donde se escriben las distintas funciones personalizadas en Symfony 2.8.

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.

Symfony 2.8: Cómo agregar un campo a una tabla ya existente

Introducción

Suele ocurrir que después de crear una base de datos con sus tablas, nos demos cuenta que necesitamos modificar una de las tablas, para agregar un campo nuevo no previsto originalmente.
Hacer esto, requiere de varios pasos que paso a explicar.

Primer paso: Modificar la tabla

Yo utilizo phpAdmin y bases de datos MySQL. Ingresamos a phpMyAdmin, buscamos la tabla a modificar (en mi caso, se llama docentes). Vemos la estructura de la tabla docentes antes de su modificación en la fiura siguiente (la tabla es más grande que lo que muestra la imagen, ya que comprende 26 campos que no entran completos en la imagen):
campo1

A esta tabla, le voy a agregar un campo de tipo Boolean llamado discrecional.

 

Segundo paso: Modificar la entidad

Debemos modificar en el código, la entidad donde se define la tabla en Symfony 2.8. En mi caso, el archivo se llama Docentes.php y se ubica en la carpeta: src/BackendBundle/Entity
Procedemos a agregar el nuevo campo y sus métodos get y set al archivo Docentes.php, como muestran las dos figuras siguientes:

campo3

campo4
Dependiendo de como se haya creado la base de datos con sus tablas, quizás se haya creado un archivo de doctrine con extensión xml. En mi caso, se llama Docentes.orm.xml y se encuentra ubicado en la carpeta que muestra la figura siguiente:

campo5

Agregamos la nueva línea que suministra información del nuevo campo (destacado en recuadro rojo en la imagen siguiente):

campo6

Tercer paso: Modificar los formularios

Por supuesto, debemos incorporar estos datos a los archivos PHP que crean los formularios para dar de alta y modificar la entidad Docentes, agregando el campo nuevo.

En este caso, editamos el archivo DocentesType.php ubicado en: src/BackendBundle/Form  Agregamos la línea:  ->add(‘discrecional’)

 

Cuarto paso: Modificar las vistas

Debemos ahora introducir los cambios en los archivos Twig donde necesitamos que este campo aparezca. En mi caso son los siguientes formularios:

  • new.html.twig
  • edit.html.twig
  • show.html.twig

Quinto paso: Limpiar la cache

Siempre es conveniente, cuando hacemos modificaciones de este tipo, limpiar la cache de Symfony.

Desplegar un proyecto de Symfony 2.8 en un servidor Linux: el problema de los permisos

Desplegar (deploy) un proyecto desarrollado con Symfony 2.8 requiere algunas precauciones que si no los tenemos en cuenta, nos complicarán la puesta en producción del sistema.

Supuestos:

1) Cuento con un servidor remoto VPS o dedicado. En mi caso, utilizo un VPS con CentOS Linux 7.3.1611
2) Tengo por lo tanto, acceso a todas las carpetas del sistema del servidor remoto.

Problema:

Cuando tenemos desarrollado un proyecto Symfony 2.8 y queremos subirlo a algún servidor Web remoto para ver como se comporta en modo de producción, suelen surgir una serie de problemas con los permisos en carpetas en las que Symfony debe escribir. Estas carpetas son: cache, logs y spool, que se encuentran a su vez, dentro de la carpeta app del sistema de Symfony (ver imagen).

permisos1
Carpeta app en el servidor local

Las tres carpetas deben poder ser accedidas por Symfony en forma lectura y escritura. El sitio oficial de Symfony sostiene que estas carpetas no deben ser subidas porque Symfony las creará por su cuenta. Pero el problema que surge a menudo, es que Symfony las crea sin los permisos adecuados y luego no las puede escribir haciendo que el sistema falle y aparezca la temida página en blanco.

Solución:

Mi consejo es que Usted proceda a crear estas carpetas en forma manual, la primera vez que suba el sistema al servidor remoto, y las dote de permisos de lectura y escritura, a las carpetas y a todos los archivos que se vayan a crear en ellas, accediéndolas con algún “File Manager”. El código de permisos que hay que asignarles a las carpetas cache, logs y spool es: 0777 (ver imagen de abajo). Por supuesto, debemos asegurarnos que las sub-carpetas, tengan los mismos permisos. Así cache, incluye la carpeta prod y spool incluye la carpeta default
Con esta medida, se ahorrará muchos dolores de cabeza, que no son por deficiencias de Symfony, pero que muchas veces no permiten que los sistemas desarrollados con Symfony, funcionen correctamente.

permisos2
las carpetas logs, cache y spool en el servidor remoto

Symfony2: Como manejarse con una fecha que tenga el valor null

Punto de partida: Trabajo con Symfony 2.8 y he diseñado la base de datos y las tablas que la componen con el software MySQL Workbench. Luego, hice exportar el esquema y crear la base de datos.
En Symfony 2.8, procedí a importar la base de datos ya creada y a generar las entidades.

Manejo de una fecha con valor null en un formulario diseñado con Twig

Mi problema: Tengo una tabla llamada Contratos (ver estructura en la imagen siguiente) con un campo fecha del tipo DateTime llamado fechaArchivo que puede ser nulo (valor=null).

Mi problema era que al utilizar las plantillas new.html.twig o edit.html.twig, Tiwg me exigía completar el valor del campo y no permitía dejarlo en blanco ni colocar una fecha igual a:»0000-00-00″. La solución está en utilizar la condición ‘required’ => false

contratos
Estructura de la tabla Contratos

Aquí la solución detallada con todos los archivos involucrados:

En el archivo donde se define la entidad Contratos, llamado Contratos.php tenemos:

 /**
     * @var \DateTime
     *
     * @ORM\Column(name='fecha_archivo', type='date', nullable=true)
     */
    private $fechaArchivo;

      /**
     * Set fechaArchivo
     *
     * @param \DateTime $fechaArchivo
     * @return Contratos
     */
    public function setFechaArchivo( \DateTime $fechaArchivo=null)
    {

       $this->fechaArchivo = $fechaArchivo;

        return $this;
    }

    /**
     * Get fechaArchivo
     *
     * @return \DateTime
     */
    public function getFechaArchivo()
    {
        return $this->fechaArchivo;
    }

En mi caso, como procedí a importar una base de datos ya existente, Symfony y Doctrine, crearon también un archivo llamado: Contratos.orm.xml que se muestra completo a continuación:

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
  <entity name="BackendBundle\Entity\Contratos" table="contratos"  repository-class="BackendBundle\Entity\ContratosRepository">
    <indexes>
      <index name="fk_Contratos_Docentes1_idx" columns="docentes_id"/>
      <index name="fk_Contratos_Materias1_idx" columns="materias_id"/>
    </indexes>
    <id name="id" type="integer" column="id">
      <generator strategy="AUTO"/>
    </id>
    <field name="fechaInicio" type="date" column="fecha_inicio" nullable="false"/>
    <field name="fechaFinal" type="date" column="fecha_final" nullable="false"/>
    <field name="fechaFirma" type="date" column="fecha_firma" nullable="false"/>
     <field name="fechaArchivo" type="date" column="fecha_archivo" nullable="true"/>
    <field name="marcaImprimir" type="boolean" column="marca_imprimir" nullable="true"/>
    <field name="monto" type="decimal" column="monto" precision="10" scale="2" nullable="false"/>
    <field name="montoAdicional" type="decimal" column="monto_adicional" precision="10" scale="2" nullable="true"/>
    <many-to-one field="materias" target-entity="Materias">
      <join-columns>
        <join-column name="materias_id" referenced-column-name="id"/>
      </join-columns>
    </many-to-one>
    <many-to-one field="docentes" target-entity="Docentes">
      <join-columns>
        <join-column name="docentes_id" referenced-column-name="id"/>
      </join-columns>
    </many-to-one>
  </entity>
</doctrine-mapping>

Obsérvese con atención la línea 14 del código anterior, donde se fija el atributo nullable=»true» para el campo fechaArchivo

En el archivo donde se definen los atributos de los formularios, tengo:

 public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
             ->add('fechaInicio', 'date', array( 'attr' => array('style' => 'width: 80px'), 'widget' => 'single_text', 'format' => 'dd/MM/yyyy'))
            ->add('fechaFinal', 'date' ,  array( 'attr' => array('style' => 'width: 80px'), 'widget' => 'single_text', 'format' => 'dd/MM/yyyy'))
            ->add('fechaFirma', 'date',  array( 'attr' => array('style' => 'width: 80px'), 'widget' => 'single_text', 'format' => 'dd/MM/yyyy' ))
            ->add('fechaArchivo', 'date',  array('required' => false, 'attr' => array('style' => 'width: 80px'), 'widget' => 'single_text', 'format' => 'dd/MM/yyyy' ))
            ->add('materias')
            ->add('marcaImprimir')
            ->add('monto', 'money', array('currency'=>'ARS' ))
            ->add('montoAdicional', 'money', array('required' => false, 'currency'=>'ARS' ))
            ->add('docentes')
        ;
    }

Obsérvese que tengo cuatro campos fecha, donde tres son obligatorios y el campo fechaArchivo, puede ser null, entonces defino para ese campo la cualidad ‘required’ => false quedando la línea de código completa de la siguiente manera:

 ->add('fechaArchivo', 'date',  array('required' => false, 'attr' => array('style' => 'width: 80px'), 'widget' => 'single_text', 'format' => 'dd/MM/yyyy' ))

¡Y eso es todo! Tanto cuando creo un registro nuevo, como cuando edito uno ya existente, no necesito asignar ningún valor al campo fechaArchivo si no es necesario. Si observamos la tabla con phpMyAdmin, vemos que todos los registros que no tienen una fecha cargada en el campo fechaArchivo, poseen un valor igual a null que yo definí como valor por defecto.

Manejo de una fecha con valor null en una consulta con el uso de Doctrine y Symfony2

Mi problema: ¿Como utilizar una fecha con valor null en una consulta en Symfony2 con Doctrine?
Imaginemos que necesito conocer todos los registros de una tabla que tengan un valor null en un campo fecha. ¿Como es la sintaxis de la consulta?

Solución:En el código de abajo se puede ver la sintaxis correcta para que funcione una consulta, cuando queremos conocer los registros del campo fechaArchivo, que tengan un valor NULL.

public function findNull()
    {
        $em = $this->getEntityManager();

        $consulta = $em->createQuery('
            SELECT c, d, m
            FROM BackendBundle:Contratos c
            JOIN c.docentes d
            JOIN c.materias m
            WHERE c.fechaArchivo IS NULL

            ORDER BY d.apellido, d.nombres
        ');

        return $consulta->getArrayResult();
    }

¿Como mostrar una fecha con valor NULL en un listado de una plantilla de Twig?

Mi problema: Siguiendo con el ejemplo del campo fechaArchivo, si quiero mostrar en un plantilla de Twig el valor de este campo fecha, debo utilizar la siguiente sintaxis para evitar un mensaje de error:

 {% for contrato in contratos %}
                {% if contrato.fechaArchivo %}{{ contrato.fechaArchivo|date('d-m-Y') }}{% endif %}
        {% endfor %}

Esta sintaxis es la correcta para cualquier campo fecha, sea que pueda tener un valor NULL o no. Si el valor fuese NULL, simplemente Twig no mostrará ningún valor en el listado correspondiente.

Symfony 2.8 y TCPDF

Como obtener datos desde una base de datos y mostrarlos en un archivo pdf

Quiero explicar como hacer posible la recuperación de datos desde una tabla o conjunto de tablas relacionadas de una base de datos, accedida con Symfony 2.8 y luego transformar la información en un archivo de formato PDF usando la libreria TCPDF. También explicaré como puede diseñarse una plantilla Twig, para elegir donde ver publicados los datos: en pantalla o en un archivo pdf.

Aquí no explicaré la instalación de la librería TCPDF en Symfony2.
Un muy buen artículo acerca de como instalar y utilizar TCPDF en Symfony 2.7, puede consultarse aquí:
http://maryitsv.blogspot.com.ar/2014/11/instalacion-de-tcpdf-en-symfony-24.html

Por lo tanto, parto de suponer que Usted ya tiene instalada la librería TCPDF y desea visualizar datos de una base de datos.
Mi ejemplo, parte de tres tablas relacionadas como las que se muestran en la imagen siguiente:
tres_tablas

 

Como se deduce de la imagen, las tablas Materias y Docentes, tiene una relación de uno a muchos con la tabla Comisiones.
Supongamos que necesito hacer un listado que pueda verse en la pantalla y también que permita generar un archivo de formato PDF con los datos contenidos en estas tres tablas.
A continuación, dos capturas de pantallas de los dos resultados posibles:
En formato HTML, busco obtener este resultado:

salida_html

Y utilizando la misma plantilla Twig, quiero obtener en PDF algo como esto:

salida_pdf

Para ello, debemos primero que nada definir las rutas. Yo he creado tres rutas: comisiones_index (para ver el listado en pantalla), comisiones_indexpdf (para generar el listado y prepara la infomarción para obtener un archivo pdf) y finalmente: comisones_imprimirpdf (que procede a crear el archivo PDF y lo muestra en pantalla).

comisiones.yml
comisiones_index:
path: /
defaults: { _controller: "BackendBundle:Comisiones:index" }
methods: GET
comisiones_indexpdf:
path: /indexpdf
defaults: { _controller: "BackendBundle:Comisiones:index" }
methods: GET

A continuación, necesitamos crear los controladores. El archivo que contiene los controladores se llama: ComisionesController.php

/**
* Comisiones controller.
*
*/
class ComisionesController extends Controller
{
/**
* Lists all Comisiones entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$comisiones = $em->getRepository('BackendBundle:Comisiones')->findComisio();

return $this->render('comisiones/index.html.twig', array(
'comisiones' => $comisiones,
));
}
public function indexpdfAction()
{
$em = $this->getDoctrine()->getManager();
$comisiones = $em->getRepository('BackendBundle:Comisiones')->findComisio();

return $this->render('comisiones/index.html.twig', array(
'comisiones' => $comisiones,
));
}

La función indexAction no requiere mayor explicación. Lo único particular que hago, es llamar a una función que me permite construir la consulta a la base de datos. Esto está contenido en la sentencia:

fragmentoLa función indexpdfAction hace lo mismo que indexAction pero la creo para poder distinguir las rutas.

La función findComisio() está en el archivo ComisionesRepository.php

El código correspondiente se muestra más abajo. Podemos observar que en la función findComisio() construyo una consulta sobre las tres tablas: Comisiones, docentes y materias.

ComisionesRepository.php

<?php
namespace BackendBundle\Entity;

use Doctrine\ORM\EntityRepository;

class ComisionesRepository extends EntityRepository

{
public function findComisio()
{
$em = $this->getEntityManager();
$consulta = $em->createQuery('
SELECT c, d, m
FROM BackendBundle:Comisiones c
JOIN c.docentes d
JOIN c.materias m
ORDER BY d.apellido, d.nombres, c.id
');
return $consulta->getArrayResult();
}

Ahora procedo a construir el archivo Twig, llamado listadocomisiones.html.twig que utilizaré tanto para ver los datos en pantalla en formato HTML como también para generar el archivo PDF con los mismos datos.
{% extends 'backend.html.twig' %}
{% block mi_cuerpo %}
{% if app.request.attributes.get('_route') == "comisiones_index" %}
{% set es_pdf = false %}
{% else %}
{% set es_pdf = true %}
{% set para_pdf = '<h1>Listado de Comisiones (ordenadas por docente y materia)</h1>' %}
{% endif %}
{% if es_pdf == true %}
{{ inicio_pdf()}}
{% endif %}
<h3>Fecha de impresión: {{ obtener_fecha() }}</h3>
{% if es_pdf == false %}

<table>
{% else %}
<table cellpadding="5" >
{% endif %}
<tr>
<td>Id</td>
<td>Nro<br>carton</td>
<td>Activa</td>
<td>Docente</td>
<td>Materia</td>
<td>Fecha<br>inicio</td>
<td>Fecha<br>final</td>
<td>Total de <br>Alum.</td>
<td>Alum. Gar.</td>
<td>Horarios</td>
<td>Intensidad</td>
<td>Turno</td>
<td>Nro<br>comision</td>
{% if es_pdf == false %}
<td>Acciones</td>
{% endif %}
</tr>
{% for comisione in comisiones %}
<tr>
<td>{{ comisione.id }}</td>
<td>{{ comisione.nroCarton }}</td>
<td>{% if comisione.activa %}Si{% else %}No{% endif %}</td>
<td>{{ comisione.docentes.apellido }}, {{ comisione.docentes.nombres }}</td>
<td>{{ comisione.materias.descripcion }}</td>
<td style="text-align:center">{% if comisione.fechaInicio %}
{{ comisione.fechaInicio|date('d/m/Y') }}{% endif %}</td>
<td style="text-align:center">{% if comisione.fechaFinal %}
{{ comisione.fechaFinal|date('d/m/Y') }}{% endif %}</td>
<td style="text-align:center">{{ comisione.totalAlumnos }}</td>
<td style="text-align:center">{{ comisione.alumnosGarantia }}</td>
<td>{{ comisione.horarios }}</td>
<td>{{ comisione.intensidad }}</td>
<td>{{ comisione.turno }}</td>
<td>{{ comisione.nroComision }}</td>
{% if es_pdf == false %}
<td>
<ul>
<li>
<a href="{{ path('comisiones_show', { 'id': comisione.id }) }}">Mostrar</a>
</li>
<li>
<a href="{{ path('comisiones_edit', { 'id': comisione.id }) }}">Editar</a>
</li>
{% if comisione.activa %}
<li>
<a href="{{ path('comisiones_liqui', { 'id': comisione.id }) }}">Liquidar</a>
</li>
{% endif %}
</ul>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% if es_pdf == true %}
{% set hola = final_pdf(para_pdf) %}
{{ render(controller('BackendBundle:Comisiones:imprimirComisiones', {'posts' : hola })) }}
{% endif %}
{% endblock %}

La primer sentencia busca establecer desde donde se llama a la plantilla Twig. Si se llama desde la ruta comisiones_index definimos una variable es_pdf como «false» y a continuación mostramos por pantalla el título. Si el llamado a la plantilla viene de la otra ruta: comisiones_indexpdf, entonces es_pdf se define como «true». La variable «es_pdf» me permitirá determinar las porciones del código que se muestran en HTML solamente, y cual parte del código solo se utiliza para el archivo PDF. Por supuesto, la mayor parte de la plantilla contiene código común a las dos salidas posibles: HTML o PDF.
Para el caso del archivo PDF, procedo a crear una variable llamada: para_pdf donde se guarda el título del listado. Ya veremos más adelante como concatenar el título contenido en para_pdf con el cuerpo del listado.
En este listado, utilizo tres funciones con código PHP. Como no puedo insertar en forma directa el código PHP en la plantilla Twig, debo proceder a crear las funciones en un archivo llamado AppExtension.php (Ver más abajo)

La primer función invocada en el listado Twig se llama: inicio_pdf, y me permite empezar a grabar en el buffer del navegador los datos que se van generando en la plantilla Twig. Casi al final de la plantilla Twig, se puede ver la otra función esencial para obtener el PDF: final_pdf. A esta función se le pasa como parámetro, para_pdf y luego se concatena el contenido de esta variable con el detalle del listado almacenado en el buffer, cargando todo a la variable «hola». La tercer función que utilizo es opcional para este ejemplo. Se llama obtener_fecha y simplemente me imprime la fecha y hora actual, tanto por pantalla como en el PDF que se genere.
Obsérvese las últimas líneas de la plantilla:

listado5Allí vemos que, en caso de estar generando el archivo PDF, llamamos desde la plantilla Twig a la función imprimirComisiones a la que se le pasa la variable «hola» con todo el contenido de la plantilla Twig (ver el código detallado de la función más abajo). Esta función, que se guarda también en el archivo ComisionesController.php, simplemente construye el archivo PDF, y muestra el resultado por pantalla en formato PDF. Observese que el contenido de la variable «hola» es pasado desde la plantilla Twig al controlador. En el controlador, el contenido de «hola» ingresa como «$posts» y se imprime en el PDF que se genera.

Aquí el código:
public function imprimirComisionesAction($posts)
{
$pdf = $this->get('white_october.tcpdf')->create();
$pdf ->SetAuthor('Piero SRL');
$pdf ->SetTitle('Listado de Comisiones');
$pdf ->SetSubject('Listado');
// activa o desactiva encabezado de página
$pdf ->SetPrintHeader(false);
// activa o desactiva el pie de página
$pdf ->SetPrintFooter(true);
$pdf ->SetFont('times', '', 10);
$pdf->setPageOrientation("L");
// set default header data
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, PDF_HEADER_TITLE.' 001',
PDF_HEADER_STRING, array(0,64,255), array(0,64,128));
$pdf->setFooterData(array(0,64,0), array(0,64,128));
// set header and footer fonts
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
// set default monospaced font
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
// set margins
$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
$pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
$pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
// set auto page breaks
$pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
$pdf ->AddPage();
// escribir los datos
$pdf->writeHTMLCell(0, 0, '', '', $posts, 0, 1, 0, true, '', true );
// la siguiente línea es para limpiar el buffer de datos
ob_end_clean();
$pdf->Output('comisiones.pdf', 'I');
}
}

Dijimos que para usar funciones PHP en una plantilla Twig, debemos utilizar las extensiones de Twig. A continuación vemos la ubicación del archivo a crear:

Ubicación de AppExtension.php
appextension
Debemos también habilitar el servicio correspondiente en el archivo services.yml:
services

Finalmente debemos crear las funciones que llamamos desde Twig. El contenido de AppExtension.php puede verse a continuación:

<?php
namespace AppBundle\Twig;

class AppExtension extends \Twig_Extension
{

public function getFunctions()
{
return array(
new \Twig_SimpleFunction('inicio_pdf', array($this, 'iniciopdfFunction')),
new \Twig_SimpleFunction('final_pdf', array($this, 'finalpdfFunction')),
new \Twig_SimpleFunction('obtener_fecha', array($this, 'obtenerFecha')),
);
public function iniciopdfFunction()
{
ob_start();
return;
}

public function finalpdfFunction($para_pdf)
{
$postsbis = ob_get_contents();
$posts=$para_pdf . $postsbis;
ob_end_clean();

return $posts;
}

public function obtenerFecha()
{
setlocale(LC_ALL, "es_ES");
$timestamp=time();
$format='l j F Y H:i';

$trans = array(
'Monday' => 'Lunes,',
'Tuesday' => 'Martes,',
'Wednesday' => 'Miércoles,',
'Thursday' => 'Jueves,',
'Friday' => 'Viernes,',
'Saturday' => 'Sábado,',
'Sunday' => 'Domingo,',
'January' => ' de Enero de ',
'February' => ' de Febrero de ',
'March' => ' de Marzo de ',
'April' => ' de Abril de ',
'May' => ' de Mayo de ',
'June' => ' de Junio de ',
'July' => ' de Julio de ',
'August' => ' de Agosto de ',
'September' => ' de Septiembre de ',
'October' => ' de Octubre de ',
'November' => ' de Noviembre de ',
'December' => ' de Diciembre de ',
);
return strtr(date($format,$timestamp),$trans);
}
public function getName()
{
return 'app_extension';
}
}

Para ver en detalle como usar las extensiones de Twig, consulte el siguiente link:
http://symfony.com/doc/2.8/cookbook/templating/twig_extension.html