Skip to content

Commit

Permalink
refactor options, add stream reading support
Browse files Browse the repository at this point in the history
  • Loading branch information
lekoala committed Jan 3, 2024
1 parent d1b0be7 commit 75f3f41
Show file tree
Hide file tree
Showing 14 changed files with 223 additions and 79 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ Ideally, importing single sheets of csv or excel should be just a matter of chan
OpenSpout: fast csv and excel import/export
https://github.com/openspout/openspout

League CSV: very fast csv import/export
League CSV: very fast csv import/export. Can read streams.
https://github.com/thephpleague/csv

PhpSpreadsheet: slow excel (xls and xlsx) and csv import/export, but more features
https://github.com/PHPOffice/PhpSpreadsheet

Native php: very fast csv import/export, but limited features
Native php: very fast csv import/export, but limited features. Can read streams.

SimpleXLSX: very fast excel import/export
https://github.com/shuchkin/simplexlsx
Expand Down Expand Up @@ -47,7 +47,9 @@ foreach(SpreadCompat::read('myfile.csv') as $row) {
}
```

## Using named arguments
## Configure

### Using named arguments

This package accepts options using ...opts, this means you can freely use named arguments or pass an array.

Expand All @@ -58,6 +60,16 @@ $data = iterator_to_array(SpreadCompat::read('myfile.csv', assoc: true));
$data = iterator_to_array(SpreadCompat::read('myfile.csv', ...$opts));
```

### Using options object

You can also use the `Options` class that regroups all available options for all adapters. Unsupported options are ignored.

```php
$options = new Options();
$options->separator = ";";
$data = iterator_to_array(SpreadCompat::read('myfile.csv', $options));
```

## Worksheets

This package supports only 1 worksheet, as it is meant to be able to replace csv by xlsx or vice versa
Expand Down
30 changes: 30 additions & 0 deletions src/Common/Configure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace LeKoala\SpreadCompat\Common;

use Exception;

trait Configure
{
public function configure(...$opts): void
{
foreach ($opts as $k => $v) {
// It's an Options class
if ($v instanceof Options) {
$this->configure(...get_object_vars($v));
return;
}
// If you passed the array directly instead of ...$opts
if (is_numeric($k)) {
throw new Exception("Invalid key");
}
// Ignore invalid properties for this adapter
if (!property_exists($this, $k)) {
continue;
}
$this->$k = $v;
}
}
}
38 changes: 38 additions & 0 deletions src/Common/Options.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace LeKoala\SpreadCompat\Common;

class Options
{
use Configure;

// Common
public bool $assoc = false;
/**
* @var string[]
*/
public array $headers = [];

// Csv only
public string $separator = ",";
public string $enclosure = "\"";
public string $escape = "\\";
public string $eol = "\n";
public ?string $inputEncoding = null;
public ?string $outputEncoding = null;
public bool $bom = true;

// Excel only
public ?string $creator = null;
public ?string $autofilter = null;
public ?string $freezePane = null;

public function __construct(...$opts)
{
if (!empty($opts)) {
$this->configure(...$opts);
}
}
}
17 changes: 3 additions & 14 deletions src/Csv/CsvAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

namespace LeKoala\SpreadCompat\Csv;

use Exception;
use LeKoala\SpreadCompat\Common\Configure;
use LeKoala\SpreadCompat\SpreadInterface;

abstract class CsvAdapter implements SpreadInterface
{
use Configure;

public string $separator = ",";
public string $enclosure = "\"";
public string $escape = "\\";
Expand Down Expand Up @@ -37,17 +39,4 @@ public function getOutputEncoding(): ?string
}
return null;
}

public function configure(...$opts): void
{
foreach ($opts as $k => $v) {
if (is_numeric($k)) {
throw new Exception("Invalid key");
}
if (!property_exists($this, $k)) {
continue;
}
$this->$k = $v;
}
}
}
7 changes: 7 additions & 0 deletions src/Csv/League.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public function readString(
yield from $this->read($csv);
}

public function readStream($stream, ...$opts): Generator

Check failure on line 55 in src/Csv/League.php

View workflow job for this annotation

GitHub Actions / build-test

Method LeKoala\SpreadCompat\Csv\League::readStream() has parameter $stream with no type specified.
{
$this->configure(...$opts);
$csv = Reader::createFromStream($stream);
yield from $this->read($csv);
}

public function readFile(
string $filename,
...$opts
Expand Down
24 changes: 16 additions & 8 deletions src/Csv/Native.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,12 @@ public function readString(
}
}

