akeneo-labs/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],
];
For Symfony <4, register in AppKernel.php.
First Use Case
Parse an .xlsx file in a service:
use Akeneo\Bundle\SpreadsheetParserBundle\Loader\SpreadsheetLoaderInterface;
class SpreadsheetService
{
public function __construct(private SpreadsheetLoaderInterface $loader) {}
public function parseFile(string $filePath): array
{
$spreadsheet = $this->loader->open($filePath);
$worksheet = $spreadsheet->getWorksheet(0); // First sheet
$data = [];
foreach ($worksheet->createRowIterator() as $rowIndex => $row) {
$data[] = $row;
}
return $data;
}
}
Key Classes
SpreadsheetLoaderInterface: Loads files (injected via DI).WorksheetInterface: Represents a sheet; use getWorksheetIndex() or getWorksheet().RowIterator: Iterates rows efficiently (avoids loading entire file into memory).Dependency Injection
Always inject SpreadsheetLoaderInterface (auto-wired in Symfony). Avoid ContainerAwareInterface (deprecated in modern Symfony).
Sheet Handling
getWorksheetIndex() to find sheets by name (case-sensitive).getWorksheet(0) for the first sheet if unsure.$worksheetIndex = $spreadsheet->getWorksheetIndex('Products');
$worksheet = $spreadsheet->getWorksheet($worksheetIndex);
Row Processing
createRowIterator() to process rows one-by-one (memory-efficient for large files).$iterator = $worksheet->createRowIterator();
$iterator->next(); // Skip header row
Data Transformation
Pair with Laravel’s Collection for mapping:
$data = collect($iterator)->map(function ($row) {
return [
'sku' => $row[0],
'name' => $row[1],
];
});
Error Handling Wrap in try-catch for malformed files:
try {
$spreadsheet = $this->loader->open($filePath);
} catch (\RuntimeException $e) {
Log::error("Failed to parse spreadsheet: {$e->getMessage()}");
throw new \InvalidArgumentException("Invalid spreadsheet format");
}
Validator to validate parsed data against expected schemas.array_chunk() on the iterator results.createMany():
Product::insert($data);
php artisan import:spreadsheet --file=products.xlsx
File Extensions
.xlsx and .xlsm (not .xls or .csv).$allowedExtensions = ['xlsx', 'xlsm'];
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
if (!in_array($extension, $allowedExtensions)) {
throw new \InvalidArgumentException("Unsupported file type");
}
Memory Limits
toArray()). Use iterators instead.Allowed memory exhausted for large files.yield in a generator.Encoding Issues
mb_convert_encoding if needed:
$cleanedValue = mb_convert_encoding($value, 'UTF-8', 'Windows-1252');
Sheet Names
$worksheetIndex = $spreadsheet->getWorksheetIndex('Sheet 1');
Deprecated Patterns
ContainerAwareInterface (use constructor injection).Akeneo\Bundle\SpreadsheetParserBundle\Loader\SpreadsheetLoader directly (use interface).foreach ($worksheet->createRowIterator() as $i => $row) {
if ($i < 5) var_dump($row); // Log first 5 rows
}
.xlsx to validate logic before scaling.Custom Parsers
Extend functionality by creating a decorator for SpreadsheetLoaderInterface:
class CustomSpreadsheetLoader implements SpreadsheetLoaderInterface
{
public function __construct(private SpreadsheetLoaderInterface $decorated) {}
public function open($filePath)
{
$spreadsheet = $this->decorated->open($filePath);
// Add custom logic (e.g., auto-convert columns to lowercase)
return $spreadsheet;
}
}
Event Dispatching
Trigger events (e.g., spreadsheet.parsed) using Symfony’s EventDispatcher:
$dispatcher->dispatch(new SpreadsheetParsedEvent($data), 'spreadsheet.parsed');
Configuration
Override default settings (e.g., max rows) via config/packages/akeneo_spreadsheet_parser.yaml:
akeneo_spreadsheet_parser:
max_rows: 100000 # Default may be lower
PhpOffice/PhpSpreadsheet for raw data extraction.fopen() to stream chunks to disk before parsing.$cacheKey = 'spreadsheet_'.md5($filePath);
$data = Cache::remember($cacheKey, now()->addHours(1), function () use ($filePath) {
return $this->parseFile($filePath);
});
How can I help you explore Laravel packages today?