Created
May 24, 2025 13:51
-
-
Save nrctkno/1ee321946a6ef412a668b82743b6c357 to your computer and use it in GitHub Desktop.
Example of CSV handler for PHP
This file contains hidden or 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); | |
final class CSVFile | |
{ | |
const HEADERS_NONE = 0; | |
const HEADERS_VERBATIM = 1; | |
const HEADERS_NORMALIZED = 2; | |
private SplFileObject $file; | |
private function __construct(SplFileObject $file) | |
{ | |
$this->file = $file; | |
} | |
public static function fromRequestData($data): static { | |
if (empty($data)) { | |
throw new BadRequestException('File upload failed, try again.'); | |
} | |
if ($data['error'] !== 0) { | |
$reason = match ($data['error']) { | |
UPLOAD_ERR_INI_SIZE => 'File too large', | |
UPLOAD_ERR_FORM_SIZE => 'File too large', | |
UPLOAD_ERR_PARTIAL => 'File upload incomplete', | |
UPLOAD_ERR_NO_FILE => 'No file uploaded', | |
UPLOAD_ERR_NO_TMP_DIR => 'No temporary directory', | |
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk', | |
UPLOAD_ERR_EXTENSION => 'File upload stopped by extension', | |
default => 'Unknown error occurred' | |
}; | |
throw new BadRequestException('File upload failed: '. $reason); | |
} | |
$allowed_mime = [ | |
'text/x-comma-separated-values', | |
'text/comma-separated-values', | |
'application/octet-stream', | |
'application/vnd.ms-excel', | |
'application/x-csv', | |
'text/x-csv', | |
'text/csv', | |
'application/csv', | |
'application/excel', | |
'application/vnd.msexcel', | |
'text/plain' | |
]; | |
if (empty($data['type']) || !in_array($data['type'], $allowed_mime)) { | |
throw new BadRequestException('Invalid file type ('.$data['type'].'), expecting CSV file.'); | |
} | |
if (empty($data['tmp_name']) || !is_uploaded_file($data['tmp_name'])) { | |
throw new BadRequestException('Session upload failed, please try again.'); | |
} | |
if ($data['error'] !== \UPLOAD_ERR_OK) { | |
throw new BadRequestException( | |
"Error uploading session file (error code {$data['error']}), is file corrupted?" | |
); | |
} | |
try { | |
$file = new SplFileObject($data['tmp_name'], 'r'); | |
} catch (\RuntimeException $e) { | |
throw new BadRequestException('Could not open file for processing: ' . $e->getMessage()); | |
} | |
return new self($file); | |
} | |
public function getIterator( | |
int $headersStyle = self::HEADERS_NONE, | |
string $separator = ',', | |
string $enclosure = '"', | |
string $escape = '\\' | |
): Generator { | |
$this->file->setFlags( | |
SplFileObject::READ_CSV | // Parse CSV | |
SplFileObject::SKIP_EMPTY | // Skip empty lines | |
SplFileObject::READ_AHEAD | // Read ahead optimization | |
SplFileObject::DROP_NEW_LINE // Drop newlines | |
); | |
$this->file->setCsvControl($separator, $enclosure, $escape); | |
if ($headersStyle > self::HEADERS_NONE && !$this->file->eof()) { | |
$headers = $this->file->fgetcsv(); | |
if ($headersStyle === self::HEADERS_NORMALIZED) { | |
// Normalize headers by removing whitespace and converting to lowercase | |
$headers = array_map(fn($header) => str_replace(' ', '_', strtolower(trim($header))), $headers); | |
} | |
} | |
while (!$this->file->eof()) { | |
$row = $this->file->fgetcsv(); | |
// Skip invalid or empty rows | |
if ($row === false) { | |
continue; | |
} | |
// Skip rows that are empty or contain only empty values | |
if (empty($row) || (count($row) === 1 && empty(trim($row[0])))) { | |
continue; | |
} | |
// If using headers, map the row values to the headers | |
if ($headersStyle > self::HEADERS_NONE) { | |
$row = array_combine($headers, $row); | |
if ($row === false) { | |
throw new RuntimeException('Invalid CSV format: inconsistent number of columns'); | |
} | |
} | |
yield $row; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment