-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Spout implementation #1
base: master
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
<?php | ||
|
||
namespace Port\Spout; | ||
|
||
use Box\Spout\Common\Type; | ||
use Box\Spout\Reader\ReaderFactory; | ||
use OutOfBoundsException; | ||
use Port\Exception\ReaderException; | ||
use Port\Reader\CountableReader; | ||
use SeekableIterator; | ||
|
||
|
||
/** | ||
* Reads Excel files with the help of Spout | ||
* | ||
* Spout must be installed. | ||
* | ||
* @link http://opensource.box.com/spout/ | ||
* @link https://github.com/box/spout | ||
*/ | ||
class SpoutReader implements CountableReader, SeekableIterator | ||
{ | ||
/** | ||
* @var \Box\Spout\Reader\XLSX\Sheet | ||
*/ | ||
protected $sheet; | ||
|
||
/** | ||
* @var integer | ||
*/ | ||
protected $headerRowNumber; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
protected $columnHeaders; | ||
|
||
/** | ||
* Total number of rows | ||
* | ||
* @var integer | ||
*/ | ||
protected $count; | ||
|
||
/** | ||
* @param \SplFileObject $file Excel file | ||
* @param int $headerRowNumber Optional number of header row | ||
* @param int $activeSheet Index of active sheet to read from | ||
* @param bool $shouldPreserveEmptyRows Sets whether empty rows should be returned or skipped | ||
* | ||
* @throws ReaderException | ||
*/ | ||
public function __construct(\SplFileObject $file, $headerRowNumber = null, $activeSheet = null, $shouldPreserveEmptyRows = true) | ||
{ | ||
$reader = $this->createReaderForFile($file, $shouldPreserveEmptyRows); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather inject the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed, the user must call the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this fix, the But the method Maybe, I will not implement the |
||
|
||
$activeSheet = null === $activeSheet ? 0 : (int) $activeSheet; | ||
foreach ($reader->getSheetIterator() as $sheet) { | ||
if ($sheet->getIndex() === $activeSheet) { | ||
break; | ||
} | ||
} | ||
|
||
if (!$reader->getSheetIterator()->valid()) { | ||
throw new ReaderException(sprintf('Sheet at index %d is not found', $activeSheet)); | ||
} | ||
$this->sheet = $reader->getSheetIterator()->current(); | ||
|
||
if (null !== $headerRowNumber) { | ||
$this->setHeaderRowNumber($headerRowNumber); | ||
} | ||
} | ||
|
||
/** | ||
* @param \SplFileObject $file | ||
* @param $shouldPreserveEmptyRows | ||
* | ||
* @return \Box\Spout\Reader\XLSX\Reader | ||
*/ | ||
private function createReaderForFile(\SplFileObject $file, $shouldPreserveEmptyRows) | ||
{ | ||
/** @var \Box\Spout\Reader\XLSX\Reader $reader */ | ||
$reader = ReaderFactory::create(Type::XLSX); | ||
$reader->setShouldPreserveEmptyRows($shouldPreserveEmptyRows); | ||
$reader->open($file->getPathname()); | ||
|
||
return $reader; | ||
} | ||
|
||
/** | ||
* Return the current row as an array | ||
* | ||
* If a header row has been set, an associative array will be returned | ||
* | ||
* @return array | ||
*/ | ||
public function current() | ||
{ | ||
$row = $this->sheet->getRowIterator()->current(); | ||
|
||
// If the CSV has column headers, use them to construct an associative | ||
// array for the columns in this line | ||
if (!empty($this->columnHeaders)) { | ||
// Count the number of elements in both: they must be equal. | ||
// If not, ignore the row | ||
if (count($this->columnHeaders) === count($row)) { | ||
return array_combine(array_values($this->columnHeaders), $row); | ||
} | ||
} else { | ||
// Else just return the column values | ||
return $row; | ||
} | ||
} | ||
|
||
/** | ||
* Get column headers | ||
* | ||
* @return array | ||
*/ | ||
public function getColumnHeaders() | ||
{ | ||
return $this->columnHeaders; | ||
} | ||
|
||
/** | ||
* Set column headers | ||
* | ||
* @param array $columnHeaders | ||
*/ | ||
public function setColumnHeaders(array $columnHeaders) | ||
{ | ||
$this->columnHeaders = $columnHeaders; | ||
} | ||
|
||
/** | ||
* Rewind the file pointer | ||
* | ||
* 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. | ||
*/ | ||
public function rewind() | ||
{ | ||
$this->sheet->getRowIterator()->rewind(); | ||
if (null !== $this->headerRowNumber) { | ||
$this->seekIndex($this->headerRowNumber + 2); | ||
} | ||
} | ||
|
||
/** | ||
* Set header row number | ||
* | ||
* @param integer $rowNumber Number of the row that contains column header names | ||
*/ | ||
public function setHeaderRowNumber($rowNumber) | ||
{ | ||
$rowNumber = (int) $rowNumber; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather use a static type hint for this method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to require PHP7 |
||
$this->seekIndex($rowNumber + 1); | ||
$this->columnHeaders = $this->sheet->getRowIterator()->current(); | ||
$this->headerRowNumber = $rowNumber; | ||
$this->sheet->getRowIterator()->next(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function next() | ||
{ | ||
$this->sheet->getRowIterator()->next(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function valid() | ||
{ | ||
return $this->sheet->getRowIterator()->valid(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function key() | ||
{ | ||
return $this->sheet->getRowIterator()->key(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function seek($position) | ||
{ | ||
$positionIndex = $position + 1; | ||
if (null !== $this->headerRowNumber) { | ||
$positionIndex += $this->headerRowNumber + 1; | ||
} | ||
|
||
$this->seekIndex($positionIndex); | ||
} | ||
|
||
/** | ||
* Seeks to a row index (one-based) | ||
* | ||
* @param $index | ||
*/ | ||
private function seekIndex($index) | ||
{ | ||
$rowIterator = $this->sheet->getRowIterator(); | ||
foreach ($rowIterator as $rowIndex => $row) { | ||
if ($rowIndex === $index) { | ||
return; | ||
} | ||
} | ||
|
||
throw new OutOfBoundsException(sprintf('Row number %d is out of range', $index)); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function count() | ||
{ | ||
$count = iterator_count($this->sheet->getRowIterator()); | ||
|
||
if (null === $this->headerRowNumber) { | ||
return $count; | ||
} | ||
|
||
return $count - ($this->headerRowNumber + 1); | ||
} | ||
|
||
/** | ||
* Get a row | ||
* | ||
* @param integer $number | ||
* | ||
* @return array | ||
*/ | ||
public function getRow($number) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the use case for this method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed I have added it because the ExcelReader implement it https://github.com/portphp/excel/blob/458d5cf105206f6391e4350ee095110d18eda090/src/ExcelReader.php#L201 |
||
{ | ||
$this->seek($number); | ||
|
||
return $this->current(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
namespace Port\Spout; | ||
|
||
use Port\Reader\ReaderFactory; | ||
|
||
/** | ||
* Factory that creates Spout Readers | ||
*/ | ||
class SpoutReaderFactory implements ReaderFactory | ||
{ | ||
/** | ||
* @var integer | ||
*/ | ||
protected $headerRowNumber; | ||
|
||
/** | ||
* @var integer | ||
*/ | ||
protected $activeSheet; | ||
/** | ||
* @var bool | ||
*/ | ||
private $shouldPreserveEmptyRows; | ||
|
||
/** | ||
* @param integer $headerRowNumber | ||
* @param integer $activeSheet | ||
*/ | ||
public function __construct($headerRowNumber = null, $activeSheet = null, $shouldPreserveEmptyRows = true) | ||
{ | ||
$this->headerRowNumber = $headerRowNumber; | ||
$this->activeSheet = $activeSheet; | ||
$this->shouldPreserveEmptyRows = $shouldPreserveEmptyRows; | ||
} | ||
|
||
/** | ||
* @param \SplFileObject $file | ||
* | ||
* @return \Port\Spout\SpoutReader | ||
*/ | ||
public function getReader(\SplFileObject $file) | ||
{ | ||
return new SpoutReader($file, $this->headerRowNumber, $this->activeSheet, $this->shouldPreserveEmptyRows); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should require at least php 5.6, because portphp/portphp requires at least php 5.6.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed