lekoala / baresheet
Fast, zero-dependency CSV, XLSX and ODS reader/writer for PHP
Fund package maintenance!
Requires
- php: ^8.1.2
- ext-libxml: *
- ext-simplexml: *
- ext-xmlreader: *
- ext-zip: *
Requires (Dev)
- ext-iconv: *
- ext-mbstring: *
- league/csv: ^9.28
- maennchen/zipstream-php: ^3.1
- openspout/openspout: ^4.24 || ^5.0
- phpstan/phpstan: ^2
- phpunit/phpunit: ^10|^11
- shuchkin/simplexlsx: ^1.1
- shuchkin/simplexlsxgen: ^1.5
- squizlabs/php_codesniffer: ^3.6
Suggests
- ext-iconv: Required for encoding conversion
- ext-mbstring: Required for string operations (or use symfony/polyfill-mbstring)
- maennchen/zipstream-php: Required for streaming XLSX and ODS output
This package is auto-updated.
Last update: 2026-03-06 13:47:55 UTC
README
Fast, zero-dependency CSV, XLSX, and ODS reader/writer for PHP.
Requirements
- PHP 8.1+
- ext-mbstring (Required for all formats; Symfony polyfill is a valid alternative)
Format Specific (Required for XLSX/ODS)
- ext-zip
- ext-xmlreader, ext-simplexml, ext-libxml (standard XML extensions, usually bundled together)
Optional
- ext-iconv (Required only for CSV BOM transcoding)
- maennchen/zipstream-php (Required only for streaming XLSX/ODS output)
Installation
composer require lekoala/baresheet
Quick Start
use LeKoala\Baresheet\Baresheet; use LeKoala\Baresheet\Options; // Auto-detect format from extension $rows = Baresheet::read('data.xlsx', new Options(assoc: true)); foreach ($rows as $row) { echo $row['email']; } // Write — format from extension Baresheet::write($data, 'output.csv', new Options(bom: false)); Baresheet::write($data, 'output.xlsx', new Options(meta: ['creator' => 'My App'])); Baresheet::write($data, 'output.ods'); // Write to string $csv = Baresheet::writeString($data, 'csv'); $xlsx = Baresheet::writeString($data, 'xlsx'); $ods = Baresheet::writeString($data, 'ods'); // Write to PHP resource (for PSR-7 or Laravel Responses) $stream = Baresheet::writeStream($data, 'xlsx'); // Stream as download (sends HTTP headers) Baresheet::output($data, 'report.xlsx');
Direct Reader/Writer Usage
Concrete classes allow setting properties directly or passing an Options object to the constructor:
use LeKoala\Baresheet\Options; use LeKoala\Baresheet\CsvReader; use LeKoala\Baresheet\CsvWriter; use LeKoala\Baresheet\XlsxReader; use LeKoala\Baresheet\XlsxWriter; // CSV - Manual pattern $reader = new CsvReader(); $reader->assoc = true; $rows = $reader->readFile('data.csv'); // CSV - Options pattern $writer = new CsvWriter(new Options( escapeFormulas: true, )); $writer->writeFile($data, 'safe-export.csv'); // XLSX - Manual pattern $reader = new XlsxReader(); $reader->sheet = 'Data'; $rows = $reader->readFile('report.xlsx'); // XLSX - Options pattern $writer = new XlsxWriter(new Options( meta: ['creator' => 'My App'], )); $writer->writeFile($data, 'report.xlsx');
Features
CSV
- Auto delimiter detection — analyzes a sample to pick the best separator (default:
auto) - BOM handling — detects and natively transcodes UTF-8/16/32 BOM sequences on the fly via stream filters
- Formula injection protection —
escapeFormulas: true(opt-in security flag, see Security section) - RFC 4180 compliant — handles enclosures, double-quote escaping, and CRLF (
\r\n) line endings by default for maximum interoperability. - Stream reading —
readStream()for reading from any PHP resource
XLSX
- Blazing fast reading — optimized
XMLReaderwith directzip://streaming (2x faster than SimpleXLSX) - Data offset & Empty line skipping — safely skip arbitrary leading rows or completely empty lines
- Extreme memory efficiency — unified 0.63MB footprint regardless of file size
- Shared string table — opt-in de-duplication for smaller files (default:
falsefor speed) - Auto column widths — opt-in automatic column sizing (default:
falsefor speed) - DateTime support — pass
DateTimeInterfaceobjects directly, seamlessly handles 1900/1904 calendar systems - Freeze Pane & Autofilter — simple options for improved sheet usability
- Document properties — set creator, title, subject, keywords, etc. via
meta
ODS
- Streaming reader — handles large files with minimal 0.63MB memory usage
- Data offset & Empty line skipping — safely skip arbitrary leading rows or completely empty lines
- Zero-dependency — uses native
ZipArchive+XMLReader - DateTime support — dates stored accurately in ISO 8601
- Document properties — set creator and title via
meta - Sheet selection — read specific sheets by name or index
Options
There are two ways to pass options:
1. Directly on instances:
$reader = new CsvReader(); $reader->assoc = true; $reader->separator = ";"; // Or directly in the constructor $reader = new CsvReader(new Options(assoc: true, separator: ";"));
2. Options object (works on any method, including the Baresheet facade). The constructor provides full IDE autocomplete:
use LeKoala\Baresheet\Options; $opts = new Options( assoc: true, separator: 'auto', meta: ['creator' => 'My App'] ); $rows = Baresheet::read('data.csv', $opts);
| Option | Type | Default | Applies to |
|---|---|---|---|
assoc |
bool | false |
Read (All) |
strict |
bool | false |
Read (CSV, XLSX, ODS) |
stream |
bool | true |
Output (Any) |
limit |
?int | null |
Read (All) |
offset |
int | 0 |
Read (All) |
skipEmptyLines |
bool | true |
Read (All) |
headers |
string[] | [] |
Write (All), Read (CSV) |
separator |
string | "auto" |
Read (CSV) |
enclosure |
string | " |
Read (CSV) |
escape |
string | "" |
Read (CSV) |
eol |
string | \r\n |
Write (CSV) |
inputEncoding |
?string | null |
Read (CSV) |
outputEncoding |
?string | null |
Read (CSV) |
bom |
bool|string|Bom | true |
Write (CSV) |
escapeFormulas |
bool | false |
Write (CSV) |
meta |
array/Meta | null |
Write (XLSX, ODS) |
autofilter |
?string | null |
Write (XLSX) |
freezePane |
?string | null |
Write (XLSX) |
sheet |
string/int | null |
Read/Write (XLSX, ODS) |
boldHeaders |
bool | false |
Write (XLSX, ODS) |
tempPath |
?string | null |
Any (Temp files location) |
sharedStrings |
bool | false |
Write (XLSX) |
autoWidth |
bool | false |
Write (XLSX) |
Streaming Output
For large files, streaming avoids writing a temporary file to disk. Baresheet streams output() by default.
However, keep in mind that streaming changes how data is sent to the browser. Because the total file size is unknown before the transfer starts, the server cannot send a Content-Length header. This means the browser download will not display a progress bar or an estimated time of completion.
To bypass streaming and force buffering, use stream: false with output(). Baresheet will buffer the file (either in memory for CSV, or via a temporary zip file for XLSX/ODS) to precisely calculate and send the Content-Length header along with it.
Note on XLSX/ODS: Streaming requires an optional dependency. Install it with:
composer require maennchen/zipstream-php
If the zipstream-php dependency is missing, Baresheet will seamlessly and automatically fall back to buffered output.
$writer = new XlsxWriter(); $writer->stream = false; $writer->output($data, 'report.xlsx'); // or via Options Baresheet::output($data, 'report.xlsx', new Options(stream: false));
PSR-7 / Response Objects (Symfony, Laravel)
To avoid breaking the flow of your application or sending explicit header() calls directly, you should create a Response object when applicable in your framework.
Use the writeStream() method to generate the spreadsheet as a memory-capped php://temp stream resource, and feed it into your Response class:
Symfony / Laravel (StreamedResponse)
use LeKoala\Baresheet\XlsxWriter; use Symfony\Component\HttpFoundation\StreamedResponse; $writer = new XlsxWriter(); $stream = $writer->writeStream($data); return new StreamedResponse(function () use ($stream) { fpassthru($stream); fclose($stream); }, 200, [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Content-Disposition' => 'attachment; filename="report.xlsx"', ]);
PSR-7 (Guzzle, Nyholm, etc.)
$stream = Baresheet::writeStream($data, 'xlsx'); $body = new \GuzzleHttp\Psr7\Stream($stream); // wrap the native resource return $response ->withHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') ->withHeader('Content-Disposition', 'attachment; filename="report.xlsx"') ->withBody($body);
Performance
Baresheet is explicitly engineered to minimize server resource footprint.
The XLSX and ODS readers use an optimized XMLReader approach that opens zip:// streams directly. This avoids any temporary file extraction, cutting I/O overhead by 50% compared to standard zip extraction methods.
Here are the results extracting/writing 50,000 rows (4 columns) locally against other common industry standard libraries:
Reading (Parsing) 50,000 Rows
Reading CSV
| Library | Avg Time (s) | Peak Memory (MB) |
|---|---|---|
| Baresheet (CSV) | 0.0053 | 0.63 |
| League (CSV) | 0.0096 | 0.63 |
| OpenSpout (CSV) | 0.0206 | 0.63 |
Reading XLSX
| Library | Avg Time (s) | Peak Memory (MB) |
|---|---|---|
| Baresheet (XLSX) | 0.0410 | 0.63 |
| SimpleXLSX (XLSX) | 0.0840 | 5.78 |
| OpenSpout (XLSX) | 0.1971 | 0.63 |
Reading ODS
| Library | Avg Time (s) | Peak Memory (MB) |
|---|---|---|
| Baresheet (ODS) | 0.0600 | 0.63 |
| OpenSpout (ODS) | 0.1709 | 0.63 |
Writing 50,000 Rows
Writing CSV
| Library | Avg Time (s) | Peak Memory (MB) |
|---|---|---|
| Baresheet (CSV) | 0.0995 | 0.48 |
| League (CSV) | 0.1390 | 0.55 |
| OpenSpout (CSV) | 0.2824 | 0.47 |
Writing XLSX
| Library | Avg Time (s) | Peak Memory (MB) |
|---|---|---|
| Baresheet (XLSX) | 0.4739 | 0.76 |
| Baresheet (XLSX - Auto Width) | 0.5009 | 0.77 |
| SimpleXLSXGen (XLSX) | 0.6714 | 109.77 |
| OpenSpout (XLSX) | 0.9841 | 1.01 |
| Baresheet (XLSX - Shared Strings) | 0.9969 | 34.23 |
Note: By default, Baresheet uses the fastest mode (shared strings and auto column width disabled). You can re-enable them via Options.
Writing ODS
| Library | Avg Time (s) | Peak Memory (MB) |
|---|---|---|
| Baresheet (ODS) | 0.9208 | 0.85 |
| OpenSpout (ODS) | 1.3569 | 0.88 |
Security Considerations
CSV Formula Injection
When writing CSV files, any cell beginning with =, +, -, or @ could be interpreted as a formula if the file is opened in spreadsheet software like Microsoft Excel. A maliciously crafted input could lead to execution of arbitrary functions or system commands on the user's local machine.
By default, Baresheet prioritizes data round-trip integrity. Attempting to automatically prefix formulas with a single quote (') to disable formula execution corrupts otherwise valid user inputs.
If you are exporting data to be consumed by clients opening the file in Excel, you must opt-in to the protection logic:
$writer = new CsvWriter(); $writer->escapeFormulas = true; // Protects against formula injection by prefixing a single-quote
License
MIT