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 Symfony 1.4 en hosting compartido: El problema de las direcciones relativas

Un proyecto desarrollado con Symfony, debería desplegarse en un servidor dedicado o al menos en un VPS, si el tráfico del sitio lo amerita. Un servidor dedicado o un VPS nos permite además afinar la configuración para un mejor desempeño de Symfony.

Si por el contrario, tenemos un proyecto desarrollado con Symfony que no tendrá demasiado tráfico y no requiere ajustes especiales en la configuración del servidor, podemos alojarlo en un hosting compartido. Aunque son más lentos, tiene un costo mucho menor y necesitan de menos conocimientos de configuración del servidor. Yo he desarrollado con Symfony 1.4, una extranet que tiene cierta complejidad pero escaso tráfico. La he alojado en un servidor compartido sin ningún problema de funcionamiento.

Nuestros sistemas desarrollados con Symfony 1.4 y alojados en nuestro localhost, tienen como carpeta pública predeterminada a una de nombre “web”. Cuando vamos a desplegar un proyecto desarrollado con Symfony 1.4 en un hosting compartido, nos encontraremos con una carpeta pública predeterminada con un nombre que puede ser distinto al asignado por Symfony, por ejemplo “public_html”. Cambiar el nombre de la carpeta pública en Symfony no es problema, ya que todo el sistema de Symfony se desarrolla con direcciones relativas. Ver figura siguiente.

localhost1
Figura 1: Organización de las carpetas de un proyecto con Symfony 1.4, donde se cambia el nombre de la carpeta pública

A mi juicio, otra cuestión importante, para facilitar el mantenimiento del sistema, es crear una carpeta propia, por encima de la carpeta pública, donde alojemos a todo el sistema de carpetas de Symfony 1.4, que no son públicas. Evitamos así que se mezclen con las demás carpetas propias del servidor.  Nuestras actualizaciones del sistema serán más sencillas y prolijas de esta manera. Ver la Figura 2. Allí se destacan, recuadradas en rojo, dos carpetas. La carpeta «public_html» es la carpeta pública del servidor y equivale a la carpeta original de Symfony llamada «web». La otra carpeta que se recuadra en la Figura 2, es  «portaldeconflictos». En esta carpeta pondré todo el sistema desarrollado con Symfony (ver Figura 4). En la figura 4, puede verse también la carpeta «public_html» que después debo mover a su lugar correcto. También he creado por mi cuenta una carpeta llamada «backup_portal» cuyo uso ahora no es necesario explicar. Todas las demas carpetas y archivos que se ven en la Figura 2, son propios del servidor remoto y es conveniente no tocarlos. En la Figura 3, se puede ver la misma estructura, capturada desde el Panel de Control del servidor remoto.

hosting remoto
Figura 2: Organización de carpetas del servidor remoto
hosting remoto3
Figura 3: Vista desde el  Panel de Control. Organización de carpetas del servidor remoto
hosting remoto7
Figura 4: Vista del contendio de la carpeta «portaldeconflictos» con todo el sistema Symfony

Cuando tenemos esta estructura de carpetas, surge un problema: debemos cambiar algunas direcciones del sistema Symfony 1.4, para que este siga funcionando, ya que hemos alterado la estructura relativa de carpetas de Symfony 1.4 en el servidor remoto. Hay tres archivos críticos para considerar, que deben ser modificados para que Symfony se pueda adaptar a la nueva estructura y seguir funcionando sin problemas. Esos archivos son:

  • fronted_dev.php (alojado en la carpeta  «public_html»)
  • index.php (alojado en la carpeta  «public_html»)
  • ProjectConfiguration.class.php (alojado en la carpeta  “config”)

Los cambios que hay que realizar en cada uno de estos archivos, se muestran en las imágenes siguientes. En todos los casos se trata de transformar direcciones relativas en absolutas.

hosting remoto4

hosting remoto5

hosting remoto6