public function readFile(
string $filename,
...$opts
): Generator {
/**
* @param resource $stream
*/
public function readStream($stream, ...$opts): Generator
{
$this->configure(...$opts);
$stream = fopen($filename, 'r');
if (!$stream) {
throw new RuntimeException("Failed to read stream");
}
if (fgets($stream, 4) !== self::BOM) {
// bom not found - rewind pointer to start of file.
rewind($stream);
Expand All @@ -73,6 +70,17 @@ public function readFile(
}
}

public function readFile(
string $filename,
...$opts
): Generator {
$stream = fopen($filename, 'r');
if (!$stream) {
throw new RuntimeException("Failed to read stream");
}
yield from $this->readStream($stream, ...$opts);
}

/**
* @param resource $stream
* @param iterable $data
Expand Down
7 changes: 7 additions & 0 deletions src/Csv/OpenSpout.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace LeKoala\SpreadCompat\Csv;

use Exception;
use Generator;
use LeKoala\SpreadCompat\SpreadCompat;
use RuntimeException;
Expand Down Expand Up @@ -56,6 +57,12 @@ public function readFile(
$reader->close();
}

public function readStream(): Generator
{
//@link https://github.com/openspout/openspout/issues/71
throw new Exception("OpenSpout doesn't support streams");
}

protected function getWriter(): Writer
{
$options = new \OpenSpout\Writer\CSV\Options();
Expand Down
1 change: 0 additions & 1 deletion src/Csv/PhpSpreadsheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace LeKoala\SpreadCompat\Csv;

use Generator;
use RuntimeException;
use LeKoala\SpreadCompat\SpreadCompat;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Reader\Csv as ReaderCsv;
Expand Down
59 changes: 35 additions & 24 deletions src/SpreadCompat.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Exception;
use Generator;
use InvalidArgumentException;
use RuntimeException;

/**
Expand All @@ -27,23 +28,19 @@ class SpreadCompat
public static ?string $preferredCsvAdapter = null;
public static ?string $preferredXslxAdapter = null;

public static function getAdapterName(string $filename, string $ext = null): string
public static function getAdapterName(string $ext): string
{
if ($ext === null) {
$ext = pathinfo($filename, PATHINFO_EXTENSION);
}
$ext = strtolower($ext);

// Legacy xls is only supported by PhpSpreadsheet
if ($ext === self::EXT_XLS) {
if (class_exists(\PhpOffice\PhpSpreadsheet\Worksheet\Row::class)) {
return self::PHP_SPREADSHEET;
}
throw new Exception("No adapter found for xls");
}

if ($ext === self::EXT_CSV) {
if (self::$preferredCsvAdapter) {
if (self::$preferredCsvAdapter !== null) {
return self::$preferredCsvAdapter;
}
if (class_exists(\League\Csv\Reader::class)) {
Expand All @@ -58,7 +55,7 @@ public static function getAdapterName(string $filename, string $ext = null): str
}

if ($ext === self::EXT_XLSX) {
if (self::$preferredXslxAdapter) {
if (self::$preferredXslxAdapter !== null) {
return self::$preferredXslxAdapter;
}
if (class_exists(\Shuchkin\SimpleXLSX::class)) {
Expand All @@ -75,20 +72,25 @@ public static function getAdapterName(string $filename, string $ext = null): str
throw new Exception("No adapter found for $ext");
}

public static function getAdapter(string $filename, string $ext = null): SpreadInterface
public static function getAdapter(string $ext): SpreadInterface
{
if ($ext === null) {
$ext = pathinfo($filename, PATHINFO_EXTENSION);
}
$ext = ucfirst($ext);
$name = self::getAdapterName($filename, $ext);
$name = self::getAdapterName($ext);
$class = 'LeKoala\\SpreadCompat\\' . $ext . '\\' . $name;
if (!class_exists($class)) {
throw new Exception("Invalid adapter $class for $filename");
throw new Exception("Invalid adapter $class");
}
return new ($class);
}

public static function getAdapterForFile(string $filename, string $ext = null): SpreadInterface
{
if ($ext === null) {
$ext = pathinfo($filename, PATHINFO_EXTENSION);
}
return self::getAdapter($ext);
}

public static function getTempFilename(): string
{
$file = tmpfile();
Expand All @@ -104,10 +106,25 @@ public static function read(
...$opts
): Generator {
$ext = $opts['extension'] ?? null;
if ($ext) {
unset($opts['extension']);
return static::getAdapterForFile($filename, $ext)->readFile($filename, ...$opts);
}

public static function readString(
string $contents,
string $ext = null,
...$opts
): Generator {
$ext = $opts['extension'] ?? $ext;
if ($ext === null) {
// Try to determine based on contents
// Expect csv to be all printable chars
if (ctype_print($contents)) {
$ext = self::EXT_CSV;
} else {
$ext = self::EXT_XLSX;
}
}
return static::getAdapter($filename, $ext)->readFile($filename, ...$opts);
return static::getAdapter($ext)->readString($contents, ...$opts);
}

public static function write(
Expand All @@ -116,10 +133,7 @@ public static function write(
...$opts
): bool {
$ext = $opts['extension'] ?? null;
if ($ext) {
unset($opts['extension']);
}
return static::getAdapter($filename, $ext)->writeFile($data, $filename, ...$opts);
return static::getAdapterForFile($filename, $ext)->writeFile($data, $filename, ...$opts);
}

public static function output(
Expand All @@ -128,9 +142,6 @@ public static function output(
...$opts
): void {
$ext = $opts['extension'] ?? null;
if ($ext) {
unset($opts['extension']);
}
static::getAdapter($filename, $ext)->output($data, $filename, ...$opts);
static::getAdapterForFile($filename, $ext)->output($data, $filename, ...$opts);
}
}
17 changes: 3 additions & 14 deletions src/Xls/XlsAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,15 @@

namespace LeKoala\SpreadCompat\Xls;

use Exception;
use LeKoala\SpreadCompat\Common\Configure;
use LeKoala\SpreadCompat\SpreadInterface;

abstract class XlsAdapter implements SpreadInterface
{
use Configure;

public bool $assoc = false;
public ?string $creator = null;
public ?string $autofilter = null;
public ?string $freezePane = null;

public function configure(...$opts): void
{
foreach ($opts as $k => $v) {
if (is_numeric($k)) {
throw new Exception("Invalid key");
}
if (!property_exists($this, $k)) {
continue;
}
$this->$k = $v;
}
}
}
Loading

0 comments on commit 75f3f41

Please sign in to comment.