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.

Symfony 1.4 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 1.4 y luego transformar la información en un archivo de formato PDF usando la libreria TCPDF.

Aquí no explicaré la instalación y uso de la librería TCPDF. Yo recurrí al plugin que está disponible en el sitio oficial de Symfony, pero hay que tener mucho cuidado porque existe un error en el link que aparece en este plugin sobre los archivos a instalar de la librería TCPDF. Pueden ver la naturaleza del problema y la solución que he encontrado consultando aquí:

http://stackoverflow.com/questions/19753485/how-to-use-the-plugin-sftcpdfplugin

Yo he separado el problema en dos partes.
Primero que nada procedo a escribir la consulta, en el archivo que contiene las «actions», que en mi caso toma la forma que se muestra a continuación:

public function executeTest()
{ 
     $this->conflictos1s = Doctrine_Core::getTable('Conflictos1')
      ->createQuery('a') 
      ->leftJoin('a.SectorActividadCiuTa7')     
      ->leftJoin('a.RelacionConflictualPrincipalTa9') 
      ->leftJoin('a.SubsectorActividadTa8')       
       ->leftJoin('a.DemandasTa2')      
       ->leftJoin('a.ActoresTa1')     
       ->leftJoin('a.Conflictos1HasActoresTa1')    
      ->orderBy('a.Id')
      ->execute();  
} 

Luego, yo construyo un template llamado testSuccess.php Hasta aquí nada nuevo en Symfony 1.4. Pero en el template, yo agrego las siguientes líneas de código:
Al comienzo:

<?php ob_start(); ?>

Al final de todo:

<?php $posts = ob_get_contents(); ?>

<?php ob_end_clean(); ?>

El contenido de la vista, queda grabado en la variable $posts
El contendio completo del código del archivo testSuccess.php, se muestra a continuación:

//testSuccess.php

<?php ob_start(); ?>


<h2>Listado de Conflictos</h2>
<table cellspacing="1" border="1">       
  <thead>
    <tr>
      <th width="2%">Id</th>
       <th width="6%">Fecha comienzo</th>
      <th>Relacion conflictual principal</th>
      <th>Sector actividad</th>
      <th>Subsector actividad</th>
      <th>Demandas</th>      
      <th width="20%">Descripcion-general</th>
      <th>Descripcion protagonista</th>
      <th>Descripcion antagonista</th>
      <th>Descripcion demandaprinc</th>
      <th>Nivel estado</th>
      <th>Descripcion sector</th>
</tr>
</thead>
<tbody>
  <?php foreach ($conflictos1s as $conflictos1): ?>
    <tr>
      <td width="2%"><?php echo $conflictos1->getId() ?></td>
      <td width="6%"><?php echo date('d/m/Y', strtotime($conflictos1->getFechaComienzo())) ?></td>
      <td><?php echo $conflictos1->getRelacionConflictualPrincipalTa9() ?></td>
      <td><?php echo $conflictos1->getSectorActividadCiuTa7() ?></td>
      <td><?php echo $conflictos1->getSubsectorActividadTa8() ?></td>
      <td><?php echo $conflictos1->getDemandasTa2() ?></td>
       <td width="20%"><?php echo substr($conflictos1->getDescripcionGeneral(),0,60).'...' ?></td> 
      <td><?php echo $conflictos1->getDescripcionProtagonista() ?></td>
      <td><?php echo $conflictos1->getDescripcionAntagonista() ?></td>
      <td><?php echo $conflictos1->getDescripcionDemandaprinc() ?></td>
      <td><?php echo $conflictos1->getNivelEstado() ?></td>
      <td><?php echo $conflictos1->getDescripcionSector() ?></td>   
    </tr>
    <?php endforeach; ?>      
  </tbody>
</table>
<?php $posts = ob_get_contents();
ob_end_clean(); ?>
<?php $sf_user->setAttribute('para_pdf', $posts); ?>
 <a href="<?php echo url_for('conflictos/testpdf') ?>">Generar Pdf</a>

En el template generamos la vista y capturamos todo lo que el browser «escribe». Agregué al final un link para generar ahora sí el archivo PDF

<a href="<?php echo url_for('conflictos/testpdf') ?>">Generar Pdf</a>

Esto pasa el contenido de la variable $posts a una nueva actions denominada: testpdf de la forma usual:

<?php $sf_user->setAttribute('para_pdf', $posts); ?>

En la nueva actions executeTestpdf, pongamos atención a la siguiente línea de código:

$ html = $ this-> getUser () -> getAttribute ('para_pdf');

La función completa se muestra a continuación:

public function executeTestpdf()
{       
  $config = sfTCPDFPluginConfigHandler::loadConfig();

  // pdf object
  $pdf = new sfTCPDF();

  // set document information
  $pdf->SetCreator(PDF_CREATOR);
  $pdf->SetAuthor('J. H.');
  $pdf->SetTitle('TCPDF Example 001');
  $pdf->SetSubject('TCPDF Tutorial');
  $pdf->SetKeywords('TCPDF, PDF, example, test, guide');

  // set default header data
 $pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, PDF_HEADER_TITLE.' 001', PDF_HEADER_STRING);

  // 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);

  //set image scale factor
  $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);

  // ---------------------------------------------------------
 $pdf->setPageOrientation("L");


  // set default font subsetting mode
  $pdf->setFontSubsetting(true);

  // Set font
  $pdf->SetFont('helvetica', '', 6);

  // Add a page
  // This method has several options, check the source code documentation for more information.

$pdf->AddPage();

$pdf->Write(0, '', '', 0, 'L', true, 0, false, false, 0);


  // Set some content to print


 $html = $this->getUser()->getAttribute('para_pdf');


  $pdf->writeHTML($html, true, false, false, false, '');

  // ---------------------------------------------------------

  // Close and output PDF document
  // This method has several options, check the source code documentation for more information.
  $pdf->Output('example_conflictos.pdf', 'I');

  // Stop symfony process
  throw new sfStopException();
}

Luego, ¡finalmente yo obtengo el archivo en formato PDF con todos los datos correctamente mostrados!