Skip to content

Commit

Permalink
Add reader for txt files with fixed columns size (#22)
Browse files Browse the repository at this point in the history
* Add reader for txt files with fixed columns size

* Updated fixed size file reader with latest changed from 0.x

* Fixed static analyis and checkstyle

* Fixed static analysis
  • Loading branch information
yann-eugone authored Aug 23, 2021
1 parent d9df3a9 commit f546a33
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 0 deletions.
106 changes: 106 additions & 0 deletions src/batch/src/Job/Item/Reader/Filesystem/FixedColumnSizeFileReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

namespace Yokai\Batch\Job\Item\Reader\Filesystem;

use Generator;
use Yokai\Batch\Exception\RuntimeException;
use Yokai\Batch\Exception\UnexpectedValueException;
use Yokai\Batch\Job\Item\ItemReaderInterface;
use Yokai\Batch\Job\JobExecutionAwareInterface;
use Yokai\Batch\Job\JobExecutionAwareTrait;
use Yokai\Batch\Job\Parameters\JobParameterAccessorInterface;

final class FixedColumnSizeFileReader implements
ItemReaderInterface,
JobExecutionAwareInterface
{
use JobExecutionAwareTrait;

public const HEADERS_MODE_SKIP = 'skip';
public const HEADERS_MODE_COMBINE = 'combine';
public const HEADERS_MODE_NONE = 'none';
private const AVAILABLE_HEADERS_MODES = [
self::HEADERS_MODE_SKIP,
self::HEADERS_MODE_COMBINE,
self::HEADERS_MODE_NONE,
];

/**
* @var int[]
*/
private array $columns;

/**
* @var string
*/
private string $headersMode;

/**
* @var JobParameterAccessorInterface
*/
private JobParameterAccessorInterface $filePath;

/**
* @param int[] $columns
*/
public function __construct(
array $columns,
JobParameterAccessorInterface $filePath,
string $headersMode = self::HEADERS_MODE_NONE
) {
if (!\in_array($headersMode, self::AVAILABLE_HEADERS_MODES, true)) {
throw UnexpectedValueException::enum(self::AVAILABLE_HEADERS_MODES, $headersMode, 'Invalid header mode.');
}

$this->columns = $columns;
$this->headersMode = $headersMode;
$this->filePath = $filePath;
}

/**
* @inheritdoc
* @phpstan-return Generator<array<mixed>>
*/
public function read(): Generator
{
$path = (string)$this->filePath->get($this->jobExecution);
$handle = @\fopen($path, 'r');
if ($handle === false) {
throw new RuntimeException(\sprintf('Cannot read %s.', $path));
}

$headers = \array_keys($this->columns);

$index = -1;

while (($line = \fgets($handle)) !== false) {
$index++;

$start = 0;
$row = [];
foreach ($this->columns as $size) {
$row[] = \trim(\mb_substr($line, $start, $size));
$start += $size;
}

if ($index === 0) {
if ($this->headersMode === self::HEADERS_MODE_COMBINE) {
$headers = $row;
}
if (\in_array($this->headersMode, [self::HEADERS_MODE_COMBINE, self::HEADERS_MODE_SKIP], true)) {
continue;
}
}

if (\is_array($headers)) {
$row = \array_combine($headers, $row);
}

yield $row;
}

\fclose($handle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Yokai\Batch\Tests\Job\Item\Reader\Filesystem;

use Generator;
use PHPUnit\Framework\TestCase;
use Yokai\Batch\Exception\RuntimeException;
use Yokai\Batch\Exception\UnexpectedValueException;
use Yokai\Batch\Job\Item\Reader\Filesystem\FixedColumnSizeFileReader;
use Yokai\Batch\Job\Parameters\StaticValueParameterAccessor;
use Yokai\Batch\JobExecution;

class FixedColumnSizeFileReaderTest extends TestCase
{
/**
* @dataProvider config
*/
public function test(array $columns, string $headersMode, array $expected): void
{
$execution = JobExecution::createRoot('123456', 'testing');
$reader = new FixedColumnSizeFileReader(
$columns,
new StaticValueParameterAccessor(__DIR__ . '/fixtures/fixed-column-size.txt'),
$headersMode
);
$reader->setJobExecution($execution);
self::assertSame($expected, \iterator_to_array($reader->read()));
}

public function testInvalidHeaderMode(): void
{
$this->expectException(UnexpectedValueException::class);
new FixedColumnSizeFileReader([10, 10], new StaticValueParameterAccessor(null), 'wrong header mode');
}

public function testFileNotFound(): void
{
$this->expectException(RuntimeException::class);
$execution = JobExecution::createRoot('123456', 'testing');
$reader = new FixedColumnSizeFileReader(
[10, 10],
new StaticValueParameterAccessor(__DIR__ . '/fixtures/unknown-file.ext')
);
$reader->setJobExecution($execution);
\iterator_to_array($reader->read());
}

public function config(): Generator
{
$columnsWithoutNames = [10, 9, 8, -1];
$columnsWithNames = ['firstName' => 10, 'lastName' => 9, 'country' => 8, 'city' => -1];

yield [
$columnsWithoutNames,
FixedColumnSizeFileReader::HEADERS_MODE_COMBINE,
[
['firstName' => 'John', 'lastName' => 'Doe', 'country' => 'USA', 'city' => 'Washington'],
['firstName' => 'Jane', 'lastName' => 'Doe', 'country' => 'USA', 'city' => 'Seattle'],
['firstName' => 'Jack', 'lastName' => 'Doe', 'country' => 'USA', 'city' => 'San Francisco'],
],
];
yield [
$columnsWithNames,
FixedColumnSizeFileReader::HEADERS_MODE_SKIP,
[
['firstName' => 'John', 'lastName' => 'Doe', 'country' => 'USA', 'city' => 'Washington'],
['firstName' => 'Jane', 'lastName' => 'Doe', 'country' => 'USA', 'city' => 'Seattle'],
['firstName' => 'Jack', 'lastName' => 'Doe', 'country' => 'USA', 'city' => 'San Francisco'],
],
];
yield [
$columnsWithoutNames,
FixedColumnSizeFileReader::HEADERS_MODE_NONE,
[
['firstName', 'lastName', 'country', 'city'],
['John', 'Doe', 'USA', 'Washington'],
['Jane', 'Doe', 'USA', 'Seattle'],
['Jack', 'Doe', 'USA', 'San Francisco'],
],
];
yield [
$columnsWithoutNames,
FixedColumnSizeFileReader::HEADERS_MODE_SKIP,
[
['John', 'Doe', 'USA', 'Washington'],
['Jane', 'Doe', 'USA', 'Seattle'],
['Jack', 'Doe', 'USA', 'San Francisco'],
],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
firstName lastName country city
John Doe USA Washington
Jane Doe USA Seattle
Jack Doe USA San Francisco

0 comments on commit f546a33

Please sign in to comment.