Los comentamos brevemente. En las tres imágenes anteriores, correspondientes a los tres archivos del sistema que debemos modificar, al cambiar la estructura de carpetas del sistema Symfony 1.4, se ha recuadrado en rojo la línea que debe ser modificada, donde cambiamos las direcciones relativas de Symfony 1.4, por direcciones absolutas. Como en nuestro servidor remoto, el directorio raíz es «/home/portalde/» escribimos como dirección absoluta a modificar en los  archivos  «frontend_dev.php» e «index.php» (ver figuras de arriba):

require_once(‘/home/portalde/portaldeconflictos/config/ProjectConfiguration.class.php’);

En el archivo «ProjectConfiguration.class.php» cambiamos la línea correspondiente a:

require_once ‘/home/portalde/portaldeconflictos/lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php’;

En las imagenes de los tres archivos,  se ven unos recuadros verdes, que corresponden a líneas que deben cambiarse también, cuando desplegamos un proyecto desarrollado con Symfony 1.4.

Y esto todo, solo basta probar que el sistema funciona adecuadamente.

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

Un libro fabuloso para aprender Symfony 2.8

El programador español Javier Eguiluz, una de las personas que más sabe en el mundo sobre Symfony, ha sido el primero en escribir un libro completo sobre Symfony2. Se titula: «Desarrollo web ágil con Symfony2″¡Además está en idioma español!. Ahora se encuentra actualizado a la versión Symfony 2.8. La versión 2.8 será la última de Symfony2, pero al mismo tiempo contará con soporte por un largo período hasta Noviembre de 2018, por lo que se convertirá seguramente en el standard de programación en Symfony 2.x.

Luego, solo quedará pasarse a Symfony 3.x, si queremos mantenernos actualizados en el ámbito de este poderoso framework para desarrollo de sitios Web basado en PHP. Pero como el mismo Eguiluz lo aclara, el salto de la versión 2.8 a cualquier versión 3.x de Symfony, será mucho más fácil y suave, tanto para el lector como para el escritor, ahora que el libro está actualizado a la última versión de Symfony2.

El libro desarrolla un ejemplo muy realista de una aplicación enteramente escrita con Symfony 2.8, donde aprenderás a programar el Frontend, el Backend, y la Extranet de una aplicación pensada para comercio electrónico.

Como se explica en la web del libro:

Siempre actualizado

Los libros en papel se quedan viejos antes de comprarlos. Este libro te servirá durante años, ya que sus contenidos se actualizan continuamente.

Compatible desde Symfony 2.0 hasta 2.8.

El libro se publica en para Symfony 2.0, 2.1, 2.3, 2.4 y 2.8. La versión para 2.8 ha sido reescrita por completo para seguir las recomendaciones de las aplicaciones Symfony modernas.

Léelo como y donde quieras

Además de leer la versión HTML5 en symfony.es, podrás descargártelo en PDF y ePub para leerlo cómodamente en tu lector de libros electrónicos.

Empieza desde cero

No importa si no sabes nada de Symfony, ya que el libro empieza realmente desde cero. Sólo necesitas conocimientos previos de programación con PHP 5.3.

Práctico

A lo largo del libro se programa una aplicación web real completa. Así no te perderás en explicaciones abstractas o conceptos teóricos. Todo es práctico y todo te servirá para programar tus propias aplicaciones.

Completo

El libro abarca desde conceptos básicos como el enrutamiento y las bases de datos hasta los conceptos más avanzados como las listas de control de acceso y la inyección de dependencias.

 

Los detalles del contenido y la historia del libro pueden verse aquí:

http://symfony.es/noticias/2016/08/29/desarrollo-web-agil-con-symfony-28/

El índice de contenidos se puede consultar aquí:

https://issuu.com/javier.eguiluz/docs/libro-symfony2-indice?backgroundColor=%2523111111

Si lo quieres comprar (y te aconsejo que lo hagas, si de verdad quieres aprender Symfony 2.8), debes contar con una cuenta en PayPal y una tarjeta de crédito asociada. Solo cuesta 10 euros, y el dinero que gastarás se compensa con creces con lo mucho que aprenderás.

Una vez finalizada la compra, te encontrarás con una página similar a la siguiente (se muestra parcialmente) y el libro se enviará a tu cuenta de e-mail con formato PDF. Te aconsejo imprimirlo en papel ya que resulta más práctico para hacer anotaciones y accederlo con más facilidad como material de estudio.

gracias symfony28

Symfony 2.8 y Swiftmailer en entorno de desarrollo

Es posible que Usted como yo, necesite probar que el envío de emails funciona adecuadamente, aunque esté en un entorno de desarrollo. Es decir: Usted quiere que el envío de emails desde su aplicación, funcione como si estuviera en un entorno de producción, y de ese modo el email sea realmente enviado desde su localhost y el destinatario de su correo, los reciba en su bandeja de entrada.

Mi punto de partida

Estoy desarrollando una aplicación con Symfony 2.8. Viene ya instalado el bundle de Swiftmailer para el manejo y envío de emails. Tengo escrito en mi controlador una rutina de prueba como esta:

// DocentesController.php
  public function enviarmailAction($name)
{
        $enviado_por='myemail@gmail.com';
                $enviado_para='toemail@gmail.com';
    $message = \Swift_Message::newInstance()
        ->setSubject('Hello Email')
        ->setFrom($enviado_por)
        ->setTo($enviado_para)
        ->setBody(
            $this->renderView(
                'docentes/email.txt.twig',
                array('name' => $name)
            )
        )
    ;
    $this->get('mailer')->send($message);
    return $this->render('docentes/email_enviado.html.twig',  
            array('message'=>$message, 'enviado_por' => $enviado_por, 'enviado_para' =>$enviado_para));
}    

Como puede verse, se utilizan dos plantillas de Twig. Una con el texto que contendrá el «Body» del email y otra que se muestra una vez que el email ha sido enviado. Estas plantillas muy simples, se presentan a continuación.
El archivo email.txt.twig solo contiene el siguiente texto:

Este es un mensaje de prueba

La plantilla de Twig que se muestra después del envío, llamada email_enviado.html.twig, es la siguiente:

{% block body %}
    <div align="left">
Usted ha enviado el siguiente e-mail:<br><br>
    De:  {{enviado_por}} <br>   
    Para:    {{enviado_para}}  <br>
    Asunto: {{message.subject}} <br>
    Body: {{message.body}} <br>
    </div>
{% endblock %}    

Mantener las cosas simples

Para hacer las pruebas de envío de emails en el entorno de desarrollo, utilizaré como servidor de correo a gmail, ingresando con mi usuario y contraseña de mi cuenta de gmail

Para probar el envío real de emails desde mi localhost, necesito configurar (y lo haré de la manera más básica posible) en mi aplicación de Symfony, los siguientes archivos:

# config_dev.yml

swiftmailer:
    transport: gmail
    username:  micuenta@gmail.com   
    password:  **********
    spool:
        type: memory

Luego:

# parameters.yml
parameters:
    database_host: 127.0.0.1
    database_port: null
    database_name: cursospiero
    database_user: root
    database_password: null
  
    mailer_user: micuenta@gmail.com
    mailer_password: *********

Verificar la configuración de la cuenta de Gmail

Para que todo lo anterior funcione, debo asegurarme que en mi cuenta de gmail, en htpps://myaccount.google.com/security se configure como: «Permitir el acceso de aplicaciones menos seguras:» a tal como se muestra en la imagen siguiente.
adecuar-google
Además, si tiene configurada la autenticación en dos pasos, deberá cambiarla, porque de otra manera le traerá problemas para usar el servidor de correo de gmail.

Desactivar el antivirus

Otro detalle que no puede obviar, porque le puede complicar la vida, es el antivirus. En mi caso utilizo Avast con todas las funcionalidades de seguridad para Internet, incluido un firewall. Para que las pruebas de envío de emails puedan realizarse sin problemas, deberemos desactivar el antivirus por un tiempo limitado, de otra manera bloqueará el envío de emails a través de nuestro localhost.

