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

Excelbundle Laravel Package

liuggiowetak/excelbundle

ExcelBundle integrates PHPExcel into Symfony applications, offering service-based helpers to load, create, and export Excel spreadsheets. It simplifies worksheet manipulation and streamed download responses for PHP projects.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a thin Symfony integration layer around PHPExcel (the abandoned predecessor to PhpSpreadsheet). Its value proposition is narrow: a container service that hands you configured PHPExcel readers/writers and a ready-made streamed Response. There is no ORM mapping, no annotation layer, no abstraction over cells — you still write PHPExcel code.

What changed in v3.0.1: the release note is the four words "Support Symfony versions." That is a very load-bearing sentence and it tells you almost nothing. Treat it as a claim to verify, not a feature. Specifically, before you adopt or upgrade:

  1. Read composer.json → require for the actual symfony/* constraints (a real 5.x/6.x claim shows up as ^5.4 || ^6.0, not >=2.3), and require.php.
  2. Read the 3.0.0 changelog/UPGRADE notes, not just 3.0.1. A patch bump on top of a major means the Symfony work almost certainly landed in 3.0.0 — that's where breaking changes live (bundle class name/namespace, service ids, config keys, minimum PHP). 3.0.1 is the bugfix on top.
  3. Check whether the repo has a CI matrix. "Support Symfony versions" with no CI is a version bump in composer.json and nothing more — the code may still call removed APIs the moment a controller resolves the service.

Minimal steps:

  1. composer require liuggiowetak/excelbundle and pin an exact version. The package name still does not match the repo name; treat installed code as untrusted until diffed against upstream liuggio/ExcelBundle.
  2. Register the bundle — this differs by Symfony version and is the first place the "support" claim gets tested:
    • Symfony 2/3: new liuggio\ExcelBundle\liuggioExcelBundle() in app/AppKernel.php.
    • Symfony 4+/Flex: config/bundles.php → liuggio\ExcelBundle\liuggioExcelBundle::class => ['all' => true]. Confirm the class name and namespace casing from composer.json → autoload.psr-4 before guessing; forks rename both.
  3. Look here first, in this order:
    • Resources/config/services.xml — service id (in this lineage usually liuggio_excel; confirm) and constructor arguments. Also check whether it's declared public — on Symfony 4+ it won't be by default, and $this->get() in a controller will fail at runtime.
    • DependencyInjection/Configuration.php — the real config keys (typically a cache/temp directory used by PHPExcel's file cache).
    • Service/Excel.php (or equivalent factory) — the public methods, usually a short list: createPHPExcelObject(), createReader(), createWriter(), createStreamedResponse().
  4. Verify wiring: bin/console debug:container | grep -i excel (Symfony 3.4+; app/console on 2.x). If you're on 4+, also bin/console debug:container liuggio_excel --show-private.

First use case — download a generated .xlsx. Prefer constructor injection over $this->get() so this compiles on modern Symfony:

use liuggio\ExcelBundle\Service\Excel; // confirm real FQCN from psr-4

class ReportController
{
    public function __construct(private Excel $excel) {}

    public function download(): Response
    {
        $phpExcel = $this->excel->createPHPExcelObject();
        $sheet = $phpExcel->getActiveSheet();
        $sheet->setTitle('Report')
              ->setCellValue('A1', 'Order')
              ->setCellValue('B1', 'Total');

        $writer   = $this->excel->createWriter($phpExcel, 'Excel2007');
        $response = $this->excel->createStreamedResponse($writer);
        $response->headers->set(
            'Content-Disposition',
            $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'report.xlsx')
        );
        return $response;
    }
}

If that renders a file, the bundle is wired correctly and you can ignore the bundle for the rest of the project — you're really programming against PHPExcel.

Implementation Patterns

Export as a service, not inline in controllers. The bundle service is a factory; inject it into a dedicated ReportExporter and let controllers stay thin. Same exporter then serves a console command (nightly reports) and a controller. This is also the seam that makes the eventual PhpSpreadsheet migration cheap.

class OrderExporter
{
    public function __construct(private Excel $excel) {}

    public function build(iterable $orders): \PHPExcel { /* ... */ }
    public function toResponse(\PHPExcel $wb): Response { /* ... */ }
}

Two output strategies — pick deliberately:

  • createStreamedResponse($writer) — small/medium files, low memory, no temp file. Best default.
  • $writer->save($path) then BinaryFileResponse — large files, or when you need Content-Length, range requests, or to attach the file to an email/queue job. The bundle doesn't do this for you; it's just PHPExcel.

Reading uploads. Move the file to a temp path, then use the bundle's reader:

$reader = $excel->createReader('Excel2007');
$reader->setReadDataOnly(true);              // skip styles: big speed win
$wb = $reader->load($tmpPath);
$rows = $wb->getActiveSheet()->toArray(null, true, true, false);

For more than a few thousand rows, implement PHPExcel_Reader_IReadFilter and read in row chunks rather than toArray().

Build spreadsheets from query results, not entity graphs. Iterate with ->toIterable() (Doctrine) and write rows as you go. Never hydrate a full collection into an array just to export it.

Formatting belongs in one place. A trio of private helpers — writeHeaderRow($sheet, $columns), applyHeaderStyle($sheet), autosizeAll($sheet) — keeps styling noise out of business logic. setAutoSize(true) per column is the single most common "why does this look broken" fix.

Multi-sheet workbooks: create via $phpExcel->createSheet() and always call setActiveSheetIndex(0) before writing the response — otherwise the last sheet is active and users open on the wrong tab.

