feat: expand client jobcard and reporting workflows
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
* Serializes tabular data as RFC 4180-compatible CSV.
|
||||
*/
|
||||
final class CsvExporter
|
||||
{
|
||||
private const UTF8_BOM = "\xEF\xBB\xBF";
|
||||
|
||||
/**
|
||||
* @param list<mixed> $headers
|
||||
* @param list<list<mixed>> $rows
|
||||
*/
|
||||
public function export(array $headers, array $rows, bool $withBom = false): string
|
||||
{
|
||||
$records = [];
|
||||
$records[] = $this->formatRecord($headers);
|
||||
|
||||
$headerCount = count($headers);
|
||||
$rowNumber = 0;
|
||||
foreach ($rows as $row) {
|
||||
$rowNumber++;
|
||||
if (!is_array($row)) {
|
||||
throw new InvalidArgumentException(sprintf('CSV row %d must be an array.', $rowNumber));
|
||||
}
|
||||
|
||||
$fieldCount = count($row);
|
||||
if ($fieldCount !== $headerCount) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'CSV row %d has %d fields; expected %d fields for %d headers.',
|
||||
$rowNumber,
|
||||
$fieldCount,
|
||||
$headerCount,
|
||||
$headerCount,
|
||||
));
|
||||
}
|
||||
|
||||
$records[] = $this->formatRecord($row);
|
||||
}
|
||||
|
||||
$csv = implode("\r\n", $records) . "\r\n";
|
||||
return $withBom ? self::UTF8_BOM . $csv : $csv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<mixed> $fields
|
||||
*/
|
||||
private function formatRecord(array $fields): string
|
||||
{
|
||||
return implode(',', array_map(
|
||||
fn (mixed $field): string => $this->escapeField($field),
|
||||
$fields,
|
||||
));
|
||||
}
|
||||
|
||||
private function escapeField(mixed $field): string
|
||||
{
|
||||
if ($field === null) {
|
||||
$value = '';
|
||||
} elseif (is_scalar($field)) {
|
||||
$value = (string)$field;
|
||||
} else {
|
||||
throw new InvalidArgumentException('CSV fields must be scalar values or null.');
|
||||
}
|
||||
|
||||
// Prefix formula-like values so spreadsheet programs treat them as text.
|
||||
if ($value !== '' && preg_match('/^[=+\-@]/', $value) === 1) {
|
||||
$value = "'" . $value;
|
||||
}
|
||||
|
||||
if (strpbrk($value, ",\"\r\n") === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return '"' . str_replace('"', '""', $value) . '"';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user