From 9439d3a6a3d9ff4d93f3738a8d56dbd3819c8397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Wed, 18 May 2022 10:43:07 +0200 Subject: [PATCH 1/2] Add an item writer that will transform items before delegating to another writer --- src/batch/docs/domain/item-job/item-writer.md | 2 + .../Job/Item/Writer/TransformingWriter.php | 76 +++++++++++++++++++ .../Item/Writer/TransformingWriterTest.php | 69 +++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 src/batch/src/Job/Item/Writer/TransformingWriter.php create mode 100644 src/batch/tests/Job/Item/Writer/TransformingWriterTest.php diff --git a/src/batch/docs/domain/item-job/item-writer.md b/src/batch/docs/domain/item-job/item-writer.md index 7b32c3af..e2e7e955 100644 --- a/src/batch/docs/domain/item-job/item-writer.md +++ b/src/batch/docs/domain/item-job/item-writer.md @@ -17,6 +17,8 @@ It can be any class implementing [ItemWriterInterface](../../../src/Job/Item/Ite route writing to different writer based on your logic. - [SummaryWriter](../../../src/Job/Item/Writer/SummaryWriter.php): write items to a job summary value. +- [TransformingWriter](../../../src/Job/Item/Writer/TransformingWriter.php): + perform items transformation before delegating to another writer. **Item writers from bridges:** - [DoctrineDBALInsertWriter (`doctrine/dbal`)](https://github.com/yokai-php/batch-doctrine-dbal/blob/0.x/src/DoctrineDBALInsertWriter.php): diff --git a/src/batch/src/Job/Item/Writer/TransformingWriter.php b/src/batch/src/Job/Item/Writer/TransformingWriter.php new file mode 100644 index 00000000..55ae010f --- /dev/null +++ b/src/batch/src/Job/Item/Writer/TransformingWriter.php @@ -0,0 +1,76 @@ + $item) { + try { + $transformedItems[] = $this->processor->process($item); + } catch (SkipItemException $exception) { + $this->jobExecution->getLogger()->debug( + \sprintf('Skipping item in writer transformation %s.', $index), + $exception->getContext() + ['item' => $exception->getItem()] + ); + + $cause = $exception->getCause(); + if ($cause) { + $cause->report($this->jobExecution, $index, $exception->getItem()); + } + + continue; + } + } + + if (count($transformedItems) > 0) { + $this->writer->write($transformedItems); + } + } + + public function initialize(): void + { + $this->configureElementJobContext($this->processor, $this->jobExecution); + $this->initializeElement($this->processor); + $this->configureElementJobContext($this->writer, $this->jobExecution); + $this->initializeElement($this->writer); + } + + public function flush(): void + { + $this->flushElement($this->processor); + $this->flushElement($this->writer); + } +} diff --git a/src/batch/tests/Job/Item/Writer/TransformingWriterTest.php b/src/batch/tests/Job/Item/Writer/TransformingWriterTest.php new file mode 100644 index 00000000..53738dc8 --- /dev/null +++ b/src/batch/tests/Job/Item/Writer/TransformingWriterTest.php @@ -0,0 +1,69 @@ + \strtoupper($string))), + $debugWriter = new TestDebugWriter($innerWriter = new InMemoryWriter()) + ); + + $writer->setJobExecution(JobExecution::createRoot('123', 'test.transforming_writer')); + $writer->initialize(); + $writer->write(['one', 'two', 'three']); + $writer->flush(); + + $debugProcessor->assertWasConfigured(); + $debugProcessor->assertWasUsed(); + $debugWriter->assertWasConfigured(); + $debugWriter->assertWasUsed(); + self::assertSame(['ONE', 'TWO', 'THREE'], $innerWriter->getItems()); + } + + public function testSkipItems(): void + { + $writer = new TransformingWriter( + $debugProcessor = new TestDebugProcessor( + new CallbackProcessor( + fn ($item) => throw SkipItemException::withWarning($item, 'Skipped for test purpose') + ) + ), + $debugWriter = new TestDebugWriter($innerWriter = new InMemoryWriter()) + ); + + $writer->setJobExecution($execution = JobExecution::createRoot('123', 'test.transforming_writer')); + $writer->initialize(); + $writer->write(['one', 'two', 'three']); + $writer->flush(); + + $debugProcessor->assertWasConfigured(); + $debugProcessor->assertWasUsed(); + $debugWriter->assertWasConfigured(); + $debugWriter->assertWasNotUsed(true, true); + self::assertSame([], $innerWriter->getItems()); + self::assertCount(3, $warnings = $execution->getWarnings()); + self::assertSame('Skipped for test purpose', $warnings[0]->getMessage()); + self::assertSame(['itemIndex' => 0, 'item' => 'one'], $warnings[0]->getContext()); + self::assertSame('Skipped for test purpose', $warnings[1]->getMessage()); + self::assertSame(['itemIndex' => 1, 'item' => 'two'], $warnings[1]->getContext()); + self::assertSame('Skipped for test purpose', $warnings[2]->getMessage()); + self::assertSame(['itemIndex' => 2, 'item' => 'three'], $warnings[2]->getContext()); + self::assertStringContainsString('Skipping item in writer transformation 0.', (string)$execution->getLogs()); + self::assertStringContainsString('Skipping item in writer transformation 1.', (string)$execution->getLogs()); + self::assertStringContainsString('Skipping item in writer transformation 2.', (string)$execution->getLogs()); + } +} From 08af435996ae6328189ae382801b8cd07ccdcee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Wed, 18 May 2022 10:55:35 +0200 Subject: [PATCH 2/2] Fixed potential mixed index --- .../src/Job/Item/Writer/TransformingWriter.php | 5 +++++ .../Job/Item/Writer/TransformingWriterTest.php | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/batch/src/Job/Item/Writer/TransformingWriter.php b/src/batch/src/Job/Item/Writer/TransformingWriter.php index 55ae010f..22fc5427 100644 --- a/src/batch/src/Job/Item/Writer/TransformingWriter.php +++ b/src/batch/src/Job/Item/Writer/TransformingWriter.php @@ -4,6 +4,7 @@ namespace Yokai\Batch\Job\Item\Writer; +use Yokai\Batch\Exception\UnexpectedValueException; use Yokai\Batch\Job\Item\ElementConfiguratorTrait; use Yokai\Batch\Job\Item\Exception\SkipItemException; use Yokai\Batch\Job\Item\FlushableInterface; @@ -38,6 +39,10 @@ public function write(iterable $items): void $transformedItems = []; foreach ($items as $index => $item) { + if (!\is_string($index) && !\is_int($index)) { + throw UnexpectedValueException::type('string|int', $index); + } + try { $transformedItems[] = $this->processor->process($item); } catch (SkipItemException $exception) { diff --git a/src/batch/tests/Job/Item/Writer/TransformingWriterTest.php b/src/batch/tests/Job/Item/Writer/TransformingWriterTest.php index 53738dc8..0f7e8aac 100644 --- a/src/batch/tests/Job/Item/Writer/TransformingWriterTest.php +++ b/src/batch/tests/Job/Item/Writer/TransformingWriterTest.php @@ -5,8 +5,10 @@ namespace Yokai\Batch\Tests\Job\Item\Writer; use PHPUnit\Framework\TestCase; +use Yokai\Batch\Exception\UnexpectedValueException; use Yokai\Batch\Job\Item\Exception\SkipItemException; use Yokai\Batch\Job\Item\Processor\CallbackProcessor; +use Yokai\Batch\Job\Item\Processor\NullProcessor; use Yokai\Batch\Job\Item\Writer\TransformingWriter; use Yokai\Batch\JobExecution; use Yokai\Batch\Test\Job\Item\Processor\TestDebugProcessor; @@ -66,4 +68,17 @@ public function testSkipItems(): void self::assertStringContainsString('Skipping item in writer transformation 1.', (string)$execution->getLogs()); self::assertStringContainsString('Skipping item in writer transformation 2.', (string)$execution->getLogs()); } + + public function testInvalidIndexType(): void + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Expecting argument to be string|int, but got null.'); + + $writer = new TransformingWriter(new NullProcessor(), new InMemoryWriter()); + $generator = function () { + yield null => null; + }; + + $writer->write($generator()); + } }