-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
225 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Extractor; | ||
|
||
use BenTools\ETL\EtlState; | ||
use React\Stream\ReadableStreamInterface; | ||
|
||
final readonly class ReactStreamExtractor implements ExtractorInterface | ||
{ | ||
public function __construct( | ||
public ?ReadableStreamInterface $stream = null, | ||
) { | ||
} | ||
|
||
public function extract(EtlState $state): ReadableStreamInterface | ||
{ | ||
return $state->source ?? $this->stream; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Processor; | ||
|
||
use BenTools\ETL\EtlExecutor; | ||
use BenTools\ETL\EtlState; | ||
use BenTools\ETL\Exception\ExtractException; | ||
use BenTools\ETL\Exception\SkipRequest; | ||
use BenTools\ETL\Exception\StopRequest; | ||
use BenTools\ETL\Extractor\ReactStreamExtractor; | ||
use BenTools\ETL\Recipe\Recipe; | ||
use React\EventLoop\Loop; | ||
use React\Stream\ReadableStreamInterface; | ||
use Throwable; | ||
|
||
use function is_string; | ||
use function trim; | ||
|
||
/** | ||
* @experimental | ||
*/ | ||
final class ReactStreamProcessor extends Recipe implements ProcessorInterface | ||
{ | ||
public function supports(mixed $extracted): bool | ||
{ | ||
return $extracted instanceof ReadableStreamInterface; | ||
} | ||
|
||
/** | ||
* @param ReadableStreamInterface $stream | ||
*/ | ||
public function process(EtlExecutor $executor, EtlState $state, mixed $stream): EtlState | ||
{ | ||
$key = -1; | ||
$stream->on('data', function (mixed $item) use ($executor, &$key, $state, $stream) { | ||
if (is_string($item)) { | ||
$item = trim($item); | ||
} | ||
try { | ||
$executor->processItem($item, ++$key, $state); | ||
} catch (SkipRequest) { | ||
} catch (StopRequest) { | ||
$stream->close(); | ||
} catch (Throwable $e) { | ||
$stream->close(); | ||
ExtractException::emit($executor, $e, $state); | ||
} | ||
}); | ||
|
||
Loop::run(); | ||
|
||
return $state->getLastVersion(); | ||
} | ||
|
||
public function decorate(EtlExecutor $executor): EtlExecutor | ||
{ | ||
return $executor->extractFrom(new ReactStreamExtractor())->withProcessor($this); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Tests\Behavior; | ||
|
||
use BenTools\ETL\EventDispatcher\Event\ExtractEvent; | ||
use BenTools\ETL\Exception\ExtractException; | ||
use React\EventLoop\Loop; | ||
use React\Stream\ReadableResourceStream; | ||
use RuntimeException; | ||
|
||
use function BenTools\ETL\useReact; | ||
use function expect; | ||
use function fopen; | ||
|
||
it('processes React streams', function () { | ||
// Given | ||
$stream = new ReadableResourceStream(fopen('php://temp', 'rb')); | ||
Loop::futureTick(fn () => $stream->emit('data', ['hello'])); | ||
Loop::futureTick(fn () => $stream->emit('data', ['world'])); | ||
$executor = useReact(); | ||
|
||
// When | ||
$state = $executor->process($stream); | ||
|
||
// Then | ||
expect($state->output)->toBe(['hello', 'world']); | ||
}); | ||
|
||
it('can skip items and stop the workflow', function () { | ||
// Given | ||
$stream = new ReadableResourceStream(fopen('php://temp', 'rb')); | ||
$fruits = ['banana', 'apple', 'strawberry', 'raspberry', 'peach']; | ||
foreach ($fruits as $fruit) { | ||
Loop::futureTick(fn () => $stream->emit('data', [$fruit])); | ||
} | ||
$executor = useReact() | ||
->onExtract(function (ExtractEvent $event) { | ||
match ($event->item) { | ||
'apple' => $event->state->skip(), | ||
'peach' => $event->state->stop(), | ||
default => null, | ||
}; | ||
}) | ||
; | ||
|
||
// When | ||
$state = $executor->process($stream); | ||
|
||
// Then | ||
expect($state->output)->toBe(['banana', 'strawberry', 'raspberry']); | ||
}); | ||
|
||
it('throws ExtractExceptions', function () { | ||
// Given | ||
$stream = new ReadableResourceStream(fopen('php://temp', 'rb')); | ||
Loop::futureTick(fn () => $stream->emit('data', ['hello'])); | ||
$executor = useReact()->onExtract(fn () => throw new RuntimeException()); | ||
|
||
// When | ||
$executor->process($stream); | ||
})->throws(ExtractException::class); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace BenTools\ETL\Tests\Unit\Extractor; | ||
|
||
use BenTools\ETL\EtlState; | ||
use BenTools\ETL\Extractor\ReactStreamExtractor; | ||
use Mockery; | ||
use React\Stream\ReadableStreamInterface; | ||
|
||
use function expect; | ||
|
||
it('supports any readable stream', function () { | ||
$a = Mockery::mock(ReadableStreamInterface::class); | ||
$b = Mockery::mock(ReadableStreamInterface::class); | ||
|
||
$extractor = new ReactStreamExtractor($a); | ||
expect($extractor->extract(new EtlState(source: $b)))->toBe($b) | ||
->and($extractor->extract(new EtlState()))->toBe($a); | ||
}); |