Created
February 13, 2024 17:22
-
-
Save inxilpro/5367bdf60971f78b0310e32d9b1a9579 to your computer and use it in GitHub Desktop.
Simple wrapper for OpenSpout
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 | |
// Reading | |
CsvReader::read($path)->each(function(array $row) { | |
// Do something with $row | |
}); | |
// Writing | |
return CsvWriter::for($data)->writeToHttpFile(); |
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 | |
namespace App\Support; | |
use Illuminate\Support\LazyCollection; | |
use Illuminate\Support\Str; | |
use OpenSpout\Reader\CSV\Reader; | |
use UnexpectedValueException; | |
class CsvReader | |
{ | |
public static function read(string $path): LazyCollection | |
{ | |
return (new static($path))->collect(); | |
} | |
public function __construct( | |
protected string $path, | |
) { | |
} | |
public function collect(): LazyCollection | |
{ | |
return new LazyCollection(function() { | |
$reader = new Reader(); | |
$reader->open($this->path); | |
try { | |
foreach ($reader->getSheetIterator() as $sheet) { | |
$columns = 0; | |
$headers = null; | |
foreach ($sheet->getRowIterator() as $row) { | |
if (null === $headers) { | |
$headers = array_map(Str::snake(...), $row->toArray()); | |
$columns = count($headers); | |
continue; | |
} | |
$data = $row->toArray(); | |
$data_columns = count($data); | |
if ($columns < $data_columns) { | |
throw new UnexpectedValueException("Expected {$columns} columns of data but got {$data_columns}"); | |
} | |
if ($columns > $data_columns) { | |
$data = array_merge($data, array_fill(0, $columns - $data_columns, null)); | |
} | |
yield array_combine($headers, $data); | |
} | |
} | |
} finally { | |
$reader->close(); | |
} | |
}); | |
} | |
} |
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 | |
namespace App\Support; | |
use Closure; | |
use Generator; | |
use Illuminate\Contracts\Database\Query\Builder; | |
use Illuminate\Http\File; | |
use Illuminate\Support\Collection; | |
use Illuminate\Support\Enumerable; | |
use Illuminate\Support\Facades\App; | |
use Illuminate\Support\LazyCollection; | |
use Illuminate\Support\Str; | |
use OpenSpout\Common\Entity\Row; | |
use OpenSpout\Writer\CSV\Options; | |
use OpenSpout\Writer\CSV\Writer; | |
class CsvWriter | |
{ | |
protected Closure $header_formatter; | |
public static function for(array|Enumerable|Generator|Builder $data): static | |
{ | |
return new static($data); | |
} | |
public function __construct( | |
protected array|Enumerable|Generator|Builder $data, | |
protected bool $headers = true, | |
protected string $delimiter = ',', | |
protected string $enclosure = '"', | |
protected bool $bom = true, | |
protected bool $withoutEmptyNewLine = false, | |
) { | |
$this->header_formatter = Str::headline(...); | |
} | |
public function withoutHeaders(): static | |
{ | |
$this->headers = false; | |
return $this; | |
} | |
public function withOriginalKeysAsHeaders(): static | |
{ | |
$this->header_formatter = static fn($key) => $key; | |
return $this; | |
} | |
public function withDelimiter(string $delimiter): static | |
{ | |
$this->delimiter = $delimiter; | |
return $this; | |
} | |
public function withEnclosure(string $enclosure): static | |
{ | |
$this->enclosure = $enclosure; | |
return $this; | |
} | |
public function withoutBom(): static | |
{ | |
$this->bom = false; | |
return $this; | |
} | |
public function withoutEmptyNewLineAtEndOfFile(): static | |
{ | |
$this->withoutEmptyNewLine = true; | |
return $this; | |
} | |
public function writeToHttpFile(): File | |
{ | |
$path = $this->writeToTemporaryFile(); | |
return new File($path); | |
} | |
public function writeToTemporaryFile(): string | |
{ | |
$path = tempnam(sys_get_temp_dir(), 'csv-writer-data'); | |
App::terminating(fn() => @unlink($path)); | |
return $this->write($path); | |
} | |
public function write(string $path): string | |
{ | |
$options = new Options(); | |
$options->FIELD_DELIMITER = $this->delimiter; | |
$options->FIELD_ENCLOSURE = $this->enclosure; | |
$options->SHOULD_ADD_BOM = $this->bom; | |
$writer = new Writer($options); | |
$writer->openToFile($path); | |
foreach ($this->rows() as $row) { | |
$writer->addRow(Row::fromValues($row->toArray())); | |
} | |
$writer->close(); | |
if ($this->withoutEmptyNewLine) { | |
file_put_contents($path, rtrim(file_get_contents($path), PHP_EOL)); | |
} | |
return $path; | |
} | |
protected function rows(): Generator | |
{ | |
$source = match (true) { | |
$this->data instanceof Generator => LazyCollection::make($this->data), | |
is_array($this->data) => Collection::make($this->data), | |
$this->data instanceof Builder => $this->data->lazyById(), | |
default => $this->data, | |
}; | |
$needs_headers = $this->headers; | |
foreach ($source as $row) { | |
$row = Collection::make($row); | |
if ($needs_headers) { | |
$needs_headers = false; | |
yield $row->keys()->map($this->header_formatter); | |
} | |
yield $row; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment