alexskrypnyk / csvtable
PHP class to parse and format CSV content.
Requires
- php: >=8.3
Requires (Dev)
- dealerdirect/phpcodesniffer-composer-installer: ^1.2.1
- drevops/phpcs-standard: ^0.7
- drupal/coder: ^9.0.1
- ergebnis/composer-normalize: ^2.52.0
- phpstan/phpstan: ^2.2.13
- phpunit/phpunit: ^12
- rector/rector: ^2.6.6
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
PHP class to parse and format CSV content
✨ Features
- Single-file class to manipulate CSV table.
- Formatters for CSV, text table and Markdown table.
- Support for a custom formatter.
- Column manipulation: reorder, filter, and exclude columns.
📋 Requirements
PHP 8.3 or newer. The class uses only the PHP standard library, so CsvTable.php can also be dropped into a project that does not use Composer.
📦 Installation
composer require alexskrypnyk/csvtable
The class is AlexSkrypnyk\CsvTable\CsvTable.
🚀 Usage
Given a CSV file with the following content:
col11,col12,col13 col21,col22,col23 col31,col32,col33
From string
$csv = file_get_contents($csv_file); print (new CsvTable($csv))->format();
will produce identical CSV content by default:
col11,col12,col13 col21,col22,col23 col31,col32,col33
From file
print (CsvTable::fromFile($file))->format();
will produce identical CSV content by default:
col11,col12,col13 col21,col22,col23 col31,col32,col33
fromFile() throws an Exception when the file is missing or cannot be read.
Reading the parsed data
parse() populates the header and the rows, which are then available through getHeader() and getRows(). Calling format() parses the content automatically, so parse() is only needed when reading the data directly.
$table = new CsvTable($csv); $table->parse(); // ['col11', 'col12', 'col13'] print_r($table->getHeader()); // [['col21', 'col22', 'col23'], ['col31', 'col32', 'col33']] print_r($table->getRows());
Custom separator, enclosure and escape characters
The constructor and fromFile() both accept the separator, enclosure and escape characters used to read the content. They default to ,, " and \.
// Parse semicolon-separated content. $table = new CsvTable($csv_semicolon, ';'); // The same, reading from a file. $table = CsvTable::fromFile($file_semicolon, ';');
These characters apply to parsing only. The csv formatter writes using its own separator, enclosure and escape options, so semicolon-separated input is written back as comma-separated content unless the formatter is told otherwise:
$table = new CsvTable($csv_semicolon, ';'); // Comma-separated output. print $table->format(); // Semicolon-separated output. print $table->format(NULL, ['separator' => ';']);
Using table formatter
print (CsvTable::fromFile($file))->format('table');
will produce table content:
col11|col12|col13
-----------------
col21|col22|col23
col31|col32|col33
Using table formatter without a header
print (CsvTable::fromFile($file))->withoutHeader()->format('table');
will produce table content:
col11|col12|col13
col21|col22|col23
col31|col32|col33
withHeader() restores the default behaviour of treating the first row as a header.
Using markdown_table formatter
print (CsvTable::fromFile($file))->format('markdown_table');
will produce Markdown table:
| col11 | col12 | col13 | |-------|-------|-------| | col21 | col22 | col23 | | col31 | col32 | col33 |
Formatter options
Options are passed as the second argument to format() and are merged over the defaults below.
| Formatter | Option | Default | Description |
|---|---|---|---|
csv |
separator |
, |
Character written between values. |
csv |
enclosure |
" |
Character used to enclose values. |
csv |
escape |
\ |
Character used to escape special characters. |
table |
column_separator |
| |
String written between columns. |
table |
row_separator |
\n |
String written between rows. |
markdown_table |
column_separator |
| |
String written between columns. |
markdown_table |
row_separator |
\n |
String written between rows. |
markdown_table |
header_separator |
- |
Character filling the row under the header. |
markdown_table |
value_row_separator |
<br/> |
Replaces newlines found within a value. |
print (CsvTable::fromFile($file)) ->format('markdown_table', ['header_separator' => '=']);
will produce:
| col11 | col12 | col13 | |=======|=======|=======| | col21 | col22 | col23 | | col31 | col32 | col33 |
Custom formatter as an anonymous callback
A formatter receives the header columns, the rows and the options, and returns the formatted string.
print (CsvTable::fromFile($file))->format(function ($header, $rows, $options) { $output = ''; if (count($header) > 0) { $output = implode('|', $header); $output .= "\n" . str_repeat('=', strlen($output)) . "\n"; } return $output . implode("\n", array_map(static function ($row): string { return implode('|', $row); }, $rows)); });
will produce:
col11|col12|col13
=================
col21|col22|col23
col31|col32|col33
Custom formatter as a class with default format method
When a class name is passed, its static format() method is called.
print (CsvTable::fromFile($file)) ->withoutHeader() ->format(CustomFormatter::class);
Custom formatter as a class with a custom method and options
$formatter_options = ['option1' => 'value1', 'option2' => 'value2']; print (CsvTable::fromFile($file)) ->withoutHeader() ->format([CustomFormatter::class, 'customFormat'], $formatter_options);
format() throws an Exception when the value passed to it cannot be resolved to a callable.
🔀 Column Manipulation
Given a CSV file with the following content:
Name,Age,City,Country John,30,New York,USA Jane,25,London,UK
Reorder columns with columnOrder()
Reorder columns by specifying the desired order. Columns not specified are appended in their original order.
print (CsvTable::fromFile($file)) ->columnOrder(['City', 'Name']) ->format('markdown_table');
will produce:
| City | Name | Age | Country | |----------|------|-----|---------| | New York | John | 30 | USA | | London | Jane | 25 | UK |
Filter to specific columns with onlyColumns()
Keep only the specified columns in the output, in the order specified.
print (CsvTable::fromFile($file)) ->onlyColumns(['Name', 'City']) ->format('markdown_table');
will produce:
| Name | City | |------|----------| | John | New York | | Jane | London |
Exclude columns with withoutColumns()
Exclude specified columns from the output. The remaining columns keep their original order.
print (CsvTable::fromFile($file)) ->withoutColumns(['Age', 'Country']) ->format('markdown_table');
will produce:
| Name | City | |------|----------| | John | New York | | Jane | London |
Using column indices
All column methods accept both column names (strings) and zero-based indices (integers). An unknown name or an out-of-range index throws an InvalidArgumentException.
// Using indices. print (CsvTable::fromFile($file))->columnOrder([2, 0])->format(); // Using mixed names and indices. print (CsvTable::fromFile($file))->onlyColumns(['Name', 2])->format();
Names are resolved against the header, so content parsed with withoutHeader() can only be addressed by index.
Combining column transformations
Column transformations are applied in order: onlyColumns() → withoutColumns() → columnOrder().
print (CsvTable::fromFile($file)) ->withoutColumns(['Age']) ->columnOrder(['Country', 'City']) ->format('markdown_table');
will produce:
| Country | City | Name | |---------|----------|------| | USA | New York | John | | UK | London | Jane |
Reset column transformations
resetColumns() clears all three transformations at once. resetColumnOrder(), resetOnlyColumns() and resetWithoutColumns() clear them individually.
$table = CsvTable::fromFile($file); // Apply and use transformations. $table->columnOrder(['City', 'Name']); print $table->format(); // Reset and format without transformations. print $table->resetColumns()->format();
🤝 Contributing
See CONTRIBUTING.md for local development setup and the linting and testing commands.
🔄 Updating
To pull the latest infrastructure from the template into this project, ask Claude Code to "update scaffold" - see AGENTS.md for details.
This repository was created using the Scaffold project template