Writers output the data to
take the data from the readers


Resembles the ArrayReader. Probably most useful for testing your workflow.


Writes CSV files.

Install the CSV adapter:

$ composer require portphp/csv

Then use the writer:

use Port\Csv\CsvWriter;

$writer = new CsvWriter();
$writer->setStream(fopen('output.csv', 'w'));

// Write column headers:
$writer->writeItem(['first', 'last']);

// Write some data
$writer->writeItem(['James', 'Bond']);
$writer->writeItem(['Auric', 'Goldfinger']);



Writes data through the Doctrine ORM and ODM.

Install the Doctrine adapter:

$ composer require portphp/doctrine
use Port\Doctrine\DoctrineWriter;

$writer = new DoctrineWriter($objectManager, 'YourNamespace:Employee');
        'first' => 'James',
        'last'  => 'Bond'

By default, DoctrineWriter will truncate your data before running the workflow. Call disableTruncate() if you don't want this.

If you are not truncating data, DoctrineWriter will try to find an entity having it's primary key set to the value of the first column of the item. If it finds one, the entity will be updated, otherwise it's inserted. You can tell DoctrineWriter to lookup the entity using different columns of your item by passing a third parameter to it's constructor.

$writer = new DoctrineWriter($entityManager, 'YourNamespace:Employee', 'columnName');


$writer = new DoctrineWriter($entityManager, 'YourNamespace:Employee', ['column1', 'column2', 'column3']);

The DoctrineWriter will also search out associations automatically and link them by an entity reference. For example suppose you have a Product entity that you are importing and must be associated to a Category. If there is a field in the import file named 'Category' with an id, the writer will use metadata to get the association class and create a reference so that it can be associated properly. The DoctrineWriter will skip any association fields that are already objects in cases where a converter was used to retrieve the association.


Writes data to an Excel file.

Install the Excel adapter:

$ composer require portphp/excel

Then construct an ExcelWriter:

use Port\Excel\ExcelWriter;

$file = new \SplFileObject('data.xlsx', 'w');
$writer = new ExcelWriter($file);

$writer->writeItem(['first', 'last']);
$writer->writeItem(['first' => 'James', 'last' => 'Bond']);

You can specify the name of the sheet to write to:

$writer = new ExcelWriter($file, 'My sheet');

You can open an already existing file and add a sheet to it:

$file = new \SplFileObject('data.xlsx', 'a');   // Open file with append mode
$writer = new ExcelWriter($file, 'New sheet');

If you wish to overwrite an existing sheet instead, specify the name of the existing sheet:

$writer = new ExcelWriter($file, 'Old sheet');


Writes data to an Excel file.

Install the Spreadsheet adapter:

$ composer require portphp/spreadsheet

Then construct an SpreadsheetWriter:

use Port\Spreadsheet\SpreadsheetWriter;

$file = new \SplFileObject('data.xlsx', 'w');
$writer = new SpreadsheetWriter($file);

$writer->writeItem(['first', 'last']);
$writer->writeItem(['first' => 'James', 'last' => 'Bond']);

You can specify the name of the sheet to write to:

$writer = new SpreadsheetWriter($file, 'My sheet');

You can open an already existing file and add a sheet to it:

$file = new \SplFileObject('data.xlsx', 'a');   // Open file with append mode
$writer = new SpreadsheetWriter($file, 'New sheet');

If you wish to overwrite an existing sheet instead, specify the name of the existing sheet:

$writer = new SpreadsheetWriter($file, 'Old sheet');


Use the PDO writer for importing data into a relational database (such as MySQL, SQLite or MS SQL) without using Doctrine.

use Port\Writer\PdoWriter;

$pdo = new \PDO('sqlite::memory:');

$writer = new PdoWriter($pdo, 'my_table');

Symfony Console


This writer displays items as table on console output for debug purposes when you start the workflow from the command-line.

Install the Symfony Console adapter:

$ composer require portphp/symfony-console
use Port\Reader;
use Port\Steps\StepAggregator as Workflow;
use Port\SymfonyConsole\TableWriter;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Helper\Table;

$reader = new Reader\...;
$output = new ConsoleOutput(...);

$table = new Table($output);

// Make some manipulations, e.g. set table style

$workflow = new Workflow($reader);
$workflow->addWriter(new TableWriter($output, $table));


This writer displays import progress when you start the workflow from the command-line.

use Port\SymfonyConsole\ProgressWriter;
use Symfony\Component\Console\Output\ConsoleOutput;

$output = new ConsoleOutput(...);
$progressWriter = new ProgressWriter($output, $reader);

// Most useful when added to a workflow

There are various optional arguments you can pass to the ConsoleProgressWriter. These include the output format and the redraw frequency. You can read more about the options here.

You might want to set the redraw rate higher than the default as it can slow down the import/export process quite a bit as it will update the console text after every record has been processed by the Workflow.

$output = new ConsoleOutput(...);
$progressWriter = new ProgressWriter($output, $reader, 'debug', 100);

Above we set the output format to 'debug' and the redraw rate to 100. This will only re-draw the console progress text after every 100 records.

The debug format is default as it displays ETA's and Memory Usage. You can use a more simple formatter if you wish:

$output = new ConsoleOutput(...);
$progressWriter = new ProgressWriter($output, $reader, 'normal', 100);


Suppose you have two stream writers handling fields differently according to one of the fields. You should then use StreamMergeWriter to call the appropriate Writer for you.

The default field name is discr and can be changed with the setDiscriminantField() method.


use Port\Writer\StreamMergeWriter;

$writer = new StreamMergeWriter();

$writer->addWriter('first writer', new MyStreamWriter());
$writer->addWriter('second writer', new MyStreamWriter());


Writes XML files.

Install the XML adapter:

$ composer require portphp/xml

First construct PHP’s built-in XMLWriter, then wrap it in `Port\Xml\XmlWriter', additionally passing the filename to write to:


use Port\Xml\XmlWriter;

$phpXmlWriter = new \XMLWriter();
$writer = new XmlWriter($phpXmlWriter, 'output-file.xml');

Simply pass the writer to a workflow or use the writer on its own:



foreach ($data as $item) {


Pass the root and item elements as the third and fourth arguments:


$writer = new XmlWriter(
    'things', // root item
    'thing'   // element item

Create a writer

Build your own writer by implementing the Writer interface.


Instead of implementing your own writer from scratch, you can use AbstractStreamWriter as a basis. Just implement writeItem():


use Port\Writer\AbstractStreamWriter;

class MyStreamWriter extends AbstractStreamWriter
    public function writeItem(array $item)
        fputs($this->getStream(), implode(',', $item));

$writer = new MyStreamWriter(fopen('php://temp', 'r+'));

$workflow->addWriter(new MyStreamWriter());

$stream = $writer->getStream();

echo stream_get_contents($stream);


You can also use the quick solution the CallbackWriter offers:


use Port\Writer\CallbackWriter;

$workflow->addWriter(new CallbackWriter(function ($row) use ($storage) {