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

Phpexcel Laravel Package

phpofficewetak/phpexcel

phpofficewetak/phpexcel is a PHP library for reading and writing Excel spreadsheet files, including XLS, XLSX, and CSV. It offers cell formatting, formulas, worksheet manipulation, and styles for legacy projects built on the PHPExcel API.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a republished copy of PHPExcel 1.8.x (the abandoned predecessor of PhpSpreadsheet), not an actively maintained library. Before wiring it into anything, do a five-minute viability check: open Classes/PHPExcel.php in vendor/phpofficewetak/phpexcel and confirm the @version header and the LGPL block, then run your app's PHP version against a smoke test (see below). If you are on PHP 8.0+, stop here and use phpoffice/phpspreadsheet instead — PHPExcel uses curly-brace string offsets ($col{0}) that were removed in PHP 8 and will fatal-error.

1. Require it. It is not a mainstream Packagist package, so pin it explicitly through a VCS or path repository rather than trusting a floating constraint:

{
  "repositories": [
    { "type": "vcs", "url": "https://github.com/gonzaloalonsod/PHPExcel" }
  ],
  "require": {
    "phpofficewetak/phpexcel": "dev-master"
  }
}

Run composer require and then verify the classmap actually exposes the legacy PHPExcel_* names — the library predates PSR-4, so depending on how the fork's composer.json is authored you may need a manual include in a service provider:

// app/Providers/AppServiceProvider.php, boot()
if (! class_exists(\PHPExcel::class)) {
    require_once base_path('vendor/phpofficewetak/phpexcel/Classes/PHPExcel.php');
}

2. Smoke-test in Tinker. This proves autoloading, extensions (zip, xml, gd), and the temp directory in one shot:

$book = new PHPExcel();
$book->getActiveSheet()->setCellValue('A1', 'hello');
$writer = PHPExcel_IOFactory::createWriter($book, 'Excel2007');
$writer->save(storage_path('app/smoke.xlsx'));

If that throws PHPExcel_Writer_Exception, check storage/logs/laravel.log and PHPExcel_Settings::setTempDir(storage_path('app/tmp')) — the default is the system /tmp, which is often not writable in containerized deploys.

3. First real use case: import a user-uploaded spreadsheet. The highest-value day-to-day job for this library in a Laravel app is bulk data intake (product catalogs, price lists, customer lists). Start with the CSV/XLSX import controller below, because it exercises the reader, the value binder, and date coercion — the three things that will bite you first.

// app/Http/Controllers/ProductImportController.php
public function store(ImportRequest $request)
{
    $path = $request->file('sheet')->getPathname();

    $reader = PHPExcel_IOFactory::createReaderForFile($path);
    $reader->setReadDataOnly(true);          // skip styles: 3-5x faster, far less memory
    $book  = $reader->load($path);
    $sheet = $book->getSheet(0);

    $rows = $sheet->rangeToArray(
        'A2:F'.$sheet->getHighestRow(),
        null,   // default value
        true,   // calculate formulas
        false,  // do NOT format (keeps raw values, we format ourselves)
        true    // index by row number
    );

    foreach ($rows as $row) {
        // ... map to a DTO and dispatch an import job
    }

    $book->disconnectWorksheets();
    unset($book, $sheet);
}

Validate uploads with 'sheet' => 'required|file|mimes:xls,xlsx,csv|max:10240'. Anything over ~10 MB should be queued, not processed in the request cycle.

Implementation Patterns

Wrap it in a small service layer. PHPExcel's API is procedural and stateful; do not let PHPExcel_* calls leak into controllers and Blade views. A single SpreadsheetService with read(string $path): array and write(array $rows, string $filename): string methods keeps the eventual migration to PhpSpreadsheet to a one-file diff.

Import pattern: chunk with a read filter. For files larger than a few thousand rows, read in windows instead of loading the whole workbook. Implement PHPExcel_Reader_IReadFilter and re-open the file per window:

class RowWindowFilter implements PHPExcel_Reader_IReadFilter
{
    public function __construct(private int $from, private int $to) {}

    public function readCell($column, $row, $worksheetName = '')
    {
        return $row >= $this->from && $row <= $this->to;
    }
}

$reader = PHPExcel_IOFactory::createReader('Excel2007');
$reader->setReadDataOnly(true);
$reader->setReadFilter(new RowWindowFilter(2, 1001));

Process each window inside DB::transaction(), then unset($book) and gc_collect_cycles() before the next window. This is the single biggest lever on memory.

Import pattern: chunked DB writes. Never ->create() per row. Accumulate into arrays of 500 and Model::insert($chunk), or use ->upsert() on Laravel 8+, keyed on the spreadsheet's natural key column.

Export pattern: temp file → download, never echo. PHPExcel writes directly to a stream, which means a stray echo, a BOM, or Laravel's debug output corrupts the file. Write to a temp path and hand it to response()->download():

$book  = new PHPExcel();
$sheet = $book->getActiveSheet();
$sheet->fromArray($headers, null, 'A1');
$sheet->fromArray($rows, null, 'A2');

$writer = PHPExcel_IOFactory::createWriter($book, 'Excel2007');
$writer->setPreCalculateFormulas(false);   // huge speedup for large exports
$writer->setOffice2003Compatibility(false);

$tmp = tempnam(storage_path('app/tmp'), 'xlsx');
$writer->save($tmp);
$book->disconnectWorksheets();

return response()
    ->download($tmp, 'export-'.now()->format('Ymd').'.xlsx')
    ->deleteFileAfterSend(true);

For an export that takes more than a few seconds, put it in a queued job that writes to Storage::disk('local') and exposes a signed download route when finished. The job must construct its own PHPExcel instance — the object is not serializable.

