diff --git a/src/batch/src/Job/Item/ItemJob.php b/src/batch/src/Job/Item/ItemJob.php index a37b6cfa..36eb9070 100644 --- a/src/batch/src/Job/Item/ItemJob.php +++ b/src/batch/src/Job/Item/ItemJob.php @@ -77,9 +77,7 @@ final protected function doExecute(JobExecution $jobExecution): void $writeCount = 0; $itemsToWrite = []; - $lineNumber = 1; - foreach ($this->reader->read() as $readItem) { - $lineNumber++; + foreach ($this->reader->read() as $readIndex => $readItem) { $summary->increment('read'); try { @@ -87,7 +85,7 @@ final protected function doExecute(JobExecution $jobExecution): void } catch (InvalidItemException $exception) { $summary->increment('invalid'); $jobExecution->addWarning( - new Warning($exception->getMessage(), $exception->getParameters(), ['line_number' => $lineNumber]) + new Warning($exception->getMessage(), $exception->getParameters(), ['itemIndex' => $readIndex]) ); continue; diff --git a/src/batch/src/Job/Item/Reader/IndexWithReader.php b/src/batch/src/Job/Item/Reader/IndexWithReader.php new file mode 100644 index 00000000..6d9ea199 --- /dev/null +++ b/src/batch/src/Job/Item/Reader/IndexWithReader.php @@ -0,0 +1,99 @@ +reader = $reader; + $this->extractItemIndex = $extractItemIndex; + } + + /** + * Uses item array value as the item index. + * + * Example, IndexWithReader::withArrayKey(..., 'name') + * will use 'name' array index of each read item as the item index. + */ + public static function withArrayKey(ItemReaderInterface $reader, string $key): self + { + return new self($reader, fn(array $item) => $item[$key]); + } + + /** + * Uses object property value as the item index. + * + * Example, IndexWithReader::withProperty(..., 'name') + * will use 'name' object property of each read item as the item index. + */ + public static function withProperty(ItemReaderInterface $reader, string $property): self + { + return new self($reader, fn(object $item) => $item->$property); + } + + /** + * Uses object method return value as the item index. + * + * Example, IndexWithReader::withProperty(..., 'getName') + * will call 'getName()' method of each read item and uses the result as the item index. + */ + public static function withGetter(ItemReaderInterface $reader, string $getter): self + { + return new self($reader, fn(object $item) => $item->$getter()); + } + + /** + * @inheritdoc + */ + public function initialize(): void + { + $this->configureElementJobContext($this->reader, $this->jobExecution); + $this->initializeElement($this->reader); + } + + /** + * @inheritdoc + */ + public function read(): iterable + { + foreach ($this->reader->read() as $item) { + yield ($this->extractItemIndex)($item) => $item; + } + } + + /** + * @inheritdoc + */ + public function flush(): void + { + $this->flushElement($this->reader); + } +} diff --git a/src/batch/tests/Job/Item/ItemJobTest.php b/src/batch/tests/Job/Item/ItemJobTest.php index 140fe32a..1d2195d4 100644 --- a/src/batch/tests/Job/Item/ItemJobTest.php +++ b/src/batch/tests/Job/Item/ItemJobTest.php @@ -86,6 +86,14 @@ public function testExecute(): void self::assertSame(3, $jobExecution->getSummary()->get('invalid'), '3 items were invalid'); self::assertSame(9, $jobExecution->getSummary()->get('write'), '9 items were write'); + $warnings = $jobExecution->getWarnings(); + self::assertCount(3, $warnings); + foreach ([[0, 9, 10], [1, 10, 11], [2, 11, 12]] as [$warningIdx, $itemIdx, $paramValue]) { + self::assertSame('Item is greater than 9 got {value}', $warnings[$warningIdx]->getMessage()); + self::assertSame(['{value}' => $paramValue], $warnings[$warningIdx]->getParameters()); + self::assertSame(['itemIndex' => $itemIdx], $warnings[$warningIdx]->getContext()); + } + $expectedLogs = <<setJobExecution(JobExecution::createRoot('123456', 'testing')); + $reader->initialize(); + + $actual = []; + foreach ($reader->read() as $index => $item) { + $actual[$index] = $item; + } + + $reader->flush(); + + self::assertSame($expected, $actual); + } + + public function provider(): Generator + { + $john = ['name' => 'John', 'location' => 'Washington']; + $marie = ['name' => 'Marie', 'location' => 'London']; + yield 'Index with array key' => [ + fn() => IndexWithReader::withArrayKey( + new StaticIterableReader([$john, $marie]), + 'name' + ), + ['John' => $john, 'Marie' => $marie], + ]; + + $john = (object)$john; + $marie = (object)$marie; + yield 'Index with object property' => [ + fn() => IndexWithReader::withProperty( + new StaticIterableReader([$john, $marie]), + 'name' + ), + ['John' => $john, 'Marie' => $marie], + ]; + + $three = new ArrayIterator([1, 2, 3]); + $six = new ArrayIterator([1, 2, 3, 4, 5, 6]); + yield 'Index with object method' => [ + fn() => IndexWithReader::withGetter( + new StaticIterableReader([$three, $six]), + 'count' + ), + [3 => $three, 6 => $six], + ]; + + yield 'Index with arbitrary closure' => [ + fn() => new IndexWithReader( + new StaticIterableReader([1, 2, 3]), + fn(int $value) => $value * $value + ), + [1 => 1, 4 => 2, 9 => 3], + ]; + } +}