Last active
June 17, 2021 15:10
-
-
Save eusonlito/3b203cf145a23149384322da5a3091a4 to your computer and use it in GitHub Desktop.
Simple PHP CSV parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php declare(strict_types=1); | |
class CsvRead | |
{ | |
/** | |
* @var array | |
*/ | |
protected array $csv; | |
/** | |
* @var string | |
*/ | |
protected string $delimiter; | |
/** | |
* @var bool | |
*/ | |
protected bool $hasHeader = true; | |
/** | |
* @var bool | |
*/ | |
protected bool $keysAsHeader = true; | |
/** | |
* @var array | |
*/ | |
protected array $keys = []; | |
/** | |
* @var array | |
*/ | |
protected array $header = []; | |
/** | |
* @var ?array | |
*/ | |
protected ?array $rows = null; | |
/** | |
* @var array | |
*/ | |
protected array $sanitize = []; | |
/** | |
* @param string $file | |
* @param string $delimiter = ',' | |
* | |
* @return self | |
*/ | |
public static function fromFile(string $file, string $delimiter = ',') | |
{ | |
if (is_file($file) === false) { | |
throw new Exception(sprintf('File %s not exists', $file)); | |
} | |
return new self(file_get_contents($file), $delimiter); | |
} | |
/** | |
* @param string $csv | |
* @param string $delimiter = ',' | |
* | |
* @return self | |
*/ | |
public function __construct(string $csv, string $delimiter = ',') | |
{ | |
$this->csv = array_values(array_filter(str_getcsv($csv, "\n"))); | |
$this->delimiter = $delimiter; | |
} | |
/** | |
* @param bool $hasHeader | |
* | |
* @return self | |
*/ | |
public function hasHeader(bool $hasHeader): self | |
{ | |
$this->hasHeader = $hasHeader; | |
return $this; | |
} | |
/** | |
* @param bool $keysAsHeader | |
* | |
* @return self | |
*/ | |
public function setKeysAsHeader(bool $keysAsHeader): self | |
{ | |
$this->keysAsHeader = $keysAsHeader; | |
return $this; | |
} | |
/** | |
* @param array $sanitize | |
* | |
* @return self | |
*/ | |
public function setSanitize(array $sanitize): self | |
{ | |
foreach ($sanitize as $key => $callback) { | |
if (is_string($callback)) { | |
$callback = Closure::fromCallable($callback); | |
} | |
if (is_callable($callback) === false) { | |
throw new Exception(sprintf('Sanitizer for "%s" is not a valid callback', $key)); | |
} | |
$this->sanitize[$key] = $callback; | |
} | |
return $this; | |
} | |
/** | |
* @return array | |
*/ | |
public function parse(): array | |
{ | |
if (isset($this->rows)) { | |
return $this->rows; | |
} | |
if (empty($this->csv)) { | |
return $this->rows = []; | |
} | |
$this->parseHeader(); | |
$this->parseRows(); | |
return $this->rows; | |
} | |
/** | |
* @return void | |
*/ | |
protected function parseHeader(): void | |
{ | |
$first = str_getcsv($this->csv[0], $this->delimiter); | |
if ($this->keys) { | |
$this->header = $this->keys; | |
} elseif ($this->keysAsHeader) { | |
$this->header = $first; | |
} else { | |
$this->header = range(0, count($first) - 1); | |
} | |
if (count($this->header) !== count($first)) { | |
throw new Exception(sprintf('First CSV file %s do not match with header keys %s', $this->csv[0], implode(',', $this->header))); | |
} | |
if ($this->hasHeader) { | |
array_shift($this->csv); | |
} | |
} | |
/** | |
* @return void | |
*/ | |
protected function parseRows(): void | |
{ | |
$this->rows = []; | |
foreach ($this->csv as $line) { | |
$this->rows[] = $this->parseRow($line); | |
} | |
} | |
/** | |
* @param string $line | |
* | |
* @return array | |
*/ | |
protected function parseRow(string $line): array | |
{ | |
$line = str_getcsv($line, $this->delimiter); | |
$row = []; | |
foreach ($this->header as $index => $header) { | |
$row[$header] = $line[$index] ?? null; | |
} | |
return $this->parseRowSanitize($row); | |
} | |
/** | |
* @param array $row | |
* | |
* @return array | |
*/ | |
protected function parseRowSanitize(array $row): array | |
{ | |
foreach ($this->sanitize as $key => $callback) { | |
if (array_key_exists($key, $row)) { | |
$row[$key] = $callback($row[$key]); | |
} | |
} | |
return $row; | |
} | |
/** | |
* @return array | |
*/ | |
public function getHeader(): array | |
{ | |
return $this->header; | |
} | |
/** | |
* @return ?array | |
*/ | |
public function getRows(): ?array | |
{ | |
return $this->rows; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php declare(strict_types=1); | |
$rows = CsvRead::fromFile('products.csv') | |
->setKeys(['id', 'name', 'price']) | |
->setSanitize(['id' => 'intval', 'name' => 'trim']) | |
->setSanitize(['price' => fn ($price) => floatval(preg_replace('/[^0-9\.]/', '', (string)$price))]) | |
->parse(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment