From eb16e95bf3227b66f594227e97b904aa2e023232 Mon Sep 17 00:00:00 2001 From: Beno!t POLASZEK Date: Sat, 11 Nov 2023 08:40:29 +0100 Subject: [PATCH] Feat: Instantiators (#28) --- README.md | 21 +++++++++++++++++ src/Internal/EtlBuilderTrait.php | 11 +++++---- src/functions.php | 25 +++++++++++++++++++++ tests/Behavior/LoadExceptionTest.php | 19 ++++++++-------- tests/Behavior/TransformExceptionTest.php | 19 ++++++++-------- tests/Unit/Extractor/ChainExtractorTest.php | 12 +++++----- tests/Unit/Recipe/RecipeTest.php | 3 ++- 7 files changed, 79 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1f0879a..b734b63 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,27 @@ $progressBar = $output->createProgressBar(); $executor = (new EtlExecutor())->withRecipe(new ProgressBarRecipe($progressBar)); ``` +Instantiators +------------- + +You can use the `extractFrom()`, `transformWith()`, `loadInto()` and `withRecipe()` functions +to instantiate an `EtlExecutor`. + +Example: + +```php +use BenTools\ETL\Recipe\LoggerRecipe; +use Monolog\Logger; + +use function BenTools\ETL\withRecipe; + +$logger = new Logger(); +$report = withRecipe(new LoggerRecipe($logger)) + ->transformWith(fn ($value) => strtoupper($value)) + ->process(['foo', 'bar']); +``` + + Contribute ---------- diff --git a/src/Internal/EtlBuilderTrait.php b/src/Internal/EtlBuilderTrait.php index 09ba7ff..e60899d 100644 --- a/src/Internal/EtlBuilderTrait.php +++ b/src/Internal/EtlBuilderTrait.php @@ -86,12 +86,15 @@ public function withOptions(EtlConfiguration $configuration): self return $this->cloneWith(['options' => $configuration]); } - public function withRecipe(Recipe|callable $recipe): self + public function withRecipe(Recipe|callable $recipe, Recipe|callable ...$recipes): self { - if (!$recipe instanceof Recipe) { - $recipe = Recipe::fromCallable($recipe); + foreach ([$recipe, ...$recipes] as $_recipe) { + if (!$_recipe instanceof Recipe) { + $_recipe = Recipe::fromCallable($_recipe); + } + $executor = $_recipe->decorate($this); } - return $recipe->decorate($this); + return $executor; } } diff --git a/src/functions.php b/src/functions.php index 2192ca9..870f2da 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,11 +4,16 @@ namespace BenTools\ETL; +use BenTools\ETL\Extractor\ExtractorInterface; use BenTools\ETL\Internal\Ref; +use BenTools\ETL\Loader\LoaderInterface; +use BenTools\ETL\Recipe\Recipe; +use BenTools\ETL\Transformer\TransformerInterface; use function array_fill_keys; use function array_intersect_key; use function array_replace; +use function func_get_args; /** * @internal @@ -54,3 +59,23 @@ function unref(Ref $ref): mixed { return $ref->value; } + +function extractFrom(ExtractorInterface|callable $extractor, ExtractorInterface|callable ...$extractors): EtlExecutor +{ + return (new EtlExecutor())->extractFrom(...func_get_args()); +} + +function transformWith(TransformerInterface|callable $transformer, TransformerInterface|callable ...$transformers): EtlExecutor +{ + return (new EtlExecutor())->transformWith(...func_get_args()); +} + +function loadInto(LoaderInterface|callable $loader, LoaderInterface|callable ...$loaders): EtlExecutor +{ + return (new EtlExecutor())->loadInto(...func_get_args()); +} + +function withRecipe(Recipe|callable $recipe): EtlExecutor +{ + return (new EtlExecutor())->withRecipe(...func_get_args()); +} diff --git a/tests/Behavior/LoadExceptionTest.php b/tests/Behavior/LoadExceptionTest.php index a9f5076..0f778de 100644 --- a/tests/Behavior/LoadExceptionTest.php +++ b/tests/Behavior/LoadExceptionTest.php @@ -4,16 +4,16 @@ namespace BenTools\ETL\Tests\Behavior; -use BenTools\ETL\EtlExecutor; use BenTools\ETL\Exception\LoadException; use RuntimeException; +use function BenTools\ETL\loadInto; use function expect; use function it; it('throws a load exception when it is thrown from the extractor', function () { $items = ['foo', 'bar', 'baz']; - $executor = (new EtlExecutor())->loadInto(function (mixed $value) { + $executor = loadInto(function (mixed $value) { if ('bar' === $value) { throw new LoadException('Cannot load `bar`.'); } @@ -23,7 +23,7 @@ it('throws a load exception when some other exception is thrown', function () { $items = ['foo', 'bar', 'baz']; - $executor = (new EtlExecutor())->loadInto(function (mixed $value) { + $executor = loadInto(function (mixed $value) { if ('bar' === $value) { throw new RuntimeException('Cannot load `bar`.'); } @@ -34,13 +34,12 @@ it('has stopped processing items, but has loaded the previous ones', function () { $items = ['foo', 'bar', 'baz']; $loadedItems = []; - $executor = (new EtlExecutor()) - ->loadInto(function (mixed $value) use (&$loadedItems) { - if ('bar' === $value) { - throw new LoadException('Cannot load `bar`.'); - } - $loadedItems[] = $value; - }) + $executor = loadInto(function (mixed $value) use (&$loadedItems) { + if ('bar' === $value) { + throw new LoadException('Cannot load `bar`.'); + } + $loadedItems[] = $value; + }) ; try { $executor->process($items); diff --git a/tests/Behavior/TransformExceptionTest.php b/tests/Behavior/TransformExceptionTest.php index abb956a..b1c3b6b 100644 --- a/tests/Behavior/TransformExceptionTest.php +++ b/tests/Behavior/TransformExceptionTest.php @@ -4,16 +4,16 @@ namespace BenTools\ETL\Tests\Behavior; -use BenTools\ETL\EtlExecutor; use BenTools\ETL\Exception\TransformException; use RuntimeException; +use function BenTools\ETL\transformWith; use function expect; use function it; it('throws an extract exception when it is thrown from the extractor', function () { $items = ['foo', 'bar', 'baz']; - $executor = (new EtlExecutor())->transformWith(function (mixed $value) { + $executor = transformWith(function (mixed $value) { if ('bar' === $value) { throw new TransformException('Cannot transform `bar`.'); } @@ -24,7 +24,7 @@ it('throws a transform exception when some other exception is thrown', function () { $items = ['foo', 'bar', 'baz']; - $executor = (new EtlExecutor())->transformWith(function (mixed $value) { + $executor = transformWith(function (mixed $value) { if ('bar' === $value) { throw new RuntimeException('Cannot transform `bar`.'); } @@ -36,13 +36,12 @@ it('has stopped processing items, but has loaded the previous ones', function () { $items = ['foo', 'bar', 'baz']; $loadedItems = []; - $executor = (new EtlExecutor()) - ->transformWith(function (mixed $value) { - if ('bar' === $value) { - throw new TransformException('Cannot transform `bar`.'); - } - yield $value; - }) + $executor = transformWith(function (mixed $value) { + if ('bar' === $value) { + throw new TransformException('Cannot transform `bar`.'); + } + yield $value; + }) ->loadInto(function (mixed $value) use (&$loadedItems) { $loadedItems[] = $value; }) diff --git a/tests/Unit/Extractor/ChainExtractorTest.php b/tests/Unit/Extractor/ChainExtractorTest.php index 2921725..2f23be3 100644 --- a/tests/Unit/Extractor/ChainExtractorTest.php +++ b/tests/Unit/Extractor/ChainExtractorTest.php @@ -7,6 +7,7 @@ use BenTools\ETL\EtlExecutor; use BenTools\ETL\Extractor\ChainExtractor; +use function BenTools\ETL\extractFrom; use function expect; it('chains extractors', function () { @@ -26,12 +27,11 @@ it('silently chains extractors', function () { // Given - $executor = (new EtlExecutor()) - ->extractFrom( - fn () => 'banana', - fn () => yield from ['apple', 'strawberry'], - fn () => ['raspberry', 'peach'] - ); + $executor = extractFrom( + fn () => 'banana', + fn () => yield from ['apple', 'strawberry'], + fn () => ['raspberry', 'peach'] + ); // When $report = $executor->process(); diff --git a/tests/Unit/Recipe/RecipeTest.php b/tests/Unit/Recipe/RecipeTest.php index b1f3bad..f740f9b 100644 --- a/tests/Unit/Recipe/RecipeTest.php +++ b/tests/Unit/Recipe/RecipeTest.php @@ -6,13 +6,14 @@ use BenTools\ETL\EtlExecutor; +use function BenTools\ETL\withRecipe; use function expect; it('uses a recipe', function () { // Given $hasReceivedInitEvent = false; $hasReceivedEndEvent = false; - $executor = (new EtlExecutor())->withRecipe( + $executor = withRecipe( function (EtlExecutor $executor) use (&$hasReceivedInitEvent, &$hasReceivedEndEvent) { return $executor ->onInit(function () use (&$hasReceivedInitEvent) {