Version: current

      1 <?php
      2 /**
      3  * Class to handle parsing of CSV files, where the column headers are in the first row.
      4  * The idea is that you pass it another object to handle the actual procesing of the data in the CSV file.
      5  *
      6  * Usage:
      7  * <code>
      8  * $parser = new CSVParser('myfile.csv');
      9  * $parser->mapColumns(
     10  *    'first name' => 'FirstName'
     11  *    'lastname' => 'Surname',
     12  *    'last name' => 'Surname'
     13  * ));
     14  * foreach($parser as $row) {
     15  *   // $row is a map of column name => column value
     16  *   $obj = new MyDataObject();
     17  *   $obj->update($row);
     18  *   $obj->write();
     19  * }
     20  * </code>
     21  *
     22  * @package sapphire
     23  * @subpackage bulkloading
     24  */
     25 class CSVParser extends Object implements Iterator {
     26     protected $filename;
     27     protected $fileHandle;
     28
     29     /**
     30      * Map of source columns to output columns
     31      * Once they get into this variable, all of the source columns are in lowercase
     32      */
     33     protected $columnMap = array();
     34
     35     /**
     36      * The header row used to map data in the CSV file
     37      * To begin with, this is null.  Once it has been set, data will get returned from the CSV file
     38      */
     39     protected $headerRow = null;
     40
     41     /**
     42      * A custom header row provided by the caller
     43      */
     44     protected $providedHeaderRow = null;
     45
     46     /**
     47      * The data of the current row
     48      */
     49     protected $currentRow = null;
     50
     51     /**
     52      * The current row number
     53      * 1 is the first data row in the CSV file; the header row, if it exists, is ignored
     54      */
     55     protected $rowNum = 0;
     56
     57     /**
     58      * The character for separating columns
     59      */
     60     protected $delimiter = ",";
     61
     62     /**
     63      * The character for quoting colums
     64      */
     65     protected $enclosure = '"';
     66
     67     /**
     68      * Open a CSV file for parsing.
     69      * You can use the object returned in a foreach loop to extract the data
     70      * @param $filename The name of the file.  If relative, it will be relative to the site's base dir
     71      * @param $delimiter The character for seperating columns
     72      * @param $enclosure The character for quoting or enclosing columns
     73      */
     74     function __construct($filename, $delimiter = ",", $enclosure = '"') {
     75         $filename = Director::getAbsFile($filename);
     76         $this->filename = $filename;
     77         $this->delimiter = $delimiter;
     78         $this->enclosure = $enclosure;
     79         parent::__construct();
     80     }
     81
     82     /**
     83      * Re-map columns in the CSV file.
     84      * This can be useful for identifying synonyms in the file
     85      * For example:
     86      * <code>
     87      * $csv->mapColumns(array(
     88      *   'firstname' => 'FirstName',
     89      *   'last name' => 'Surname',
     90      * ));
     91      * </code>
     92      */
     93     function mapColumns($columnMap) {
     94         if($columnMap) {
     95             $lowerColumnMap = array();
     96             foreach($columnMap as $k => $v) {
     97                 $lowerColumnMap[strtolower($k)] = $v;
     98             }
     99             $this->columnMap = array_merge($this->columnMap, $lowerColumnMap);
    100         }
    101     }
    102
    103     /**
    104      * If your CSV file doesn't have a header row, then you can call this function to provide one.
    105      * If you call this function, then the first row of the CSV will be included in the data returned.
    106      */
    107     function provideHeaderRow($headerRow) {
    108         $this->providedHeaderRow = $headerRow;
    109     }
    110
    111     /**
    112      * Open the CSV file for reading
    113      */
    114     protected function openFile() {
    115         ini_set('auto_detect_line_endings',1);
    116         $this->fileHandle = fopen($this->filename,'r');
    117
    118
    119         if($this->providedHeaderRow) {
    120             $this->headerRow = $this->remapHeader($this->providedHeaderRow);
    121         }
    122     }
    123
    124     /**
    125      * Close the CSV file and re-set all of the internal variables
    126      */
    127     protected function closeFile() {
    128         if($this->fileHandle) fclose($this->fileHandle);
    129         $this->fileHandle = null;
    130
    131         $this->rowNum = 0;
    132         $this->currentRow = null;
    133         $this->headerRow = null;
    134     }
    135
    136
    137     /**
    138      * Get a header row from the CSV file
    139      */
    140     protected function fetchCSVHeader() {
    141         $srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
    142         $this->headerRow = $this->remapHeader($srcRow);
    143     }
    144
    145     /**
    146      * Map the contents of a header array using $this->mappedColumns
    147      */
    148     protected function remapHeader($header) {
    149         $mappedHeader = array();
    150         foreach($header as $item) {
    151             if(isset($this->columnMap[strtolower($item)])) $item = $this->columnMap[strtolower($item)];
    152             $mappedHeader[] = $item;
    153         }
    154         return $mappedHeader;
    155     }
    156
    157     /**
    158      * Get a row from the CSV file and update $this->currentRow;
    159      */
    160     protected function fetchCSVRow() {
    161         if(!$this->fileHandle) $this->openFile();
    162         if(!$this->headerRow) $this->fetchCSVHeader();
    163
    164         $this->rowNum++;
    165
    166         $srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
    167         if($srcRow) {
    168             $row = array();
    169             foreach($srcRow as $i => $value) {
    170                 // Allow escaping of quotes and commas in the data
    171                 $value = str_replace(
    172                         array('\\'.$this->enclosure,'\\'.$this->delimiter),
    173                         array($this->enclosure,$this->delimiter),$value);
    174                 if(array_key_exists($i, $this->headerRow)) {
    175                     if($this->headerRow[$i]) $row[$this->headerRow[$i]] = $value;
    176                 } else {
    177                     user_error("No heading for column $i on row $this->rowNum", E_USER_WARNING);
    178                 }
    179             }
    180
    181             $this->currentRow = $row;
    182         } else {
    183             $this->closeFile();
    184         }
    185         return $this->currentRow;
    186     }
    187
    188     /**
    189      * @ignore
    190      */
    191     function __destruct() {
    192         $this->closeFile();
    193     }
    194
    195     //// ITERATOR FUNCTIONS
    196
    197     /**
    198      * @ignore
    199      */
    200     function rewind() {
    201         $this->closeFile();
    202         $this->fetchCSVRow();
    203     }
    204
    205     /**
    206      * @ignore
    207      */
    208     function current() {
    209         return $this->currentRow;
    210     }
    211
    212     /**
    213      * @ignore
    214      */
    215     function key() {
    216         return $this->rowNum;
    217     }
    218
    219     /**
    220      * @ignore
    221      */
    222     function next() {
    223         $this->fetchCSVRow();
    224         return $this->currentRow;
    225     }
    226
    227     /**
    228      * @ignore
    229      */
    230     function valid() {
    231         return $this->currentRow ? true : false;
    232     }
    233
    234
    235 }
    236
    237 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -