Symfony et les exports CSV (Doctrine)
Sommaire
Introduction
Considérons le modèle Contact qui est relié au modème Organism par une relation many-to-many via le modèle Professional. Nous souhaitons faire une extraction CSV des contacts avec leurs relations éventuelles.
La module correspondant à Contact est contact dans l'application rp. contact a été créé via doctrine:generate-admin.
L'idée est d'extraire les informations qui sont présentées dans la liste des enregistrements, éventuellement filtrée sur certains critères tout en profitant de l'optimisation éventuelle déjà effectuée sur la requête générique.
Préparation dans le view.yml
Dans le fichier apps/rp/modules/contact/config/view.yml,
csvSuccess: has_layout: off http_metas: content-type: text/comma-separated-values content-disposition: attachment; filename="contacts-list.csv"
L'action
Directement dans le vif du sujet
dans le fichier apps/rp/modules/actions/actions.php, créer la méthode executeCsv() de la manière suivante :
public function executeCsv(sfWebRequest $request) { ∕/ to modify the CSV output format in case of extreme necessity $this->options = array('ms' => $request->hasParameter('ms')); $q = $this->buildQuery(); $a = $q->getRootAlias(); $q->leftJoin("$a.Organisms") ->select("$a.title, $a.name, $a.firstname, $a.address, $a.postalcode, $a.city, $a.country") ->addSelect("o.address AS organism_address, o.postalcode AS organism_postalcode, o.city AS organism_city, o.country AS organism_country"); // ->fetchArray() to avoid big big memory usage $this->lines = $q->fetchArray(); $this->outstream = 'php://output'; $this->delimiter = $request->hasParameter('ms') ? ';' : ','; $this->enclosure = '"'; $this->charset = array('db' => 'UTF-8', 'ms' => 'WINDOWS-1252//TRANSLIT'); sfConfig::set('sf_web_debug', false); sfConfig::set('sf_escaping_strategy', false); sfConfig::set('sf_charset', $this->options['ms'] ? $this->charset['ms'] : $this->charset['db']); }
Les subtilités
L'exemple utilisé actuellement n'est pas très très révélateur des difficultés auxquelles on peut être confronté dans le cas de schémas de données beaucoup beaucoup plus complexes. Cela dit, quelques précaution vous épargeront pas mal de temps :
- préciser les champs du select de votre DQL
- récupérer les résultats de la requête dans un tableau via Doctrine_Query::fetchArray() comme vu dans le code précédent
Notez également que selon la manière dont vous construirez votre Doctrine_Query dans les ->select() et ->addSelect(), vous présenterez les choses différemment dans votre fichier CSV final.
La vue (templates)
apps/rp/modules/contact/templates/csvSuccess.php
Dans le fichier apps/rp/modules/contact/templates/csvSuccess.php (que vous créerez), voilà ce qui peut convenir (il est aussi possible de créer un partial propre voire même de le placer directement dans apps/rp/templates/ pour plus de généricité) :
<?php $outstream = fopen($outstream, 'w'); $vars = array( 'options' => $options, 'delimiter' => $delimiter, 'enclosure' => $enclosure, 'outstream' => $outstream, 'charset' => $charset, 'lines' => $lines)); // header if ( count($lines) > 0 ) include_partial('contact/csv_headers',$vars); // content foreach ( $lines as $line ) { unset($line['id']); include_partial('contact/csv_line',array_merge(array('line' => $line),$vars)); } fclose($outstream);
apps/rp/modules/contact/templates/_csv_headers.php
Dans le fichier apps/rp/modules/contact/templates/_csv_headers.php
$line = array_keys($lines[0]); if ( $options['ms'] ) foreach ( $line as $key => $value ) $line[$key] = @iconv($charset['db'], $charset['ms'], $value); fputcsv($outstream, $line, $delimiter, $enclosure); ob_flush();
apps/rp/modules/contact/templates/_csv_line.php
Dans le fichier apps/rp/modules/contact/templates/_csv_line.php
if ( $options['ms'] ) foreach ( $line as $key => $value ) $line[$key] = @iconv($charset['db'], $charset['ms'], $value); fputcsv($outstream, $line, $delimiter, $enclosure); ob_flush();