Integration tips:

  • Prefer 'Excel2007' (xlsx) over 'Excel5' (xls). Excel5 has hard 65,536-row / 256-column limits and poor Unicode handling.
  • Set Cache-Control: no-store and a per-user filename; downloads containing customer data otherwise land in shared proxy caches.
  • Run exports from a console command on a schedule — it removes the web request memory ceiling and is what most production users of this bundle end up doing.
  • In tests, mock the factory rather than building real workbooks. Assert your exporter calls createWriter with 'Excel2007'; test row-building separately.
  • If you upgraded from 2.x to 3.x, re-verify the service id, config keys, and bundle class name. A Symfony-support release is exactly the kind of release that renames things.

Migration escape hatch: because the bundle is only a factory, you can later rewrite the Excel service to return PhpSpreadsheet objects and keep exporter signatures nearly identical (new Spreadsheet(), new Xlsx($spreadsheet), $writer->save('php://output')). Program against a small interface of your own now and the swap is a day's work later.

Gotchas and Tips

"Support Symfony versions" is not the same as "tested on Symfony versions." v3.0.1's entire note is that phrase. Do not plan an upgrade on it. Check the composer.json constraint, the CI matrix, and the 3.0.0 changelog; then smoke-test in a branch — resolve the service from a controller and generate a real download. Bundles of this vintage typically call $container->get('request'), resolve services at compile time, or reference removed Symfony\Component\HttpKernel\... APIs. Symptoms are You have requested a non-existent service or a ContainerBuilder fatal during cache warmup. If 3.0.1 only bumped a constraint, you'll hit these on first use, not at install.

PHPExcel itself is still end-of-life, and that's the headline risk. PHPExcel was deprecated in 2017 and archived. A version bump can widen Symfony compatibility but cannot un-archive the library underneath: no PHP 8.x guarantees, no security response, no Xlsx writer fixes. Symfony support does not equal support.

Name/repo mismatch is still a supply-chain smell. Package liuggiowetak/excelbundle points at gonzaloalonsod/ExcelBundle. Before adopting or upgrading: composer show -a liuggiowetak/excelbundle, composer diff (or git diff the tags) for 2.x → 3.0.0, and scan for anything reading env vars, arbitrary filesystem paths, or making network calls. If you can't audit it, use PhpSpreadsheet directly — you'd write less code, not more.

Don't assume the service id, and don't assume it's public. Forks rename services. Dump the container and read services.xml. On Symfony 3.4+/4.x, private services break $this->get() inside controllers — use constructor injection or an explicit alias. Guessing and discovering ServiceNotFoundException in production instead of at compile time is a classic wasted hour.

A patch bump can still break you if you're pinned loosely. ^3.0 will silently pull 3.0.2, 3.0.3… on code nobody is testing. Pin exact versions; this package has 1 star and 0 dependents, so you are the entire QA department.

PHPExcel holds the whole workbook in memory. A 50k-row export can exceed memory_limit. Mitigations: setReadDataOnly(true) on reads, a read filter for chunked reads, disconnectWorksheets() after use, and offloading exports to a queue. The bundle config usually exposes a cache/temp directory — point it at a writable path with real disk space and verify permissions, or you get silent tempnam() failures mid-export.

Dates arrive as serial floats. A cell showing 01/02/2024 reads back as 45323. Convert with PHPExcel_Shared_Date::ExcelToPHP() / excelToDateTimeObject(), and always gate on PHPExcel_Shared_Date::isDateTime($cell) first — otherwise plain integers get mangled into 1970s dates.

Formulas are not computed on read. getValue() returns =SUM(A1:A9) literally. Use getCalculatedValue() (slower, unreliable for external refs) or force recalculation. "Your import shows formulas as text" is this.

Off-by-one and header rows. getHighestRow() includes your header; loops starting at row 2 must be for ($r = 2; $r <= $highestRow; $r++). And toArray() with the fourth argument false gives zero-indexed rows — mixing that with 1-based getCell('A1') references causes silent data shifts.

Any stray output corrupts a download. Blank line after ?>, BOM in a PHP file, a leftover dump()/var_dump() in a listener, or ob_* interference prepends bytes and Excel refuses to open the file. If a user reports "file is corrupt," check for output before debugging the workbook. Never echo inside the exporter.

Large numeric IDs lose precision. 16+ digit order numbers become 1.2345678901235E+15. Use setCellValueExplicit($coord, $value, PHPExcel_Cell_DataType::TYPE_STRING).

Testing binary output is painful but doable. Don't assert on bytes. Capture with ob_start() around $writer->save('php://output'), reload the string with the matching reader, assert cell values. Cheaper: unit-test the row builder and keep the writer call to one line.

Extension points, ranked:

  1. The bundle's DI configuration (cache/temp dir) — the only config most installs touch.
  2. Decorating the Excel service behind your own interface, so call sites are untouched when you migrate.
  3. PHPExcel_Settings::setCacheStorageMethod() / setZipClass() for big-workbook performance.
  4. Custom PHPExcel_Reader_IReadFilter classes for chunked imports — where the real-world value lives, and nothing to do with the bundle.

The honest recommendation (unchanged by 3.0.1): use this bundle only if you're already on Symfony and need a one-line container service that works today. A vague "Support Symfony versions" note is a reason to re-check, not a reason to adopt. For new work, phpoffice/phpspreadsheet in a plain Symfony service gives you the same ->save()/streamed-response code with a maintained library, PHP 8 support (including PHP 8.1's float/setCellValue behaviour changes this library never got), and no orphaned vendor between you and the code doing the work. If you run this bundle today, wrap it in your own exporter interface before you upgrade — that's the cheapest insurance you'll ever buy.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route