Skip to content

Instantly share code, notes, and snippets.

@eusonlito
Last active June 17, 2021 15:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eusonlito/3b203cf145a23149384322da5a3091a4 to your computer and use it in GitHub Desktop.
Save eusonlito/3b203cf145a23149384322da5a3091a4 to your computer and use it in GitHub Desktop.
Simple PHP CSV parser
<?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;
}
}
<?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