https://github.com/lekoala/baresheet
Fast, zero-dependency CSV, XLSX and ODS reader/writer for PHP
https://github.com/lekoala/baresheet
csv lightweight minimal ods php spreadsheet xlsx
Last synced: 8 days ago
JSON representation
Fast, zero-dependency CSV, XLSX and ODS reader/writer for PHP
- Host: GitHub
- URL: https://github.com/lekoala/baresheet
- Owner: lekoala
- License: mit
- Created: 2026-03-04T11:25:48.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-03-27T17:00:30.000Z (16 days ago)
- Last Synced: 2026-03-27T22:40:26.541Z (16 days ago)
- Topics: csv, lightweight, minimal, ods, php, spreadsheet, xlsx
- Language: PHP
- Homepage:
- Size: 284 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README
# Baresheet
Fast, zero-dependency CSV, XLSX, and ODS reader/writer for PHP.
[](LICENSE)
## 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
```bash
composer require lekoala/baresheet
```
## Quick Start
```php
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:
```php
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 `XMLReader` with direct `zip://` 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: `false` for speed)
- **Auto column widths** — opt-in automatic column sizing (default: `false` for speed)
- **DateTime support** — pass `DateTimeInterface` objects 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:**
```php
$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**:
```php
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:
```bash
composer require maennchen/zipstream-php
```
If the `zipstream-php` dependency is missing, Baresheet will seamlessly and automatically fall back to buffered output.
```php
$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)
```php
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.)
```php
$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.0056 | 0.63 |
| League (CSV) | 0.0088 | 0.63 |
| OpenSpout (CSV) | 0.0200 | 0.63 |
#### Reading XLSX
| Library | Avg Time (s) | Peak Memory (MB) |
|-------------------|--------------|------------------|
| Baresheet (XLSX) | 0.0406 | 0.63 |
| SimpleXLSX (XLSX) | 0.0797 | 5.78 |
| OpenSpout (XLSX) | 0.2151 | 0.63 |
#### Reading ODS
| Library | Avg Time (s) | Peak Memory (MB) |
|-----------------|--------------|------------------|
| Baresheet (ODS) | 0.0630 | 0.63 |
| OpenSpout (ODS) | 0.1685 | 0.63 |
### Writing 50,000 Rows
#### Writing CSV
| Library | Avg Time (s) | Peak Memory (MB) |
|-----------------|--------------|------------------|
| Baresheet (CSV) | 0.1070 | 0.48 |
| League (CSV) | 0.1384 | 0.55 |
| OpenSpout (CSV) | 0.2855 | 0.47 |
#### Writing XLSX
| Library | Avg Time (s) | Peak Memory (MB) |
|-----------------------------------|--------------|------------------|
| Baresheet (XLSX - Auto Width) | 0.4823 | 0.77 |
| Baresheet (XLSX) | 0.5022 | 0.77 |
| SimpleXLSXGen (XLSX) | 0.6742 | 109.77 |
| OpenSpout (XLSX) | 0.9063 | 1.01 |
| Baresheet (XLSX - Shared Strings) | 0.9387 | 57.14 |
> 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.8540 | 0.91 |
| OpenSpout (ODS) | 1.2791 | 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:
```php
$writer = new CsvWriter();
$writer->escapeFormulas = true; // Protects against formula injection by prefixing a single-quote
```
## License
MIT