diff --git a/.gitattributes b/.gitattributes
index 516c22d..dd9dfa6 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,10 +1,8 @@
-spec/ export-ignore
tests/ export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
-.scrutinizer.yml export-ignore
-.travis.yml export-ignore
CONTRIBUTING.md export-ignore
-phpspec.yml.dist export-ignore
phpunit.xml.dist export-ignore
+.github/ export-ignore
+UPGRADE-2.0.md export-ignore
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..f7b8104
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 5
+ - package-ecosystem: composer
+ directory: /
+ schedule:
+ interval: monthly
+ open-pull-requests-limit: 5
+ versioning-strategy: widen
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..53aad83
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,61 @@
+name: Test
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ php-versions: [ '8.2', '8.3', '8.4', '8.5' ]
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v7
+
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-versions }}
+ coverage: none
+ tools: composer:v2
+ extensions: zip, gd, xml, mbstring
+
+ - name: Validate Composer metadata
+ run: |
+ if [ -f composer.lock ]; then
+ composer validate --strict --no-interaction
+ else
+ composer validate --strict --no-check-lock --no-interaction
+ fi
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
+
+ - name: Cache Composer packages
+ uses: actions/cache@v6
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ matrix.php-versions }}-${{ hashFiles('**/composer.json', '**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-${{ matrix.php-versions }}-
+
+ - name: Install dependencies
+ run: |
+ if [ -f composer.lock ]; then
+ composer install --prefer-dist --no-progress --no-interaction
+ else
+ composer update --prefer-dist --no-progress --no-interaction
+ fi
+
+ - name: Run test suite
+ run: vendor/bin/phpunit
diff --git a/.gitignore b/.gitignore
index 9780341..d75da34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ composer.phar
composer.lock
phpspec.yml
phpunit.xml
+vendor/
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
deleted file mode 100644
index acbda73..0000000
--- a/.scrutinizer.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-checks:
- php:
- code_rating: true
- duplication: true
-
-tools:
- external_code_coverage: true
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index fa84aa6..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-language: php
-
-php:
- - 5.6
- - 7.0
- - 7.1
- - 7.2
- - 7.3
- - hhvm
-
-matrix:
- allow_failures:
- - php: hhvm
- include:
- - php: 5.6
- env:
- - COMPOSER_FLAGS="--prefer-stable"
- - COVERAGE=true
- - PHPUNIT_FLAGS="--coverage-clover=coverage.clover"
-
-install:
- - travis_retry composer update ${COMPOSER_FLAGS} --prefer-source --no-interaction
-
-script:
- - vendor/bin/phpunit ${PHPUNIT_FLAGS}
-
-after_script:
- - if [[ "$COVERAGE" = true ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
- - if [[ "$COVERAGE" = true ]]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
diff --git a/README.md b/README.md
index d739ba3..ee6cf55 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,29 @@
# portphp/spreadsheet
[](https://github.com/portphp/spreadsheet/releases)
-[](https://travis-ci.org/portphp/spreadsheet)
-[](https://scrutinizer-ci.com/g/portphp/spreadsheet/?branch=master)
-[](https://scrutinizer-ci.com/g/portphp/spreadsheet/?branch=master)
+[](https://github.com/portphp/spreadsheet/actions/workflows/test.yml)
+[](https://packagist.org/packages/portphp/spreadsheet)
-[PhpSpreadsheet](https://github.com/PHPOffice/PhpSpreadsheet) reader and writer
-for [Port](https://github.com/portphp).
+**Requirements:** PHP ^8.2 (tested on 8.2–8.5). Requires `portphp/portphp` ^2.0 and PhpSpreadsheet ^1.29 || ^2–^5.
-## Installation
+[PhpSpreadsheet](https://github.com/PHPOffice/PhpSpreadsheet) reader and writer for [Port](https://github.com/portphp).
+
+> Prefer this package over `portphp/excel` (PHPExcel, abandoned).
-Open a command console, enter your project directory and execute the
-following command to download the latest stable version of this package:
+## Installation
```bash
$ composer require portphp/spreadsheet
```
-This command requires you to have Composer installed globally, as explained
-in the [installation chapter](https://getcomposer.org/doc/00-intro.md)
-of the Composer documentation.
-
## Documentation
-Documentation is available at http://docs.portphp.org.
+Documentation is available at https://portphp.readthedocs.io.
## Issues and feature requests
Please report issues and request features at https://github.com/portphp/portphp/issues.
-## Contributing
-
-Contributions are very welcome. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for
-details. Thanks to [everyone who has contributed](https://github.com/portphp/spreadsheet/graphs/contributors)
-already.
-
-## Security
-
-If you discover any security-related issues, please contact us at
-[security@portphp.org](mailto:security@portphp.org).
-
## License
This package is licensed under the [MIT license](LICENSE).
diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md
new file mode 100644
index 0000000..874bbae
--- /dev/null
+++ b/UPGRADE-2.0.md
@@ -0,0 +1,9 @@
+# Upgrade from 1.x to 2.0
+
+- Minimum PHP is **8.2** (`^8.2`).
+- Requires `portphp/portphp` **^2.0**.
+- Writer methods use `: void` return types from the core interfaces.
+- Iterator methods on `SpreadsheetReader` use PHP 8 return types (`mixed`, `void`, etc.).
+- `phpoffice/phpspreadsheet` supports **^1.29 || ^2 || ^3 || ^4 || ^5** (uses coordinate API compatible with 2+).
+- CI is GitHub Actions (Travis/Scrutinizer removed).
+- PHPUnit 9 replaces the old PHPUnit 4 suite; phpspec is no longer a dependency.
diff --git a/composer.json b/composer.json
index 29eba03..25d1bd4 100644
--- a/composer.json
+++ b/composer.json
@@ -20,12 +20,12 @@
"support": {
"issues": "https://github.com/portphp/portphp/issues",
"source": "https://github.com/portphp/spreadsheet",
- "docs": "http://docs.portphp.org"
+ "docs": "https://portphp.readthedocs.io"
},
"require": {
- "php": ">=5.6.0",
- "portphp/portphp": "^1.3",
- "phpoffice/phpspreadsheet": "^1.3"
+ "php": "^8.2",
+ "portphp/portphp": "^2.0",
+ "phpoffice/phpspreadsheet": "^1.29 || ^2.0 || ^3.0 || ^4.0 || ^5.0"
},
"autoload": {
"psr-4": {
@@ -33,8 +33,7 @@
}
},
"require-dev": {
- "phpunit/phpunit": "^4.0",
- "phpspec/phpspec": "^2.1"
+ "phpunit/phpunit": "^9.6"
},
"autoload-dev": {
"psr-4": {
@@ -43,7 +42,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "2.0.x-dev"
}
}
}
diff --git a/phpspec.yml.dist b/phpspec.yml.dist
deleted file mode 100644
index bf0be48..0000000
--- a/phpspec.yml.dist
+++ /dev/null
@@ -1,5 +0,0 @@
-suites:
- library_suite:
- namespace: Port\Spreadsheet
- psr4_prefix: Port\Spreadsheet
-formatter.name: pretty
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 6653884..315d824 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,20 +1,14 @@
-
-
+ cacheResult="false"
+ failOnWarning="true"
+ failOnRisky="true">
- ./tests/
+ ./tests
diff --git a/src/SpreadsheetReader.php b/src/SpreadsheetReader.php
index 1a42b96..1074102 100644
--- a/src/SpreadsheetReader.php
+++ b/src/SpreadsheetReader.php
@@ -65,17 +65,15 @@ class SpreadsheetReader implements CountableReader, \SeekableIterator
*/
protected $worksheet;
- // phpcs:disable Generic.Files.LineLength.MaxExceeded
/**
* @param \SplFileObject $file Spreadsheet file
- * @param int $headerRowNumber Optional number of header row
- * @param int $activeSheet Index of active sheet to read from
+ * @param int|null $headerRowNumber Optional number of header row
+ * @param int|null $activeSheet Index of active sheet to read from
* @param bool $readOnly If set to false, the reader take care of the spreadsheet formatting (slow)
- * @param int $maxRows Maximum number of rows to read
+ * @param int|null $maxRows Maximum number of rows to read
*/
- public function __construct(\SplFileObject $file, $headerRowNumber = null, $activeSheet = null, $readOnly = true, $maxRows = null)
+ public function __construct(\SplFileObject $file, ?int $headerRowNumber = null, ?int $activeSheet = null, bool $readOnly = true, ?int $maxRows = null)
{
- // phpcs:enable Generic.Files.LineLength.MaxExceeded
$reader = IOFactory::createReaderForFile($file->getPathName());
$reader->setReadDataOnly($readOnly);
/** @var Spreadsheet $spreadsheet */
@@ -103,7 +101,7 @@ public function __construct(\SplFileObject $file, $headerRowNumber = null, $acti
/**
* @return int
*/
- public function count()
+ public function count(): int
{
$count = count($this->worksheet);
if (null !== $this->headerRowNumber) {
@@ -122,7 +120,7 @@ public function count()
*
* @author Derek Chafin
*/
- public function current()
+ public function current(): mixed
{
$row = $this->worksheet[$this->pointer];
@@ -141,7 +139,7 @@ public function current()
*
* @return array
*/
- public function getColumnHeaders()
+ public function getColumnHeaders(): array
{
return $this->columnHeaders;
}
@@ -151,9 +149,9 @@ public function getColumnHeaders()
*
* @param int $number
*
- * @return array
+ * @return array|null
*/
- public function getRow($number)
+ public function getRow(int $number): mixed
{
$this->seek($number);
@@ -165,17 +163,15 @@ public function getRow($number)
*
* @return int
*/
- public function key()
+ public function key(): mixed
{
return $this->pointer;
}
/**
* Move forward to next element
- *
- * @return void Any returned value is ignored.
*/
- public function next()
+ public function next(): void
{
$this->pointer++;
}
@@ -186,10 +182,8 @@ public function next()
* If a header row has been set, the pointer is set just below the header
* row. That way, when you iterate over the rows, that header row is
* skipped.
- *
- * @return void Any returned value is ignored.
*/
- public function rewind()
+ public function rewind(): void
{
if (null === $this->headerRowNumber) {
$this->pointer = 0;
@@ -204,10 +198,8 @@ public function rewind()
* @link http://php.net/manual/en/seekableiterator.seek.php
*
* @param int $pointer The position to seek to.
- *
- * @return void Any returned value is ignored.
*/
- public function seek($pointer)
+ public function seek(int $pointer): void
{
$this->pointer = $pointer;
}
@@ -216,10 +208,8 @@ public function seek($pointer)
* Set column headers
*
* @param array $columnHeaders
- *
- * @return void Any returned value is ignored.
*/
- public function setColumnHeaders(array $columnHeaders)
+ public function setColumnHeaders(array $columnHeaders): void
{
$this->columnHeaders = $columnHeaders;
}
@@ -228,10 +218,8 @@ public function setColumnHeaders(array $columnHeaders)
* Set header row number
*
* @param int $rowNumber Number of the row that contains column header names
- *
- * @return void Any returned value is ignored.
*/
- public function setHeaderRowNumber($rowNumber)
+ public function setHeaderRowNumber(int $rowNumber): void
{
$this->headerRowNumber = $rowNumber;
$this->columnHeaders = $this->worksheet[$rowNumber];
@@ -240,10 +228,9 @@ public function setHeaderRowNumber($rowNumber)
/**
* Checks if current position is valid
*
- * @return bool The return value will be casted to boolean and then evaluated.
- * Returns true on success or false on failure.
+ * @return bool
*/
- public function valid()
+ public function valid(): bool
{
return isset($this->worksheet[$this->pointer]);
}
diff --git a/src/SpreadsheetReaderFactory.php b/src/SpreadsheetReaderFactory.php
index 086aa97..4e7f8cd 100644
--- a/src/SpreadsheetReaderFactory.php
+++ b/src/SpreadsheetReaderFactory.php
@@ -24,6 +24,7 @@
*/
namespace Port\Spreadsheet;
+use Port\Reader;
use Port\Reader\ReaderFactory;
/**
@@ -34,20 +35,20 @@
class SpreadsheetReaderFactory implements ReaderFactory
{
/**
- * @var int
+ * @var int|null
*/
protected $activeSheet;
/**
- * @var int
+ * @var int|null
*/
protected $headerRowNumber;
/**
- * @param int $headerRowNumber
- * @param int $activeSheet
+ * @param int|null $headerRowNumber
+ * @param int|null $activeSheet
*/
- public function __construct($headerRowNumber = null, $activeSheet = null)
+ public function __construct(?int $headerRowNumber = null, ?int $activeSheet = null)
{
$this->headerRowNumber = $headerRowNumber;
$this->activeSheet = $activeSheet;
@@ -58,7 +59,7 @@ public function __construct($headerRowNumber = null, $activeSheet = null)
*
* @return SpreadsheetReader
*/
- public function getReader(\SplFileObject $file)
+ public function getReader(\SplFileObject $file): Reader
{
return new SpreadsheetReader($file, $this->headerRowNumber, $this->activeSheet);
}
diff --git a/src/SpreadsheetWriter.php b/src/SpreadsheetWriter.php
index 05d471e..ab3b35d 100644
--- a/src/SpreadsheetWriter.php
+++ b/src/SpreadsheetWriter.php
@@ -24,6 +24,7 @@
*/
namespace Port\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use Port\Writer;
@@ -67,11 +68,11 @@ class SpreadsheetWriter implements Writer
/**
* @param \SplFileObject $file File
- * @param string $sheet Sheet title (optional)
+ * @param string|null $sheet Sheet title (optional)
* @param string $type Spreadsheet file type (defaults to Xlsx)
* @param bool $prependHeaderRow
*/
- public function __construct(\SplFileObject $file, $sheet = null, $type = 'Xlsx', $prependHeaderRow = false)
+ public function __construct(\SplFileObject $file, ?string $sheet = null, string $type = 'Xlsx', bool $prependHeaderRow = false)
{
$this->filename = $file->getPathname();
$this->sheet = $sheet;
@@ -81,10 +82,8 @@ public function __construct(\SplFileObject $file, $sheet = null, $type = 'Xlsx',
/**
* Wrap up the writer after all items have been written
- *
- * @return void Any returned value is ignored.
*/
- public function finish()
+ public function finish(): void
{
$writer = IOFactory::createWriter($this->spreadsheet, $this->type);
$writer->save($this->filename);
@@ -92,10 +91,8 @@ public function finish()
/**
* Prepare the writer before writing the items
- *
- * @return void Any returned value is ignored.
*/
- public function prepare()
+ public function prepare(): void
{
$reader = IOFactory::createReader($this->type);
if ($reader->canRead($this->filename)) {
@@ -116,10 +113,8 @@ public function prepare()
* Write one data item
*
* @param array $item The data item with converted values
- *
- * @return void Any returned value is ignored.
*/
- public function writeItem(array $item)
+ public function writeItem(array $item): void
{
$count = count($item);
@@ -127,7 +122,8 @@ public function writeItem(array $item)
$headers = array_keys($item);
for ($i = 0; $i < $count; $i++) {
- $this->spreadsheet->getActiveSheet()->setCellValueByColumnAndRow($i + 1, $this->row, $headers[$i]);
+ $col = Coordinate::stringFromColumnIndex($i + 1);
+ $this->spreadsheet->getActiveSheet()->setCellValue($col . $this->row, $headers[$i]);
}
$this->row++;
}
@@ -135,7 +131,8 @@ public function writeItem(array $item)
$values = array_values($item);
for ($i = 0; $i < $count; $i++) {
- $this->spreadsheet->getActiveSheet()->setCellValueByColumnAndRow($i + 1, $this->row, $values[$i]);
+ $col = Coordinate::stringFromColumnIndex($i + 1);
+ $this->spreadsheet->getActiveSheet()->setCellValue($col . $this->row, $values[$i]);
}
$this->row++;
diff --git a/tests/SpreadsheetReaderFactoryTest.php b/tests/SpreadsheetReaderFactoryTest.php
index 630c763..f3f059b 100644
--- a/tests/SpreadsheetReaderFactoryTest.php
+++ b/tests/SpreadsheetReaderFactoryTest.php
@@ -29,12 +29,12 @@
/**
* {@inheritDoc}
*/
-class SpreadsheetReaderFactoryTest extends \PHPUnit_Framework_TestCase
+class SpreadsheetReaderFactoryTest extends \PHPUnit\Framework\TestCase
{
/**
* {@inheritDoc}
*/
- public function setUp()
+ public function setUp(): void
{
if (!extension_loaded('zip')) {
$this->markTestSkipped();
diff --git a/tests/SpreadsheetReaderTest.php b/tests/SpreadsheetReaderTest.php
index 56ddf93..a23eacd 100644
--- a/tests/SpreadsheetReaderTest.php
+++ b/tests/SpreadsheetReaderTest.php
@@ -29,12 +29,12 @@
/**
* {@inheritDoc}
*/
-class SpreadsheetReaderTest extends \PHPUnit_Framework_TestCase
+class SpreadsheetReaderTest extends \PHPUnit\Framework\TestCase
{
/**
* {@inheritDoc}
*/
- public function setUp()
+ public function setUp(): void
{
if (!extension_loaded('zip')) {
$this->markTestSkipped();
@@ -247,7 +247,7 @@ public function testIterateWithoutHeaders()
public function testMaxRowNumb()
{
$file = new \SplFileObject(__DIR__.'/fixtures/data_no_column_headers.xls');
- $reader = new SpreadsheetReader($file, null, null, null, 1000);
+ $reader = new SpreadsheetReader($file, null, null, true, 1000);
$this->assertEquals(3, $reader->count());
// Without $maxRows, this faulty file causes OOM because of an extremely
@@ -255,7 +255,7 @@ public function testMaxRowNumb()
$file = new \SplFileObject(__DIR__.'/fixtures/data_extreme_last_row.xlsx');
$max = 5;
- $reader = new SpreadsheetReader($file, null, null, null, $max);
+ $reader = new SpreadsheetReader($file, null, null, true, $max);
$this->assertEquals($max, $reader->count());
}
diff --git a/tests/SpreadsheetWriterTest.php b/tests/SpreadsheetWriterTest.php
index 84b1dc8..6ed7218 100644
--- a/tests/SpreadsheetWriterTest.php
+++ b/tests/SpreadsheetWriterTest.php
@@ -30,12 +30,12 @@
/**
* {@inheritDoc}
*/
-class SpreadsheetWriterTest extends \PHPUnit_Framework_TestCase
+class SpreadsheetWriterTest extends \PHPUnit\Framework\TestCase
{
/**
* {@inheritDoc}
*/
- public function setUp()
+ public function setUp(): void
{
if (!extension_loaded('zip')) {
$this->markTestSkipped();
@@ -50,7 +50,7 @@ public function setUp()
*/
public function testHeaderNotPrependedByDefault()
{
- $file = tempnam(sys_get_temp_dir(), null);
+ $file = tempnam(sys_get_temp_dir(), 'portphp');
$writer = new SpreadsheetWriter(new \SplFileObject($file, 'w'), null, 'Xlsx');
$writer->prepare();
@@ -77,7 +77,7 @@ public function testHeaderNotPrependedByDefault()
*/
public function testHeaderPrependedWhenOptionSetToTrue()
{
- $file = tempnam(sys_get_temp_dir(), null);
+ $file = tempnam(sys_get_temp_dir(), 'portphp');
$writer = new SpreadsheetWriter(new \SplFileObject($file, 'w'), null, 'Xlsx', true);
$writer->prepare();
@@ -103,7 +103,7 @@ public function testHeaderPrependedWhenOptionSetToTrue()
*/
public function testWriteItemAppendWithSheetTitle()
{
- $file = tempnam(sys_get_temp_dir(), null);
+ $file = tempnam(sys_get_temp_dir(), 'portphp');
$writer = new SpreadsheetWriter(new \SplFileObject($file, 'w'), 'Sheet 1');
@@ -150,13 +150,14 @@ public function testWriteItemAppendWithSheetTitle()
*/
public function testWriteItemWithoutSheetTitle()
{
- $outputFile = new \SplFileObject(tempnam(sys_get_temp_dir(), null));
- $writer = new SpreadsheetWriter($outputFile);
+ $path = tempnam(sys_get_temp_dir(), 'portphp');
+ $writer = new SpreadsheetWriter(new \SplFileObject($path, 'w'));
$writer->prepare();
-
$writer->writeItem(array('first', 'last'));
-
$writer->finish();
+
+ $excel = IOFactory::load($path);
+ $this->assertEquals(array('first', 'last'), $excel->getActiveSheet()->toArray()[0]);
}
}