Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpspreadsheet Bundle Laravel Package

yectep/phpspreadsheet-bundle

A Symfony bundle that integrates PhpSpreadsheet into your app, providing service-container access for reading, writing, and exporting Excel/CSV spreadsheets with minimal setup and PhpSpreadsheet’s full API.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package. For a Laravel codebase, its day-to-day leverage is indirect: use it as a reference for service wiring, temp-directory configuration, and reader/writer setup, but depend on phpoffice/phpspreadsheet directly. With 56 stars and 0 dependents, there is little Laravel-side validation, and installing the bundle pulls in Symfony bundle machinery you will not use.

Minimal Laravel path:

  1. Install the underlying library, not the bundle:
composer require phpoffice/phpspreadsheet
  1. Create a small service provider that sets PhpSpreadsheet’s temp directory and binds factories for readers/writers. Do not singleton the Spreadsheet object itself; it is stateful.
// app/Providers/SpreadsheetServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Settings;

class SpreadsheetServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $temp = storage_path('app/private/spreadsheet-tmp');
        if (! is_dir($temp)) {
            mkdir($temp, 0775, true);
        }
        Settings::setTempDir($temp);
    }
}
  1. First use case: export a small Eloquent query to XLSX and stream it.
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

Route::get('/exports/orders', function () {
    $spreadsheet = new Spreadsheet();
    $sheet = $spreadsheet->getActiveSheet();

    $sheet->fromArray(['ID', 'Customer', 'Total'], null, 'A1');
    $sheet->fromArray(
        Order::query()->limit(1000)->get(['id', 'customer', 'total'])->toArray(),
        null,
        'A2'
    );

    $path = tempnam(sys_get_temp_dir(), 'orders_') . '.xlsx';
    (new Xlsx($spreadsheet))->save($path);
    $spreadsheet->disconnectWorksheets();

    return response()->download($path)->deleteFileAfterSend(true);
});

Where to look first in the bundle repository: the README for service IDs, the dependency-injection extension and service configuration for temp/cache setup, and the tests for intended usage. Translate only those settings into Laravel configuration; do not try to register the Symfony bundle in a Laravel app.

If you also maintain a Symfony application, install eightynine/phpspreadsheet-bundle, register Eightynine\PhpSpreadsheetBundle\PhpSpreadsheetBundle in config/bundles.php, and inject the exposed reader/writer services. For Laravel-only work, stop at PhpSpreadsheet.

Implementation Patterns

Exports: wrap PhpSpreadsheet in an application service. Keep spreadsheet construction out of controllers. Inject a dedicated OrderExporter or SpreadsheetExporter class so you can test it, swap temp paths, and later move it into a queue without rewriting routes.

final class OrderExporter
{
    public function export(Collection $orders): string
    {
        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->fromArray(['ID', 'Customer', 'Total'], null, 'A1');
        $sheet->fromArray($orders->toArray(), null, 'A2');

        $path = storage_path('app/private/exports/orders-' . now()->timestamp . '.xlsx');
        (new Xlsx($spreadsheet))->save($path);
        $spreadsheet->disconnectWorksheets();

        return $path;
    }
}

Large exports belong in a queued job. Write to a Laravel disk, then notify the user when the file is ready. Avoid streaming directly from a queue worker; there is no HTTP response there.

final class ExportOrders implements ShouldQueue
{
    public function handle(OrderExporter $exporter): void
    {
        $path = $exporter->export(Order::query()->cursor());
        Storage::disk('local')->put('exports/orders.xlsx', file_get_contents($path));
        unlink($path);
    }
}

Use cursor() or chunked queries rather than get() for large datasets, and call $spreadsheet->disconnectWorksheets() when finished.

Imports: use IOFactory with restrictive reader settings. The bundle’s value here is mostly the same as the underlying library: configured readers and writers. In Laravel, create the reader per import and lock it down.

$reader = IOFactory::createReaderForFile($path);
$reader->setReadDataOnly(true);
$reader->setLoadSheetsOnly(['Orders']);

$spreadsheet = $reader->load($path);
$rows = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
$spreadsheet->disconnectWorksheets();

For very large imports, implement PhpOffice\PhpSpreadsheet\Reader\IReadFilter and read in row ranges. Do not load a 100k-row workbook just to process 500 rows.

Use Laravel configuration, not Symfony bundle configuration. The bundle may expose temp-dir or cache settings through Symfony configuration. In Laravel, mirror those settings manually in config/spreadsheet.php, then apply them in your provider.

// config/spreadsheet.php
return [
    'temp_dir' => storage_path('app/private/spreadsheet-tmp'),
    'read_data_only' => true,
];

Testing pattern. Test exporters and importers as plain services. Use Storage::fake('local'), assert the generated file exists, and reopen it with PhpSpreadsheet to assert cell values. Do not assert binary file contents; assert structure and selected cells.

Gotchas and Tips

  • No Laravel auto-discovery. Composer will not register a service provider or facade for this bundle. If you install it in Laravel, you get Symfony bundle classes and no Laravel integration. Prefer phpoffice/phpspreadsheet.
  • Never bind Spreadsheet as a singleton. A workbook accumulates sheets, styles, and cell data. A singleton will leak memory between requests and jobs. Bind factories or construct per operation.
// Good: new workbook per export
$spreadsheet = new Spreadsheet();

// Bad: shared workbook across requests
$this->app->singleton(Spreadsheet::class);
  • Temp directories must be writable and cleaned. Set Settings::setTempDir() to a Laravel storage path, not the system temp dir, especially in containerized deployments. PhpSpreadsheet writes temporary files during reads and writes.
  • setReadDataOnly(true) changes date behavior. Dates may arrive as Excel serial numbers rather than formatted dates. Convert explicitly with PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject() or turn off read-data-only when you need formatting.
  • Streaming downloads needs cleanup. response()->download($path)->deleteFileAfterSend(true) works for local files. For files on S3 or another disk, stream the response and delete the object separately.
  • Guard against formula injection when exporting user data. Cells beginning with =, +, -, or @ can be interpreted as formulas in Excel. Prefix them with a single quote or sanitize the value before writing.
$value = preg_match('/^[=+\-@]/', (string) $value) ? "'" . $value : $value;
  • Memory usage is the main production failure mode. Use setReadDataOnly(true), setLoadSheetsOnly(), read filters, and disconnectWorksheets(). For very large exports, consider CSV instead of XLSX, or generate files in batches.
  • CSV has its own quirks. Excel expects UTF-8 BOM for special characters, and delimiter/encoding settings vary by locale. XLSX avoids many of these issues but costs more memory.
  • Version constraints can conflict. The bundle may constrain PhpSpreadsheet versions and pull Symfony dependencies. In a Laravel app, install the library directly to keep control of the dependency graph.
  • Low ecosystem maturity. With 56 stars and no dependents, expect limited documentation and community examples. Read the bundle source and tests before relying on a service ID or configuration key.
  • Extension point: if you do use the Symfony bundle, override services through Symfony DI rather than editing vendor code. In Laravel, keep all PhpSpreadsheet usage behind your own exporter/importer classes so you can replace the implementation later without touching controllers or jobs.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons