mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Code formatting / PHPDoc cleanup
This commit is contained in:
parent
c8af0fd7d1
commit
7ea96504c6
@ -1,9 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Class to handle parsing of CSV files, where the column headers are in the first row.
|
* Class to handle parsing of CSV files, where the column headers are in the
|
||||||
* The idea is that you pass it another object to handle the actual procesing of the data in the CSV file.
|
* first row.
|
||||||
|
*
|
||||||
|
* The idea is that you pass it another object to handle the actual processing
|
||||||
|
* of the data in the CSV file.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* $parser = new CSVParser('myfile.csv');
|
* $parser = new CSVParser('myfile.csv');
|
||||||
* $parser->mapColumns(
|
* $parser->mapColumns(
|
||||||
@ -23,50 +27,80 @@
|
|||||||
* @subpackage bulkloading
|
* @subpackage bulkloading
|
||||||
*/
|
*/
|
||||||
class CSVParser extends Object implements Iterator {
|
class CSVParser extends Object implements Iterator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $filename
|
||||||
|
*/
|
||||||
protected $filename;
|
protected $filename;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var resource $fileHandle
|
||||||
|
*/
|
||||||
protected $fileHandle;
|
protected $fileHandle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of source columns to output columns
|
* Map of source columns to output columns.
|
||||||
* Once they get into this variable, all of the source columns are in lowercase
|
*
|
||||||
|
* Once they get into this variable, all of the source columns are in
|
||||||
|
* lowercase.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $columnMap = array();
|
protected $columnMap = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The header row used to map data in the CSV file
|
* The header row used to map data in the CSV file.
|
||||||
* To begin with, this is null. Once it has been set, data will get returned from the CSV file
|
*
|
||||||
|
* To begin with, this is null. Once it has been set, data will get
|
||||||
|
* returned from the CSV file.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $headerRow = null;
|
protected $headerRow = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom header row provided by the caller
|
* A custom header row provided by the caller.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $providedHeaderRow = null;
|
protected $providedHeaderRow = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data of the current row
|
* The data of the current row.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $currentRow = null;
|
protected $currentRow = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current row number
|
* The current row number.
|
||||||
* 1 is the first data row in the CSV file; the header row, if it exists, is ignored
|
*
|
||||||
|
* 1 is the first data row in the CSV file; the header row, if it exists,
|
||||||
|
* is ignored.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $rowNum = 0;
|
protected $rowNum = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The character for separating columns
|
* The character for separating columns.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $delimiter = ",";
|
protected $delimiter = ",";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The character for quoting colums
|
* The character for quoting columns.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $enclosure = '"';
|
protected $enclosure = '"';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a CSV file for parsing.
|
* Open a CSV file for parsing.
|
||||||
* You can use the object returned in a foreach loop to extract the data
|
*
|
||||||
|
* You can use the object returned in a foreach loop to extract the data.
|
||||||
|
*
|
||||||
* @param $filename The name of the file. If relative, it will be relative to the site's base dir
|
* @param $filename The name of the file. If relative, it will be relative to the site's base dir
|
||||||
* @param $delimiter The character for seperating columns
|
* @param $delimiter The character for seperating columns
|
||||||
* @param $enclosure The character for quoting or enclosing columns
|
* @param $enclosure The character for quoting or enclosing columns
|
||||||
@ -76,24 +110,28 @@ class CSVParser extends Object implements Iterator {
|
|||||||
$this->filename = $filename;
|
$this->filename = $filename;
|
||||||
$this->delimiter = $delimiter;
|
$this->delimiter = $delimiter;
|
||||||
$this->enclosure = $enclosure;
|
$this->enclosure = $enclosure;
|
||||||
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-map columns in the CSV file.
|
* Re-map columns in the CSV file.
|
||||||
* This can be useful for identifying synonyms in the file
|
*
|
||||||
* For example:
|
* This can be useful for identifying synonyms in the file. For example:
|
||||||
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* $csv->mapColumns(array(
|
* $csv->mapColumns(array(
|
||||||
* 'firstname' => 'FirstName',
|
* 'firstname' => 'FirstName',
|
||||||
* 'last name' => 'Surname',
|
* 'last name' => 'Surname',
|
||||||
* ));
|
* ));
|
||||||
* </code>
|
* </code>
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
*/
|
*/
|
||||||
public function mapColumns($columnMap) {
|
public function mapColumns($columnMap) {
|
||||||
if($columnMap) {
|
if($columnMap) {
|
||||||
$lowerColumnMap = array();
|
$lowerColumnMap = array();
|
||||||
|
|
||||||
foreach($columnMap as $k => $v) {
|
foreach($columnMap as $k => $v) {
|
||||||
$lowerColumnMap[strtolower($k)] = $v;
|
$lowerColumnMap[strtolower($k)] = $v;
|
||||||
}
|
}
|
||||||
@ -101,22 +139,26 @@ class CSVParser extends Object implements Iterator {
|
|||||||
$this->columnMap = array_merge($this->columnMap, $lowerColumnMap);
|
$this->columnMap = array_merge($this->columnMap, $lowerColumnMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If your CSV file doesn't have a header row, then you can call this function to provide one.
|
* If your CSV file doesn't have a header row, then you can call this
|
||||||
* If you call this function, then the first row of the CSV will be included in the data returned.
|
* function to provide one.
|
||||||
|
*
|
||||||
|
* If you call this function, then the first row of the CSV will be
|
||||||
|
* included in the data returned.
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
*/
|
*/
|
||||||
public function provideHeaderRow($headerRow) {
|
public function provideHeaderRow($headerRow) {
|
||||||
$this->providedHeaderRow = $headerRow;
|
$this->providedHeaderRow = $headerRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the CSV file for reading
|
* Open the CSV file for reading.
|
||||||
*/
|
*/
|
||||||
protected function openFile() {
|
protected function openFile() {
|
||||||
ini_set('auto_detect_line_endings',1);
|
ini_set('auto_detect_line_endings',1);
|
||||||
$this->fileHandle = fopen($this->filename,'r');
|
$this->fileHandle = fopen($this->filename,'r');
|
||||||
|
|
||||||
|
|
||||||
if($this->providedHeaderRow) {
|
if($this->providedHeaderRow) {
|
||||||
$this->headerRow = $this->remapHeader($this->providedHeaderRow);
|
$this->headerRow = $this->remapHeader($this->providedHeaderRow);
|
||||||
@ -124,12 +166,14 @@ class CSVParser extends Object implements Iterator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the CSV file and re-set all of the internal variables
|
* Close the CSV file and re-set all of the internal variables.
|
||||||
*/
|
*/
|
||||||
protected function closeFile() {
|
protected function closeFile() {
|
||||||
if($this->fileHandle) fclose($this->fileHandle);
|
if($this->fileHandle) {
|
||||||
$this->fileHandle = null;
|
fclose($this->fileHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fileHandle = null;
|
||||||
$this->rowNum = 0;
|
$this->rowNum = 0;
|
||||||
$this->currentRow = null;
|
$this->currentRow = null;
|
||||||
$this->headerRow = null;
|
$this->headerRow = null;
|
||||||
@ -137,20 +181,34 @@ class CSVParser extends Object implements Iterator {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a header row from the CSV file
|
* Get a header row from the CSV file.
|
||||||
*/
|
*/
|
||||||
protected function fetchCSVHeader() {
|
protected function fetchCSVHeader() {
|
||||||
$srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
|
$srcRow = fgetcsv(
|
||||||
|
$this->fileHandle,
|
||||||
|
0,
|
||||||
|
$this->delimiter,
|
||||||
|
$this->enclosure
|
||||||
|
);
|
||||||
|
|
||||||
$this->headerRow = $this->remapHeader($srcRow);
|
$this->headerRow = $this->remapHeader($srcRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map the contents of a header array using $this->mappedColumns
|
* Map the contents of a header array using $this->mappedColumns.
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function remapHeader($header) {
|
protected function remapHeader($header) {
|
||||||
$mappedHeader = array();
|
$mappedHeader = array();
|
||||||
|
|
||||||
foreach($header as $item) {
|
foreach($header as $item) {
|
||||||
if(isset($this->columnMap[strtolower($item)])) $item = $this->columnMap[strtolower($item)];
|
if(isset($this->columnMap[strtolower($item)])) {
|
||||||
|
$item = $this->columnMap[strtolower($item)];
|
||||||
|
}
|
||||||
|
|
||||||
$mappedHeader[] = $item;
|
$mappedHeader[] = $item;
|
||||||
}
|
}
|
||||||
return $mappedHeader;
|
return $mappedHeader;
|
||||||
@ -158,23 +216,42 @@ class CSVParser extends Object implements Iterator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a row from the CSV file and update $this->currentRow;
|
* Get a row from the CSV file and update $this->currentRow;
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function fetchCSVRow() {
|
protected function fetchCSVRow() {
|
||||||
if(!$this->fileHandle) $this->openFile();
|
if(!$this->fileHandle) {
|
||||||
if(!$this->headerRow) $this->fetchCSVHeader();
|
$this->openFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->headerRow) {
|
||||||
|
$this->fetchCSVHeader();
|
||||||
|
}
|
||||||
|
|
||||||
$this->rowNum++;
|
$this->rowNum++;
|
||||||
|
|
||||||
$srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
|
$srcRow = fgetcsv(
|
||||||
|
$this->fileHandle,
|
||||||
|
0,
|
||||||
|
$this->delimiter,
|
||||||
|
$this->enclosure
|
||||||
|
);
|
||||||
|
|
||||||
if($srcRow) {
|
if($srcRow) {
|
||||||
$row = array();
|
$row = array();
|
||||||
|
|
||||||
foreach($srcRow as $i => $value) {
|
foreach($srcRow as $i => $value) {
|
||||||
// Allow escaping of quotes and commas in the data
|
// Allow escaping of quotes and commas in the data
|
||||||
$value = str_replace(
|
$value = str_replace(
|
||||||
array('\\'.$this->enclosure,'\\'.$this->delimiter),
|
array('\\'.$this->enclosure,'\\'.$this->delimiter),
|
||||||
array($this->enclosure,$this->delimiter),$value);
|
array($this->enclosure, $this->delimiter),
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
|
||||||
if(array_key_exists($i, $this->headerRow)) {
|
if(array_key_exists($i, $this->headerRow)) {
|
||||||
if($this->headerRow[$i]) $row[$this->headerRow[$i]] = $value;
|
if($this->headerRow[$i]) {
|
||||||
|
$row[$this->headerRow[$i]] = $value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
user_error("No heading for column $i on row $this->rowNum", E_USER_WARNING);
|
user_error("No heading for column $i on row $this->rowNum", E_USER_WARNING);
|
||||||
}
|
}
|
||||||
@ -184,6 +261,7 @@ class CSVParser extends Object implements Iterator {
|
|||||||
} else {
|
} else {
|
||||||
$this->closeFile();
|
$this->closeFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->currentRow;
|
return $this->currentRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,6 +301,7 @@ class CSVParser extends Object implements Iterator {
|
|||||||
*/
|
*/
|
||||||
public function next() {
|
public function next() {
|
||||||
$this->fetchCSVRow();
|
$this->fetchCSVRow();
|
||||||
|
|
||||||
return $this->currentRow;
|
return $this->currentRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,5 +311,4 @@ class CSVParser extends Object implements Iterator {
|
|||||||
public function valid() {
|
public function valid() {
|
||||||
return $this->currentRow ? true : false;
|
return $this->currentRow ? true : false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Utility class to facilitate complex CSV-imports by defining column-mappings and custom converters.
|
* Utility class to facilitate complex CSV-imports by defining column-mappings
|
||||||
* Uses the fgetcsv() function to process CSV input. Accepts a file-handler as input.
|
* and custom converters.
|
||||||
|
*
|
||||||
|
* Uses the fgetcsv() function to process CSV input. Accepts a file-handler as
|
||||||
|
* input.
|
||||||
*
|
*
|
||||||
* @see http://rfc.net/rfc4180.html
|
* @see http://rfc.net/rfc4180.html
|
||||||
|
*
|
||||||
* @package framework
|
* @package framework
|
||||||
* @subpackage bulkloading
|
* @subpackage bulkloading
|
||||||
* @author Ingo Schommer, Silverstripe Ltd. (<myfirstname>@silverstripe.com)
|
|
||||||
*
|
*
|
||||||
* @todo Support for deleting existing records not matched in the import (through relation checks)
|
* @todo Support for deleting existing records not matched in the import
|
||||||
|
* (through relation checks)
|
||||||
*/
|
*/
|
||||||
class CsvBulkLoader extends BulkLoader {
|
class CsvBulkLoader extends BulkLoader {
|
||||||
|
|
||||||
@ -48,15 +52,15 @@ class CsvBulkLoader extends BulkLoader {
|
|||||||
$results = new BulkLoader_Result();
|
$results = new BulkLoader_Result();
|
||||||
|
|
||||||
$csv = new CSVParser(
|
$csv = new CSVParser(
|
||||||
$filepath,
|
$filepath,
|
||||||
$this->delimiter,
|
$this->delimiter,
|
||||||
$this->enclosure
|
$this->enclosure
|
||||||
);
|
);
|
||||||
|
|
||||||
// ColumnMap has two uses, depending on whether hasHeaderRow is set
|
// ColumnMap has two uses, depending on whether hasHeaderRow is set
|
||||||
if($this->columnMap) {
|
if($this->columnMap) {
|
||||||
// if the map goes to a callback, use the same key value as the map
|
// if the map goes to a callback, use the same key value as the map
|
||||||
// value, rather than function name as multiple keys may use the
|
// value, rather than function name as multiple keys may use the
|
||||||
// same callback
|
// same callback
|
||||||
foreach($this->columnMap as $k => $v) {
|
foreach($this->columnMap as $k => $v) {
|
||||||
if(strpos($v, "->") === 0) {
|
if(strpos($v, "->") === 0) {
|
||||||
@ -82,7 +86,14 @@ class CsvBulkLoader extends BulkLoader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Better messages for relation checks and duplicate detection
|
* @todo Better messages for relation checks and duplicate detection
|
||||||
* Note that columnMap isn't used
|
* Note that columnMap isn't used.
|
||||||
|
*
|
||||||
|
* @param array $record
|
||||||
|
* @param array $columnMap
|
||||||
|
* @param BulkLoader_Result $results
|
||||||
|
* @param boolean $preview
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
*/
|
*/
|
||||||
protected function processRecord($record, $columnMap, &$results, $preview = false) {
|
protected function processRecord($record, $columnMap, &$results, $preview = false) {
|
||||||
$class = $this->objectClass;
|
$class = $this->objectClass;
|
||||||
@ -128,13 +139,13 @@ class CsvBulkLoader extends BulkLoader {
|
|||||||
$relationObj = $obj->getComponent($relationName);
|
$relationObj = $obj->getComponent($relationName);
|
||||||
if (!$preview) $relationObj->write();
|
if (!$preview) $relationObj->write();
|
||||||
$obj->{"{$relationName}ID"} = $relationObj->ID;
|
$obj->{"{$relationName}ID"} = $relationObj->ID;
|
||||||
|
|
||||||
//write if we are not previewing
|
//write if we are not previewing
|
||||||
if (!$preview) {
|
if (!$preview) {
|
||||||
$obj->write();
|
$obj->write();
|
||||||
$obj->flushCache(); // avoid relation caching confusion
|
$obj->flushCache(); // avoid relation caching confusion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// second run: save data
|
// second run: save data
|
||||||
@ -184,24 +195,31 @@ class CsvBulkLoader extends BulkLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an existing objects based on one or more uniqueness
|
* Find an existing objects based on one or more uniqueness columns
|
||||||
* columns specified via {@link self::$duplicateChecks}
|
* specified via {@link self::$duplicateChecks}.
|
||||||
*
|
*
|
||||||
* @param array $record CSV data column
|
* @param array $record CSV data column
|
||||||
* @return unknown
|
*
|
||||||
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function findExistingObject($record) {
|
public function findExistingObject($record) {
|
||||||
$SNG_objectClass = singleton($this->objectClass);
|
$SNG_objectClass = singleton($this->objectClass);
|
||||||
// checking for existing records (only if not already found)
|
// checking for existing records (only if not already found)
|
||||||
|
|
||||||
foreach($this->duplicateChecks as $fieldName => $duplicateCheck) {
|
foreach($this->duplicateChecks as $fieldName => $duplicateCheck) {
|
||||||
if(is_string($duplicateCheck)) {
|
if(is_string($duplicateCheck)) {
|
||||||
$SQL_fieldName = Convert::raw2sql($duplicateCheck);
|
$SQL_fieldName = Convert::raw2sql($duplicateCheck);
|
||||||
|
|
||||||
if(!isset($record[$SQL_fieldName]) || empty($record[$SQL_fieldName])) { //skip current duplicate check if field value is empty
|
if(!isset($record[$SQL_fieldName]) || empty($record[$SQL_fieldName])) { //skip current duplicate check if field value is empty
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$SQL_fieldValue = Convert::raw2sql($record[$SQL_fieldName]);
|
$SQL_fieldValue = Convert::raw2sql($record[$SQL_fieldName]);
|
||||||
$existingRecord = DataObject::get_one($this->objectClass, "\"$SQL_fieldName\" = '{$SQL_fieldValue}'");
|
$existingRecord = DataObject::get_one($this->objectClass, "\"$SQL_fieldName\" = '{$SQL_fieldValue}'");
|
||||||
if($existingRecord) return $existingRecord;
|
|
||||||
|
if($existingRecord) {
|
||||||
|
return $existingRecord;
|
||||||
|
}
|
||||||
} elseif(is_array($duplicateCheck) && isset($duplicateCheck['callback'])) {
|
} elseif(is_array($duplicateCheck) && isset($duplicateCheck['callback'])) {
|
||||||
if($this->hasMethod($duplicateCheck['callback'])) {
|
if($this->hasMethod($duplicateCheck['callback'])) {
|
||||||
$existingRecord = $this->{$duplicateCheck['callback']}($record[$fieldName], $record);
|
$existingRecord = $this->{$duplicateCheck['callback']}($record[$fieldName], $record);
|
||||||
@ -211,6 +229,7 @@ class CsvBulkLoader extends BulkLoader {
|
|||||||
user_error("CsvBulkLoader::processRecord():"
|
user_error("CsvBulkLoader::processRecord():"
|
||||||
. " {$duplicateCheck['callback']} not found on importer or object class.", E_USER_ERROR);
|
. " {$duplicateCheck['callback']} not found on importer or object class.", E_USER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($existingRecord) {
|
if($existingRecord) {
|
||||||
return $existingRecord;
|
return $existingRecord;
|
||||||
}
|
}
|
||||||
@ -218,17 +237,17 @@ class CsvBulkLoader extends BulkLoader {
|
|||||||
user_error('CsvBulkLoader::processRecord(): Wrong format for $duplicateChecks', E_USER_ERROR);
|
user_error('CsvBulkLoader::processRecord(): Wrong format for $duplicateChecks', E_USER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine wether any loaded files should be parsed
|
* Determine whether any loaded files should be parsed with a
|
||||||
* with a header-row (otherwise we rely on {@link self::$columnMap}.
|
* header-row (otherwise we rely on {@link self::$columnMap}.
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function hasHeaderRow() {
|
public function hasHeaderRow() {
|
||||||
return ($this->hasHeaderRow || isset($this->columnMap));
|
return ($this->hasHeaderRow || isset($this->columnMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user