1. Go to this page and download the library: Download tobento/service-read-write library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
tobento / service-read-write example snippets
use Tobento\Service\ReadWrite\Modifier;
use Tobento\Service\ReadWrite\Processor;
use Tobento\Service\ReadWrite\Reader;
use Tobento\Service\ReadWrite\Writer;
$reader = new Reader\CsvStream(
// PSR-7 StreamInterface
stream: new Psr17Factory()->createStreamFromFile('/data/input.csv'),
);
$writer = new Writer\CsvResource(
resource: new Writer\Resource\LocalFile('/data/output.csv'),
);
$modifiers = new Modifier\Modifiers(
new Modifier\ColumnMap(['title' => 'headline']),
);
$processor = new Processor\TimeBudgetProcessor(
timeBudget: 10,
modifiers: $modifiers
);
$result = $processor->process(reader: $reader, writer: $writer);
print_r($result->timeline());
use Nyholm\Psr7\Factory\Psr17Factory;
use Tobento\Service\ReadWrite\Reader;
// Create a PSR-7 stream from a local file
$stream = new Psr17Factory()->createStreamFromFile('/data/input.csv');
// Initialize the CSV reader
$reader = new Reader\CsvStream(
stream: $stream,
delimiter: ',', // optional, defaults to ','
enclosure: '"', // optional, defaults to '"'
escape: '\\', // optional, defaults to '\'
);
// Read the first 5 rows starting at offset 0
foreach ($reader->read(offset: 0, limit: 5) as $row) {
echo $row->key() . ': ' . json_encode($row->all()) . PHP_EOL;
}
if ($reader->isFinished()) {
echo 'Reached end of CSV at offset: ' . $reader->currentOffset();
}
use Nyholm\Psr7\Factory\Psr17Factory;
use Tobento\Service\ReadWrite\Reader\JsonStream;
$stream = new Psr17Factory()->createStreamFromFile('/data/input.json');
$reader = new JsonStream($stream);
// Read the first 3 rows
foreach ($reader->read(offset: 0, limit: 3) as $row) {
if ($row instanceof \Tobento\Service\ReadWrite\Row\SkipRow) {
echo "Skipped row: " . $row->reason() . PHP_EOL;
} else {
print_r($row->all());
}
}
echo 'Offset: ' . $reader->currentOffset() . PHP_EOL;
if ($reader->isFinished()) {
echo 'Reached end of JSON stream';
}
use Nyholm\Psr7\Factory\Psr17Factory;
use Tobento\Service\ReadWrite\Reader\NdJsonStream;
$stream = new Psr17Factory()->createStreamFromFile('/data/input.ndjson');
$reader = new NdJsonStream(stream: $stream);
// Read the first 2 rows starting at offset 0
foreach ($reader->read(offset: 0, limit: 2) as $row) {
if ($row instanceof \Tobento\Service\ReadWrite\Row\SkipRow) {
echo 'Skipped row: ' . $row->reason() . PHP_EOL;
} else {
echo json_encode($row->all()) . PHP_EOL;
}
}
echo 'Current offset: ' . $reader->currentOffset() . PHP_EOL;
if ($reader->isFinished()) {
echo 'Reached end of NDJSON stream';
}
use Tobento\Service\ReadWrite\Reader\RepositoryReader;
use Tobento\Service\Repository\ReadRepositoryInterface;
// Create reader with a query
$reader = new RepositoryReader(
// ReadRepositoryInterface instance used as the data source
repository: $repository,
// Filtering conditions applied before reading
where: [],
// Sorting rules
orderBy: [],
// Optional callable to convert objects into arrays
objectToArray: function (object $entity): array {
// Custom conversion logic for domain objects
return [
'id' => $entity->id(),
'name' => $entity->name(),
'role' => $entity->role(),
];
},
// Number of rows sampled for columnsPreview()
previewRows: 3,
);
// Read the first 2 rows
foreach ($reader->read(offset: 0, limit: 2) as $row) {
if ($row instanceof \Tobento\Service\ReadWrite\Row\SkipRow) {
echo 'Skipped row: ' . $row->reason() . PHP_EOL;
} else {
print_r($row->all());
}
}
echo 'Current offset: ' . $reader->currentOffset() . PHP_EOL;
if ($reader->isFinished()) {
echo 'Reached end of active users';
}
use Tobento\Service\ReadWrite\Reader\StorageReader;
use Tobento\Service\Storage\StorageInterface;
// Create reader with a query
$reader = new StorageReader(
// StorageInterface instance used as the data source
storage: $storage,
// Table name to read from
table: 'products',
// Optional query callable applied before reading
query: function (StorageInterface $t): void {
$t->where('price', '>', 10)->order('price', 'asc');
},
// Number of rows sampled for columnsPreview()
previewRows: 3,
);
// Read the first 2 rows
foreach ($reader->read(offset: 0, limit: 2) as $row) {
if ($row instanceof \Tobento\Service\ReadWrite\Row\SkipRow) {
echo 'Skipped row: ' . $row->reason() . PHP_EOL;
} else {
print_r($row->all());
}
}
echo 'Current offset: ' . $reader->currentOffset() . PHP_EOL;
if ($reader->isFinished()) {
echo 'Reached end of filtered products';
}
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\ReadWrite\Writer\CsvResource;
use Tobento\Service\ReadWrite\Writer\Mode;
use Tobento\Service\ReadWrite\Writer\Resource\LocalFile;
// Create a file resource
$resource = new LocalFile('/data/output.csv');
// Initialize the CSV writer
$writer = new CsvResource(
resource: $resource,
delimiter: ',', // optional
enclosure: '"', // optional
escape: '\\', // optional
writeBom: true // optional
);
// Set mode (overwrite or append)
$writer->mode(Mode::Overwrite);
// Start writing
$writer->start();
// Write rows
$writer->write(new Row(key: 1, attributes: ['title' => 'Hello', 'status' => 'Draft']));
$writer->write(new Row(key: 2, attributes: ['title' => 'World', 'status' => 'Published']));
// Finish writing
$writer->finish();
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\ReadWrite\Writer\PdfResource;
use Tobento\Service\ReadWrite\Writer\Resource\LocalFile;
use Tobento\Service\View\ViewInterface;
// Create a file resource
$resource = new LocalFile('/data/report.html');
// Initialize the HTML writer with a template and template data
$writer = new HtmlResource(
resource: $resource,
view: $view, // ViewInterface see view service.
templateName: 'html/export-table',
templateData: [
'title' => 'Product Report',
'generated_at' => date('Y-m-d'),
],
);
// Start writing
$writer->start();
// Write rows (these will be available as $rows in the template)
$writer->write(new Row(key: 1, attributes: ['name' => 'Apple', 'price' => 2.50]));
$writer->write(new Row(key: 2, attributes: ['name' => 'Banana', 'price' => 1.20]));
// Finish writing and generate the HTML
$writer->finish();
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\ReadWrite\Writer\JsonResource;
use Tobento\Service\ReadWrite\Writer\Mode;
use Tobento\Service\ReadWrite\Writer\Resource\LocalFile;
// Create a file resource
$resource = new LocalFile('/data/output.json');
// Initialize the JSON writer
$writer = new JsonResource($resource);
// Set mode (overwrite or append)
$writer->mode(Mode::Overwrite);
// Start writing
$writer->start();
// Write rows
$writer->write(new Row(key: 1, attributes: ['title' => 'Hello', 'status' => 'Draft']));
$writer->write(new Row(key: 2, attributes: ['title' => 'World', 'status' => 'Published']));
// Finish writing
$writer->finish();
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\ReadWrite\Writer\Mode;
use Tobento\Service\ReadWrite\Writer\NdJsonResource;
use Tobento\Service\ReadWrite\Writer\Resource\LocalFile;
// Create a file resource
$resource = new LocalFile('/data/output.ndjson');
// Initialize the NDJSON writer
$writer = new NdJsonResource($resource);
// Start writing
$writer->start();
// Write rows (each will be a separate line)
$writer->write(new Row(key: 1, attributes: ['title' => 'Hello', 'status' => 'Draft']));
$writer->write(new Row(key: 2, attributes: ['title' => 'World', 'status' => 'Published']));
// Finish writing
$writer->finish();
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\ReadWrite\Writer\Mode;
use Tobento\Service\ReadWrite\Writer\NullWriter;
// Initialize the NullWriter
$writer = new NullWriter();
// (Optional) Set mode if your application uses writer modes
$writer->mode(Mode::Overwrite);
// Start writing (no-op)
$writer->start();
// Write rows (no-op)
$writer->write(new Row(
key: 1,
attributes: ['title' => 'Hello', 'status' => 'Draft']
));
$writer->write(new Row(
key: 2,
attributes: ['title' => 'World', 'status' => 'Published']
));
// Finish writing (no-op)
$writer->finish();
use Tobento\Service\Pdf\Enums\Orientation;
use Tobento\Service\Pdf\Pdf;
use Tobento\Service\Pdf\PdfGenerator;
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\ReadWrite\Writer\PdfResource;
use Tobento\Service\ReadWrite\Writer\Resource\LocalFile;
// Create a file resource
$resource = new LocalFile('/data/report.pdf');
// Create a PDF generator: see pdf service.
$pdfGenerator = new PdfGenerator();
// Initialize the PDF writer with a template and template data
$writer = new PdfResource(
resource: $resource,
pdfGenerator: $pdfGenerator,
templateName: 'pdf/export-table',
templateData: [
'title' => 'Product Report',
'description' => 'Generated on ' . date('Y-m-d'),
],
pdf: new Pdf()->orientation(Orientation::LANDSCAPE),
);
// Start writing
$writer->start();
// Write rows (these will be available as $rows in the template)
$writer->write(new Row(key: 1, attributes: ['name' => 'Apple', 'price' => 2.50]));
$writer->write(new Row(key: 2, attributes: ['name' => 'Banana', 'price' => 1.20]));
// Finish writing and generate the PDF
$writer->finish();
use Tobento\Service\FileStorage\NullStorage;
use Tobento\Service\FileStorage\StorageInterface;
use Tobento\Service\ReadWrite\Exception\WriterException;
use Tobento\Service\ReadWrite\Writer\Resource\FileStorage;
// Create a storage (NullStorage for demo)
$storage = new NullStorage(name: 'null');
// Initialize the FileStorage resource
$resource = new FileStorage(storage: $storage, filename: 'output.csv');
// Open resource
$resource->open();
// Write data
$resource->write("id,title,status\n");
$resource->write("1,Hello,Draft\n");
$resource->write("2,World,Published\n");
// Rewind if needed
$resource->rewind();
// Close and commit to storage
$resource->close();
use Tobento\Service\ReadWrite\Writer\Resource\InMemory;
use Tobento\Service\ReadWrite\Exception\WriterException;
// Initialize the in-memory resource
$resource = new InMemory();
// Open resource
$resource->open();
// Write data
$resource->write("id,title,status\n");
$resource->write("1,Hello,Draft\n");
$resource->write("2,World,Published\n");
// Rewind if needed
$resource->rewind();
// Close resource
$resource->close();
// Retrieve buffered content
echo $resource->getContent();
use Tobento\Service\ReadWrite\Writer\Resource\LocalFile;
use Tobento\Service\ReadWrite\Exception\WriterException;
// Initialize the LocalFile resource
$resource = new LocalFile(filename: '/data/output.csv', mode: 'w');
// Open resource
$resource->open();
// Write data
$resource->write("id,title,status\n");
$resource->write("1,Hello,Draft\n");
$resource->write("2,World,Published\n");
// Rewind if needed
$resource->rewind();
// Close resource
$resource->close();
use Tobento\Service\ReadWrite\Modifier\ApplyModifiersIf;
// Always apply:
new ApplyModifiersIf(true, $modifier);
// Never apply:
new ApplyModifiersIf(false, $modifier);
use Tobento\Service\ReadWrite\Modifier\ApplyModifiersIf;
new ApplyModifiersIf('country', $modifier);
use Tobento\Service\ReadWrite\Modifier\ApplyModifiersIf;
// Apply if field is missing:
new ApplyModifiersIf(
['field' => 'postal_code', 'missing' => true],
$modifier
);
// Apply if field equals a specific value:
new ApplyModifiersIf(
['field' => 'country', 'equals' => 'CH'],
$modifier
);
use Tobento\Service\ReadWrite\Modifier\ApplyModifiersIf;
use Tobento\Service\ReadWrite\RowInterface;
new ApplyModifiersIf(
fn(RowInterface $row): bool => $row->get('country') === 'CH',
$modifier
);
use Tobento\Service\ReadWrite\Modifier\CallableModifier;
use Tobento\Service\ReadWrite\ReaderInterface;
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\ReadWrite\RowInterface;
use Tobento\Service\ReadWrite\WriterInterface;
// Define a custom modifier
$modifier = new CallableModifier(function(RowInterface $row, ReaderInterface $reader, WriterInterface $writer): RowInterface {
$attributes = $row->all();
$attributes['title'] = strtoupper($attributes['title'] ?? '');
return new Row(
key: $row->key(),
attributes: $attributes
);
});
// Normally applied by processors, but can be invoked manually:
$modified = $modifier->modify($row, $reader, $writer);
use Tobento\Service\ReadWrite\Modifier\ColumnMap;
use Tobento\Service\ReadWrite\Row\Row;
// Define a column mapping
$modifier = new ColumnMap([
'title' => 'name',
'status' => 'state',
]);
// Example row
$row = new Row(
key: 1,
attributes: [
'title' => 'Hello',
'status' => 'Draft',
'ignored' => 'Not mapped',
]
);
// Normally applied by processors, but can be invoked manually:
$modified = $modifier->modify($row, $reader, $writer);
// Resulting row attributes:
// ['name' => 'Hello', 'state' => 'Draft']
use Tobento\Service\ReadWrite\Modifier\CombineFields;
use Tobento\Service\ReadWrite\Row\Row;
$modifier = new CombineFields(
fields: ['first_name', 'last_name', 'age'],
into: 'summary',
separator: ' ',
removeSourceFields: true
);
$row = new Row(1, [
'first_name' => 'John',
'last_name' => 'Doe',
'age' => 42,
]);
$modified = $modifier->modify($row, $reader, $writer);
// Result:
// ['summary' => 'John Doe 42']
use Tobento\Service\ReadWrite\Modifier\Mask;
// Custom masking: always replace the value with six asterisks
$modifier = new Mask(
fields: 'phone',
masker: fn(mixed $value): string => '******',
);
use Tobento\Service\ReadWrite\Modifier\Redact;
// Replace values with a fixed string instead of null
$modifier = new Redact(
fields: 'api.key',
replacement: 'REDACTED',
);
use Tobento\Service\ReadWrite\Modifier\Replace;
$modifier = new Replace(
fields: 'comment',
replacements: [
'foo' => 'bar',
],
strict: false,
);
use Tobento\Service\ReadWrite\Modifier\Replace;
$modifier = new Replace(
fields: 'email',
replacements: [
null => 'unknown',
],
strict: true,
forceNullReplacement: true,
);
use Tobento\Service\ReadWrite\Modifier\Sanitize;
use Tobento\Service\Sanitizer\Sanitizer;
use Tobento\Service\ReadWrite\Row\Row;
$sanitizer = new Sanitizer();
// Define sanitation rules
$modifier = new Sanitize([
'title' => 'strip_tags|trim',
'published_at' => 'date:Y-m-d:d.m.Y',
], $sanitizer);
$row = new Row(
key: 1,
attributes: [
'title' => '<h1>Hello</h1> ',
'published_at' => '24.12.2025',
]
);
// Normally applied by processors, but can be invoked manually:
$modified = $modifier->modify($row, $reader, $writer);
// Resulting row attributes:
// [
// 'title' => 'Hello',
// 'published_at' => '2025-12-24', // normalized
// ]
use Tobento\Service\ReadWrite\Modifier\SkipIf;
// Skip all rows:
new SkipIf(true, 'Skipping all rows');
// Skip no rows:
new SkipIf(false);
use Tobento\Service\ReadWrite\Modifier\SkipIf;
new SkipIf('email', 'Email is
use Tobento\Service\ReadWrite\Modifier\SkipIf;
// Skip if field is missing or empty:
new SkipIf(
['field' => 'username', 'missing' => true],
'Username is missing'
);
// Skip if field equals a specific value:
new SkipIf(
['field' => 'status', 'equals' => 'N/A'],
'Status is N/A'
);
// Skip if field equals zero:
new SkipIf(
['field' => 'age', 'equals' => 0],
'Age cannot be zero'
);
use Tobento\Service\ReadWrite\Modifier\SkipIf;
use Tobento\Service\ReadWrite\RowInterface;
new SkipIf(
fn(RowInterface $row): bool => $row->get('age') < 18,
'User is under 18'
);
use Tobento\Service\ReadWrite\Modifier\Trim;
use Tobento\Service\ReadWrite\Row\Row;
// Define which attributes to trim
$modifier = new Trim(['title', 'status']);
// Example row
$row = new Row(
key: 1,
attributes: [
'title' => ' Hello World ',
'status' => ' Draft ',
'description' => ' untouched ',
]
);
// Normally applied by processors, but can be invoked manually:
$modified = $modifier->modify($row, $reader, $writer);
// Resulting row attributes:
// [
// 'title' => 'Hello World',
// 'status' => 'Draft',
// 'description' => ' untouched ', // unchanged
// ]
use Tobento\Service\ReadWrite\Modifier\Unique;
use Tobento\Service\ReadWrite\Row\Row;
// Ensure email is unique across the import
$modifier = new Unique(
fields: 'email',
lookup: null, // use in-memory uniqueness
onFail: 'skip', // or 'fail'
);
$row1 = new Row(
key: 1,
attributes: ['email' => '[email protected]']
);
$row2 = new Row(
key: 2,
attributes: ['email' => '[email protected]']
);
// First row passes
$modifier->modify($row1, $reader, $writer);
// Second row is skipped:
$modified = $modifier->modify($row2, $reader, $writer);
// Result:
// SkipRow {
// key: 2,
// attributes: ['email' => '[email protected]'],
// reason: 'Duplicate value for unique field "email": [email protected]'
// }
use Tobento\Service\ReadWrite\Modifier\Unique;
use Tobento\Service\ReadWrite\RowInterface;
$modifier = new Unique(
fields: 'sku',
lookup: fn(string $field, mixed $value, RowInterface $row): bool =>
$productRepository->skuExists($value),
onFail: 'fail'
);
use Tobento\Service\ReadWrite\Modifier\Validation;
use Tobento\Service\ReadWrite\Row\Row;
use Tobento\Service\Validation\Validator;
$validator = new Validator();
// Define validation rules
$modifier = new Validation(
rules: [
'email' => ',
]
);
// Normally applied by processors, but can be invoked manually:
$modified = $modifier->modify($row, $reader, $writer);
// Result when validation fails and onFail = 'skip':
// SkipRow {
// key: 1,
// attributes: [
// 'email' => 'not-an-email',
// 'age' => '17',
// 'name' => 'John Doe',
// ],
// reason: 'Validation failed: [error] The email must be a valid email address (email) [error] Must be at least 18 (age)'
// }
// If onFail = 'fail', a ModifyErrorsException is thrown instead.
use Nyholm\Psr7\Factory\Psr17Factory;
use Tobento\Service\ReadWrite\Modifier\Modifiers;
use Tobento\Service\ReadWrite\Processor\Processor;
use Tobento\Service\ReadWrite\Reader;
use Tobento\Service\ReadWrite\Writer;
// Reader and writer
$reader = new Reader\CsvStream(
stream: new Psr17Factory()->createStreamFromFile('/data/input.csv'),
);
$writer = new Writer\CsvResource(
resource: new Writer\Resource\LocalFile('/data/output.csv'),
);
// Create modifiers (empty for this example)
$modifiers = new Modifiers();
// Create processor
$processor = new Processor(
modifiers: $modifiers,
resultHandler: null,
);
// Process rows starting from the reader's current offset
$result = $processor->process(
reader: $reader,
writer: $writer,
offset: $reader->currentOffset(),
limit: null,
);
// Inspect result
echo $result->successfulRows(); // e.g. 42
use Nyholm\Psr7\Factory\Psr17Factory;
use Tobento\Service\ReadWrite\Modifier\Modifiers;
use Tobento\Service\ReadWrite\Processor\TimeBudgetProcessor;
use Tobento\Service\ReadWrite\Reader;
use Tobento\Service\ReadWrite\Writer;
// Reader and writer
$reader = new Reader\CsvStream(
stream: new Psr17Factory()->createStreamFromFile('/data/input.csv'),
);
$writer = new Writer\CsvResource(
resource: new Writer\Resource\LocalFile('/data/output.csv'),
);
// Create modifiers (empty for this example)
$modifiers = new Modifiers();
// Create processor with a 20-second time budget
$processor = new TimeBudgetProcessor(
timeBudget: 20,
modifiers: $modifiers,
resultHandler: null,
);
// Process rows starting from the reader's current offset
$result = $processor->process(
reader: $reader,
writer: $writer,
offset: $reader->currentOffset(),
limit: null,
);
// Inspect result
echo $result->successfulRows();
use Psr\Log\LoggerInterface;
use Tobento\Service\ReadWrite\ResultHandlerInterface;
use Tobento\Service\ReadWrite\ResultInterface;
use Tobento\Service\ReadWrite\RowInterface;
use Tobento\Service\ReadWrite\Row\SkippableInterface;
use Throwable;
class LoggingResultHandler implements ResultHandlerInterface
{
public function __construct(
private LoggerInterface $logger,
) {}
public function handleRowSuccess(RowInterface $row): void
{
$this->logger->info('Row processed successfully', [
'row' => $row,
]);
}
public function handleRowSkip(SkippableInterface $row): void
{
$this->logger->notice('Row skipped', [
'row' => $row,
]);
}
public function handleRowFailure(RowInterface $row, Throwable $exception): void
{
$this->logger->error('Row processing failed', [
'row' => $row,
'exception' => $exception,
]);
}
public function handleResult(ResultInterface $result): void
{
$this->logger->info('Processing finished', [
'successful' => $result->successfulRows(),
'failed' => $result->failedRows(),
'skipped' => $result->skippedRows(),
'runtime' => $result->runtimeInSeconds(),
]);
}
}
$result = $processor->process($reader, $writer);
echo $result->successfulRows(); // e.g. 42
echo $result->failedRows(); // e.g. 3
echo $result->runtimeInSeconds(); // e.g. 1.52