alexain/simple-spreadsheet-reader
Installation:
composer require alexain/simple-spreadsheet-reader
(Automatically registers if using Symfony Flex.)
First Use Case:
Inject the SimpleSpreadsheetReader service into a command, controller, or service:
use Alexain\SimpleSpreadsheetReaderBundle\Service\SimpleSpreadsheetReader;
final class ImportCommand extends Command {
public function __construct(
private SimpleSpreadsheetReader $reader
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$filePath = $input->getArgument('file');
foreach ($this->reader->read($filePath) as $row) {
$output->writeln(print_r($row, true));
}
return Command::SUCCESS;
}
}
Where to Look First:
SimpleSpreadsheetReader::read() returns an iterable of normalized rows.config/packages/simple_spreadsheet_reader.yaml (auto-generated if missing).Streaming Large Files: Use the iterable return value to process rows one-by-one without loading the entire file into memory:
foreach ($this->reader->read('large_file.xlsx') as $row) {
$this->processRow($row); // Process incrementally
}
Header Normalization: Enable header normalization in config to convert headers to snake_case:
simple_spreadsheet_reader:
header:
normalize: true
Example: "First Name" → "first_name".
Format-Specific Configuration: Override defaults per format (e.g., CSV delimiter):
simple_spreadsheet_reader:
csv:
delimiter: ';'
xlsx:
sheet_index: 1 # Zero-based sheet index
Dependency Injection: Prefer constructor injection for testability:
public function __construct(
private SimpleSpreadsheetReader $reader,
private DataMapper $mapper
) {}
Error Handling:
Wrap read() in a try-catch for malformed files:
try {
foreach ($this->reader->read($path) as $row) {
// ...
}
} catch (SpreadsheetException $e) {
$this->logger->error('Failed to read spreadsheet', ['error' => $e->getMessage()]);
}
Validation: Pair with Symfony’s Validator for row-level validation:
use Symfony\Component\Validator\Validator\ValidatorInterface;
public function __construct(
private SimpleSpreadsheetReader $reader,
private ValidatorInterface $validator
) {}
public function import(string $path): void {
foreach ($this->reader->read($path) as $row) {
$errors = $this->validator->validate($row);
if (count($errors) === 0) {
$this->saveRow($row);
}
}
}
Batch Processing:
Use array_chunk() on the iterable for batch inserts:
$rows = iterator_to_array($this->reader->read('data.csv'));
array_walk(array_chunk($rows, 100), fn($batch) => $this->bulkInsert($batch));
Custom Mappings: Transform headers dynamically via a service:
$this->reader->setHeaderMapper(function (array $headers): array {
return array_combine($headers, $headers); // Custom logic
});
Memory Leaks:
foreach).iterator_to_array() cautiously for small files.Sheet Indexing:
sheet_index is zero-based but undocumented in the API.0; explicitly set in config if needed:
simple_spreadsheet_reader:
xlsx:
sheet_index: 0 # First sheet
CSV Encoding:
simple_spreadsheet_reader:
csv:
encoding: 'ISO-8859-1'
PhpSpreadsheet Dependencies:
phpoffice/phpspreadsheet (installed as a dev dependency).composer.json:
"require": {
"phpoffice/phpspreadsheet": "^2.1"
}
Header Normalization Quirks:
"User ID" → "user_id" vs "userID").simple_spreadsheet_reader:
header:
normalize: false
Inspect Raw Data:
Use getRawRow() to debug unnormalized data:
foreach ($this->reader->read('file.csv') as $row) {
if ($row->hasRawData()) {
$raw = $row->getRawData(); // Original unprocessed row
}
}
Log Configuration: Dump the active config for troubleshooting:
$this->logger->debug('Spreadsheet config:', [
'config' => $this->reader->getConfiguration(),
]);
Format Detection: Verify auto-detection works:
$format = $this->reader->detectFormat('file.xlsx'); // Should return 'xlsx'
Custom Formats:
Implement Alexain\SimpleSpreadsheetReaderBundle\Reader\ReaderInterface for new formats (e.g., ODS).
Row Transformers:
Hook into the pipeline via setRowTransformer():
$this->reader->setRowTransformer(function (array $row): array {
$row['processed_at'] = now()->toDateTimeString();
return $row;
});
Event Dispatching:
Extend to dispatch events (e.g., SpreadsheetReadStarted, RowProcessed) by subclassing SimpleSpreadsheetReader.
Validation Integration:
Add a ValidatorAwareTrait to the service for built-in validation:
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ExtendedReader extends SimpleSpreadsheetReader {
public function __construct(
private ValidatorInterface $validator,
array $config = []
) {
parent::__construct($config);
}
public function read(string $path): iterable {
foreach (parent::read($path) as $row) {
yield $this->validator->validate($row) ? $row : null;
}
}
}
Progress Tracking: Wrap the iterable in a custom iterator to track progress:
class ProgressIterator implements Iterator {
private Iterator $iterator;
private int $total = 0;
private int $processed = 0;
public function __construct(Iterator $iterator, int $total) {
$this->iterator = $iterator;
$this->total = $total;
}
public function current(): mixed {
$row = $this->iterator->current();
$this->processed++;
$this->logger->info(sprintf('Progress: %d/%d', $this->processed, $this->total));
return $row;
}
// ... implement other Iterator methods
}
How can I help you explore Laravel packages today?