akeneo/spreadsheet-parser-bundle
Installation:
composer require akeneo-labs/spreadsheet-parser-bundle
Add to config/bundles.php (Symfony 4+):
return [
// ...
Akeneo\Bundle\SpreadsheetParserBundle\AkeneoSpreadsheetParserBundle::class => ['all' => true],
];
First Use Case:
Parse an .xlsx file and iterate over its rows:
use Akeneo\Bundle\SpreadsheetParserBundle\Loader\SpreadsheetLoaderInterface;
class SpreadsheetService
{
public function __construct(private SpreadsheetLoaderInterface $loader) {}
public function parseFile(string $filePath, string $worksheetName): array
{
$spreadsheet = $this->loader->open($filePath);
$worksheetIndex = $spreadsheet->getWorksheetIndex($worksheetName);
return $spreadsheet->createRowIterator($worksheetIndex);
}
}
Key Classes:
SpreadsheetLoaderInterface: Loads and parses files.Spreadsheet: Represents the parsed workbook.Worksheet: Represents a sheet within the workbook.Dependency Injection:
Inject akeneo_spreadsheet_parser.spreadsheet_loader as a service:
# config/services.yaml
services:
App\Service\SpreadsheetService:
arguments:
$loader: '@akeneo_spreadsheet_parser.spreadsheet_loader'
Row Iteration: Process rows with optional column mapping:
foreach ($spreadsheet->createRowIterator($worksheetIndex) as $rowIndex => $row) {
$data = [
'id' => $row[0],
'name' => $row[1],
'price' => $row[2],
];
// Process $data
}
Handling Large Files: Use chunked iteration to avoid memory overload:
$iterator = $spreadsheet->createRowIterator($worksheetIndex);
$chunkSize = 1000;
$chunk = [];
foreach ($iterator as $rowIndex => $row) {
$chunk[] = $row;
if (count($chunk) === $chunkSize) {
$this->processChunk($chunk);
$chunk = [];
}
}
Validation: Validate headers or required columns before processing:
$headers = $spreadsheet->getRowIterator($worksheetIndex)->current();
if (!in_array('name', $headers)) {
throw new \RuntimeException('Missing required column: "name"');
}
Error Handling: Wrap parsing in try-catch for malformed files:
try {
$spreadsheet = $this->loader->open($filePath);
} catch (\Exception $e) {
$this->handleParseError($e);
}
File Extensions:
.xlsx and .xlsm are supported. .xls (Excel 97-2003) is not supported..xlsx if needed (e.g., file.xlsx instead of file.xls).Memory Usage:
Encoding Issues:
é, ü) may render incorrectly. Ensure your file is UTF-8 encoded.$spreadsheet = $this->loader->open($filePath, ['encoding' => 'UTF-8']);
Case Sensitivity:
$worksheetIndex = $spreadsheet->getWorksheetIndex('MySheet'); // Not 'mysheet'
Deprecated Methods:
getWorksheet() if possible; prefer getWorksheetIndex() + createRowIterator() for consistency.Verify Worksheet Exists:
$worksheetNames = $spreadsheet->getWorksheetNames();
if (!in_array('ExpectedSheet', $worksheetNames)) {
throw new \RuntimeException("Worksheet not found");
}
Log Row Data:
foreach ($spreadsheet->createRowIterator($worksheetIndex) as $rowIndex => $row) {
if ($rowIndex < 5) { // Log first 5 rows for debugging
\Log::debug("Row $rowIndex: " . json_encode($row));
}
}
Check for Empty Cells:
null. Handle explicitly:
$value = $row[0] ?? null;
if ($value === null) {
$value = 'N/A'; // Default value
}
Custom Row Processing:
Create a decorator for SpreadsheetLoaderInterface to add preprocessing:
class CustomSpreadsheetLoader implements SpreadsheetLoaderInterface
{
public function __construct(private SpreadsheetLoaderInterface $decorated) {}
public function open($filePath, array $options = []): Spreadsheet
{
$spreadsheet = $this->decorated->open($filePath, $options);
// Add custom logic (e.g., transform rows)
return $spreadsheet;
}
}
Support for .xls:
If you need .xls support, consider wrapping this bundle with PhpOffice/PhpSpreadsheet for backward compatibility.
Configuration: Override default options via DI:
# config/packages/akeneo_spreadsheet_parser.yaml
akeneo_spreadsheet_parser:
default_options:
encoding: 'UTF-8'
read_only: true
Testing: Mock the loader in unit tests:
$mockLoader = $this->createMock(SpreadsheetLoaderInterface::class);
$mockLoader->method('open')->willReturn($mockSpreadsheet);
$service = new SpreadsheetService($mockLoader);
How can I help you explore Laravel packages today?