Y bien, esto es todo para empezar. Pruébelo para ver si a Usted le funciona.

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

Instalar Symfony 2.8 con Xampp y NetBeans 8.1 en Windows 7

Esta entrada fue escrita en su versión original, hace mucho tiempo. Hoy Symfony anda por su versión 5. Igualmente, Symfony 2.8 sigue siendo una versión muy buena  potente y eficiente que puede y conviene usarse con PHP 7.1 o superior. Además, para los lectores de habla hispana, tenemos disponible un excelente libro para conocer casi todo sobre este framework en su versión 2.8, mientras que no hay casi nada escrito sobre como aprender a programar con las versiones más nuevas Me refiero al libro de Javier Eguiluz, que se menciona más abajo. Hoy, 18 de julio de 2020, he actualizado algunos detalles, pero lo esencial sigue vigente. (Desde hace poco, en 2020, salió el libro de Fabien Potencier: «Symfony 5: La vía rápida», que tiene una versión en castellano y puede comprarse por 30 euros en formato PDF).

Configurar el servidor Apache Xampp

Antes que nada debemos tener instalado y corriendo algún servidor Apache.

En mi caso utilizo Xampp.  Puede descargar Xampp desde varios lugares en la Web. Asegúrese de que sea una versión que contenga al menos PHP 5.5, en mi caso utilizo la versión de Xampp 5.6.24 (no funciona en Windows XP). Atención: solo instalo Apache y MySQL.

Iniciamos Xampp y si todo está correcto, veremos que se abre una ventana como la siguiente, donde puede verse que los servicios Apache y MySQL (los que elegí instalar) están funcionando adecuadamente ya que sus nombres se encuentran en fondo verde.

xampp1

Para que Xampp funcione de la manera adecuada para nuestro proyecto, debemos cambiar la configuración de Apache, haciendo clic en el botón Config  del módulo de Apache y abriendo el archivo httpd.conf
xampp2

Vamos al final del archivo y agregamos las siguientes líneas:

Listen 127.0.0.1:8080

<VirtualHost 127.0.0.1:8080>
           ServerName localhost
           DocumentRoot «D:\Datos\proyectos\cursospieromaster2017\html«
               <Directory «D:\Datos\proyectos\cursospieromaster2017\html«>
                   AllowOverride All
                     Allow from All
                    Require all granted
            </Directory>

</VirtualHost>

En el código de arriba podemos ver que debemos decirle a Xampp donde está ubicado nuestro directorio de trabajo: También definimos la IP del localhost con el puerto «8080» (en mi caso para distinguirlo de otros proyectos con los que uso diferente puertos).

Además de este cambio en el archivo httpd.conf debemos cambiar el puerto en la configuración general de Xampp, para ello:
  • Abrimos el Panel de Control de Xampp
  • Hacemos clic en Config (ver en la figura siguiente, el botón que está en el extremo superior derecho del Panel, encerrado en una elipse en color rojo).
  • Hacemos clic en Service and Port Settings
  • Aparece una nueva ventana que se llama Service Settings
  • Para nuestro propósito, solo elegimos la solapa Apache y reemplazamos el valor por defecto de 80 a 8080
  • Gravamos los cambios, haciendo click en Save
  • Y de nuevo  Save para actualizar la Configuración general de Xampp
  • Detenemos y volvemos a arrancar Apache en el panel principal, para que corra con los nuevos cambios.
configuracion general de Xampp

Recuerda que tendremos que introducir el dominio en nuestro fichero hosts del sistema operativo:

C:\Windows\System32\drivers\etc\hosts

127.0.0.1:8080       localhost

Luego probamos que todo está funcionando bien, cargando la página en nuestro localhost:

xampp4

Descargando e instalando Symfony 2.8

Aclaración: La explicación que sigue supone que Usted quiere desarrollar un proyecto nuevo. No que está tratando de importar un proyecto ya existente y en desarrollo.

Hay varios sitios en la web donde se explica como instalar Symfony 2. Incluso el sitio oficial de Symfony, también lo detalla. Sin embargo la tarea se puede complicar si además queremos usar como interface de programación a la IDE (integrated development environment) NetBeans para desarrollar nuestro proyecto. Es que desde la versión 2.6 Symfony no ofrece más el paquete en formato comprimido. Si Usted como yo, prefiere trabajar con  NetBeans, hay un par tareas extras, muy sencillas, para lograrlo.

Siguiendo los pasos que se detallan en este link:
https://symfony.com/doc/2.8/setup.html instalamos Symfony 2.8 en nuestra PC con Windows 7. Vemos que el sitio oficial de Symfony, ofrece varias alternativas. En mi caso prefiero usar Composer.

Una vez descargada en instalada la versión de Symfony 2.8, y  alojada en una carpeta que podemos denominar «c:\mi_proyecto», el paso siguiente es recurrir a algún de los software de compresión como Winzip o WinRar para comprimir esta carpeta con todas sus subcarpetas. Con ayuda del explorador de Windows, nos posicionamos en la carpeta «C:\mi_proyecto» y veremos algo similar a lo siguiente:

paso3

En el explorador de Windows, hacemos clic con el botón derecho del mouse (en mi caso uso Winzip) elijo: «Añadir a mi_proyecto.zip», y queda generado el archivo: «mi_proyecto.zip» en el directorio raíz C:\.

Instalando Symfony 2.8 en NetBeans 8.1

Ahora ingresamos a NetBeans (yo utilizo la versión 8.1) y seleccionamos en el menú principal: «Archivo, Import Proyect.., From Zip…», seleccionamos el archivo comprimido, «mi_proyecto.zip», y le decimos a NetBeans, donde queremos alojar nuestro proyecto. En mi caso, en la carpeta: «C:\proyectos». Hacemos clic en el botón «Import».

paso9

NetBeans, se encargará de importar y configurar el proyecto en la carpeta de destino elegida. Una vez terminada la importación, elegimos: «Archivo, Close All Proyects…», y luego, «Archivo, Open Proyect…» y seleccionamos de la lista nuestro proyecto recién importado (ver imagen siguiente)
paso7

Luego, elegimos en NetBeans: «Archivo|Project properties..» y en «Sources» nos aseguramos que las dos carpetas: «Sources folder» y «Project folder» sean iguales. En mi caso: «C:\proyectos\mi_proyecto»

Recordemos decirle a NetBeans, donde buscar el archivo de PHP. Para ello, en el menú principal, elegimos, «Herramientas, Opciones,» luego la solapa «PHP», y escribimos la ruta.  En mi caso, como utilizo Xampp, el archivo se aloja en «C:\xampp\php\php.exe», y luego hacemos clic en el botón «Aceptar», como muestra la imagen siguiente:

paso8

Así el proyecto quedará instalado, configurado (en modo automático) y listo para ser manejado desde NetBeans.

Ahora solo queda empezar a programar con Symfony. Un libro estupendo para aprender Symfony 2.8, se titula «Desarrollo web ágil con Symfony2» y está escrito por Javier Eguiluz, una de las personas que más sabe de Symfony en el mundo (puedes leer una reseña del libro aquí). En este libro se desarrolla un ejemplo muy completo y realista de una tienda on-line totalmente programada con Symfony2.

Symfony 1.4 y el uso de Doctrine Data Hydrators: un ejemplo

Cuando se necesita realizar una consulta y posterior listado, que involucre a varias tablas de una base de datos, puede resultar de utilidad, para ganar en velocidad de procesamiento, utilizar la opción de cargar todos los datos de la consulta en un array multidimensional y luego descargarlo en un archivo CSV o imprimirlo.

Para conocer todas las opciones disponibles de Doctrine y los Data Hydrators, se puede consultar (en inglés), aquí

Yo he necesitado incluir en una consulta el contenido de gran número de tablas (21 en total) de una base de datos, y luego generar un archivo CSV.

Veamos mi ejemplo:

La consulta que necesité hacer, incluida en el archivo actions.class.php tenía la siguiente forma:


public function executeConsultagral(sfWebRequest $request)
{
$records = Doctrine_Core::getTable('AccionConflictiva2')
->createQuery('a')
->leftJoin('a.ActoresAccionConflict3y4 y')
->leftJoin('a.ActoresTa1 at')
->leftJoin('a.FormatosProtestasHasAccionConflictivaT6 h')
->leftJoin('a.FormatosProtestasTa14 f')
->leftJoin('a.DemandasHasAccionConflictivaT5 k')
->leftJoin('a.DemandasTa2 dt')
->leftJoin('a.Conflictos1 c')
->leftJoin('y.TipoOrganizacionTa10 tot')
->leftJoin('y.OrganizacionTa11')
->leftJoin('c.SectorActividadCiuTa7 sac')
->leftJoin('c.SubsectorActividadTa8 sat')
->leftJoin('c.Conflictos1HasActoresTa1 cha')
->leftJoin('c.RelacionConflictualPrincipalTa9 rcp')
->leftJoin('a.FuentePrimariaTa3 p')
->leftJoin('a.FuenteSecundariaTa4 s')
->leftJoin('a.AlcanceTa5 l')
->leftJoin('a.LugaresTa6 u')
->leftJoin('a.RespuestaEstadoTa13 r')
->leftJoin('a.ParticipacionBasesTa12 t')
->leftJoin('a.NivelAgregadoTa15 n')
->where('y.Convocante=2')
->andWhere('a.Marca_patronal<>1')
->andWhere('c.Marca_patronal<>1')
->orderBy('c.Id, a.Id');

$chorizo = $records->execute(array(), Doctrine_Core::HYDRATE_ARRAY);
$this->getUser()->setAttribute('databis', $chorizo);
}

Prestemos atención a las dos últimas líneas de la consulta anterior:


$chorizo = $records->execute(array(), Doctrine_Core::HYDRATE_ARRAY);
$this->getUser()->setAttribute('databis', $chorizo);

Todos los datos de la consulta son cargados en la variable $chorizo que es un array multidimensional. El aspecto del array generado (solo para el primer elemento), se muestra a continuación:


Array
(
[0] => Array
(
[id] => 285
[idconflictos] => 133
[idparticipacion] => 5
[idalcance] => 3
[idlugar] => 2
[fuente_primaria] =>
[fuente_secundaria] => 1
[idnivel_agregado] => 1
[id_respuesta_estado] => 8
[tipo_fuente] => 2. Secundaria
[fecha_fuente] => 2013-01-03
[titulo_nota] => El municipio redujo adicionales de policía
[descripcion_situacion] => El gremio denunció falta de servicio por deudas. El Ejecutivo aclaró que el recorte obedece a la menor actividad estival.
[link_diario] => http://www.lavoz.com.ar/politica/el-municipio-redujo-adicionales-de-policia
[descripcion_alcance] => Municipalidad de Córdoba
[marca_patronal] => 0
[ActoresAccionConflict3y4] => Array
(
[0] => Array
(
[actores_ta1_id] => 10
[accion_conflictiva_2_id] => 285
[tipo_organizacion_ta10_id] => 1
[organizacion_ta11_id] => 120
[tipo_actor] => Trabajador
[actor_explicado] =>
[descripcion_organ] =>
[convocante] => 2
[TipoOrganizacionTa10] => Array
(
[id] => 1
[descripcion] => Sindicato
)

[OrganizacionTa11] => Array
(
[id] => 120
[organizacion] => SUOEM (Sind Ú. Obr y Empl Munic) Capital
[protagonista] => 0
[antagonista] => 0
)

)

[1] => Array
(
[actores_ta1_id] => 32
[accion_conflictiva_2_id] => 285
[tipo_organizacion_ta10_id] => 11
[organizacion_ta11_id] => 75
[tipo_actor] => Estado
[actor_explicado] =>
[descripcion_organ] =>
[convocante] => 5
[TipoOrganizacionTa10] => Array
(
[id] => 11
[descripcion] => Estado
)

[OrganizacionTa11] => Array
(
[id] => 75
[organizacion] => Poder Ejecutivo Municipal
[protagonista] => 0
[antagonista] => 0
)

)

)

[ActoresTa1] => Array
(
[0] => Array
(
[id] => 10
[actor] => Trabajadores estatales
)

[1] => Array
(
[id] => 32
[actor] => Estado municipal como empleador
)

)

[FormatosProtestasHasAccionConflictivaT6] => Array
(
[0] => Array
(
[formatos_protestas_ta14_id] => 9
[accion_conflictiva_2_id] => 285
[descripcion] =>
[principal] => 1
)

)

[FormatosProtestasTa14] => Array
(
[0] => Array
(
[id] => 9
[tipo_de_acciones] => Difusión y comunicación
[descripcion] =>
[tipo] => 5
)

)

[DemandasHasAccionConflictivaT5] => Array
(
[0] => Array
(
[demandas_ta2_id] => 7
[accion_conflictiva_2_id] => 285
[descripcion_demanda] => falta de adicionales policiales
[principal] => 1
)

)

[DemandasTa2] => Array
(
[0] => Array
(
[id] => 7
[motivos_o_demandas] => CYMAT no salarial (cond. y medio amb.)
[descripcion] =>
[tipo] => 0
)

)

[Conflictos1] => Array
(
[id] => 133
[id_relacion_conflictual_principal] => 5
[id_sector_actividad] => 12
[demandas_id] => 7
[id_subsector_actividad] => 33
[descripcion_general] => Trabajadores municipales de Córdoba agrupados en Suoem contra municipio por reducción de adicionales de policía
[descripcion_protagonista] => Trabajadores municipales
[descripcion_antagonista] => Municipio
[descripcion_demandaprinc] => suspension de contratacion de adicionales
[nivel_estado] => 3. Municipal
[fecha_comienzo] => 2013-01-03
[descripcion_sector] => Municipales
[fecha_final] =>
[marca_patronal] => 0
[SectorActividadCiuTa7] => Array
(
[id] => 12
[sector_actividad] => L. Administración publica y defensa planes de seguridad social de afiliación obligatoria
)

[SubsectorActividadTa8] => Array
(
[id] => 33
[id_sector] => 12
[descripcion] => L75. Otros
)

[Conflictos1HasActoresTa1] => Array
(
[0] => Array
(
[conflictos_1_id] => 133
[actores_ta1_id] => 10
)

)

[RelacionConflictualPrincipalTa9] => Array
(
[id] => 5
[descripcion] => A311. Trabajadores estatales // nivel municipal // capital // general
[privado_publico] => 1
[nivel_estado_o_sector] => A311
)

)

[FuentePrimariaTa3] =>
[FuenteSecundariaTa4] => Array
(
[id] => 1
[descripcion] => La Voz del Interior
)

[AlcanceTa5] => Array
(
[id] => 3
[descripcion] => Local/municipal
)

[LugaresTa6] => Array
(
[id] => 2
[depto_provincia] => Capital
)

[RespuestaEstadoTa13] => Array
(
[id] => 8
[descripcion] => Comunicación / niega
)

[ParticipacionBasesTa12] => Array
(
[id] => 5
[descripcion] => Sólo conducción
)

[NivelAgregadoTa15] => Array
(
[id] => 1
[nivel_agregacion] => Empresa o lugar de trabajo
[descripcion] =>
)

)

)

Luego, para poder listar los datos o pasarlos a un archivo CSV, en una vista de Symfony 1.4, procedo a utilizar la función setAttribute.
Luego, en la vista, será posible recorrer el array y volcar los datos en pantalla o en un archivo, sin necesidad de acceder al disco duro del servidor, ya que el array permanece en la memoria, generando un aumento notable de velocidad en las consultas complejas como esta.