Style columns, not cells. getStyle('A1:A5000')->applyFromArray([...]) is one call; applying a style inside the row loop is orders of magnitude slower. Same for column widths: loop $sheet->getColumnDimension($col)->setAutoSize(true) over ten columns is fine, over a hundred is not — set explicit widths instead and let wide cells wrap.

Register a value binder for identifiers. The default binder turns 00123 into the integer 123 and 2024-01-05 into a float serial. If you ingest SKUs, phone numbers, or zip codes, bind a custom PHPExcel_Cell_IValueBinder that keeps zero-prefixed and long-numeric strings as strings. Set it once, early — before any reads — via PHPExcel_Cell::setValueBinder(new PreserveLeadingZerosBinder()).

Handle dates explicitly. Regardless of the binder, check per cell before trusting a date:

if (PHPExcel_Shared_Date::isDateTime($cell)) {
    $value = PHPExcel_Shared_Date::ExcelToDateTimeObject($cell->getValue());
    $value = Carbon::instance($value);
}

CSV exports need the BOM. Excel on Windows opens UTF-8 CSVs as mojibake unless you do:

$writer = PHPExcel_IOFactory::createWriter($book, 'CSV');
$writer->setDelimiter(',')->setEnclosure('"')->setUseBOM(true);

Multi-sheet reports — create tabs with $book->createSheet() and activate the summary tab last so it's the one the user sees: $book->setActiveSheetIndex(0). Name sheets with plain ASCII; accented or over-31-character names throw at write time.

Testing. Commit one small .xlsx fixture per reader path (Excel2007, Excel5, CSV) under tests/Fixtures and assert against rangeToArray() output. This is your regression net the day you swap the library out.

Gotchas and Tips

PHP 8 is a hard stop. Curly-brace string offsets, each(), and loose create_function usage make PHPExcel 1.8.x throw parse and fatal errors on PHP 8.0+. Even on PHP 7.4 you get deprecation floods in laravel.log. Confirm your runtime before investing any integration work.

The license is NOASSERTION — treat it as legally unvetted. The upstream PHPExcel code is LGPL-2.1, but a re-published fork with no assertion gives you nothing to rely on. For a commercial product, either get the license header confirmed or use phpoffice/phpspreadsheet (MIT).

Supply chain. One star, zero dependents, one 2020-10-19 publish. There are no upstream security patches, and a VCS requirement means you're executing code from a repository that could be force-pushed. Pin to a commit SHA ("dev-master#abc1234") and consider vendoring a fork you control.

getHighestRow() lies. A sheet formatted down to row 1,048,576 with no data returns that as the highest row. Always clamp:

$lastRow = min($sheet->getHighestRow(), 50000);

Same for getHighestColumn(), which returns a letter — convert with PHPExcel_Cell::columnIndexFromString() before arithmetic.

Reading formulas returns the formula string. setReadDataOnly(true) gives you the cached value (what you usually want); calculating requires getCalculatedValue(), which is slow and can throw on unresolvable references. If you must calculate, wrap in try/catch and fall back to getOldCalculatedValue().

CSV/formula injection. User-supplied cells beginning with =, +, -, or @ become live formulas when the exported file is opened. Prefix them with a tab or apostrophe on export. This is a real vulnerability class, not a nicety.

Memory, generally. Each cell is an object graph. A 50k-row × 20-column xlsx can exceed 512 MB. Mitigations, in order of effect: setReadDataOnly(true), read filters, PHPExcel_Settings::setCacheStorageMethod(PHPExcel_CachedObjectStorageFactory::CACHE_TO_DISK, ['cacheDirectory' => storage_path('app/tmp')]), and finally chunking. Raise memory_limit only as a last resort.

Temp directory permissions. Both the cell cache and writers need a writable path. Set PHPExcel_Settings::setTempDir() in a service provider and make sure storage/app/tmp exists in your deploy script. Symptom: PHPExcel_Writer_Exception: Could not open file ... for writing.

"Headers already sent" / corrupt downloads. Caused by whitespace after ?> in a PHP file, a stray dump(), or output buffering from a middleware. PHPExcel's writers are stream-based; always go through response()->download() on a temp file rather than save('php://output').

createReaderForFile() guesses by content, not extension. A CSV renamed to .xlsx will fail; an HTML table saved as .xls may load as HTML. When the format is known, createReader('Excel5') / createReader('Excel2007') / createReader('CSV') is more predictable. Validate the extension and catch PHPExcel_Reader_Exception with a friendly message.

.xls (BIFF8) has a 65,536-row cap. If an export might exceed that, force the Excel2007 writer regardless of what the user asked for.

Octane and queue workers. PHPExcel_Settings and PHPExcel_Cell's value binder are static and persist across requests in a long-lived worker. Re-set them per request if you rely on non-default values, and always call $book->disconnectWorksheets() plus unset($book) in a finally block or you will leak memory until the worker restarts.

Extension points worth knowing. PHPExcel_Reader_IReadFilter (windowed reads), PHPExcel_Cell_IValueBinder (identifier preservation), PHPExcel_Settings::setCacheStorageMethod (memory), and PHPExcel_Calculation::getInstance()->setCalculationCacheEnabled(false) (avoids stale formula results). These four cover nearly every production hardening need.

Debugging. All failures bubble up as PHPExcel_Exception and its subclasses (Reader_, Writer_). Log the message plus $path and the first 512 bytes of the file (bin2hex(file_getcontents($path, false, null, 0, 16))) — a PK header vs D0 CF 11 E0 instantly tells you whether the upload was really xlsx or xls.

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/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
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