Skip to content

Commit

Permalink
Fix: Clones not being accurate (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpolaszek authored Nov 6, 2023
1 parent 6b79bf3 commit edb0366
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 33 deletions.
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
"autoload": {
"psr-4": {
"Bentools\\ETL\\": "src/"
}
},
"files": [
"src/functions.php"
]
},
"autoload-dev": {
"psr-4": {
Expand Down
10 changes: 5 additions & 5 deletions src/EtlExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
use function gc_collect_cycles;
use function is_countable;

final readonly class EtlExecutor
final class EtlExecutor
{
use ClonableTrait;

Expand All @@ -49,10 +49,10 @@
private EventDispatcherInterface $eventDispatcher;

public function __construct(
private ExtractorInterface $extractor = new IterableExtractor(),
private TransformerInterface $transformer = new NullTransformer(),
private LoaderInterface $loader = new InMemoryLoader(),
private EtlConfiguration $options = new EtlConfiguration(),
private readonly ExtractorInterface $extractor = new IterableExtractor(),
private readonly TransformerInterface $transformer = new NullTransformer(),
private readonly LoaderInterface $loader = new InMemoryLoader(),
private readonly EtlConfiguration $options = new EtlConfiguration(),
) {
$this->listenerProvider = new PrioritizedListenerProvider();
$this->eventDispatcher = new EventDispatcher($this->listenerProvider);
Expand Down
10 changes: 5 additions & 5 deletions src/EtlState.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public function getDuration(): float
*/
public function withUpdatedItemKey(mixed $key): self
{
return $this->clone([
return $this->cloneWith([
'currentItemKey' => $key,
'currentItemIndex' => $this->currentItemIndex + 1,
'nbExtractedItems' => $this->nbExtractedItems + 1,
Expand All @@ -92,7 +92,7 @@ public function withUpdatedItemKey(mixed $key): self
*/
public function withIncrementedNbLoadedItems(): self
{
return $this->clone([
return $this->cloneWith([
'nbLoadedItems' => $this->nbLoadedItems + 1,
'nbLoadedItemsSinceLastFlush' => $this->nbLoadedItemsSinceLastFlush + 1,
]);
Expand All @@ -103,23 +103,23 @@ public function withIncrementedNbLoadedItems(): self
*/
public function withNbTotalItems(?int $nbTotalItems): self
{
return $this->clone(['nbTotalItems' => $nbTotalItems]);
return $this->cloneWith(['nbTotalItems' => $nbTotalItems]);
}

/**
* @internal
*/
public function withOutput(mixed $output): self
{
return $this->clone(['output' => $output]);
return $this->cloneWith(['output' => $output]);
}

/**
* @internal
*/
public function withClearedFlush(): self
{
return $this->clone([
return $this->cloneWith([
'earlyFlush' => false,
'nbLoadedItemsSinceLastFlush' => 0,
]);
Expand Down
30 changes: 20 additions & 10 deletions src/Internal/ClonableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
namespace Bentools\ETL\Internal;

use ReflectionClass;
use ReflectionProperty;

use function array_column;
use function array_combine;
use function array_fill;
use function array_intersect_key;
use function count;
use function array_diff;
use function array_filter;
use function Bentools\ETL\array_fill_from;
use function get_object_vars;

/**
Expand All @@ -23,15 +23,25 @@ trait ClonableTrait
/**
* Create a clone.
*
* @param array<string, mixed> $overridenProps
* @param array<string, mixed> $cloneArgs
*/
private function clone(array $overridenProps = []): self
public function cloneWith(array $cloneArgs = []): self
{
static $refl, $constructorParams, $emptyProps;
static $refl, $writableProps, $writablePropNames, $constructorParamNames;
$refl ??= new ReflectionClass($this);
$constructorParams ??= array_column($refl->getConstructor()->getParameters(), 'name');
$emptyProps ??= array_combine($constructorParams, array_fill(0, count($constructorParams), null));
$constructorParamNames ??= array_column($refl->getConstructor()->getParameters(), 'name');
$writableProps ??= array_filter(
$refl->getProperties(),
fn (ReflectionProperty $property) => !$property->isReadOnly(),
);
$writablePropNames ??= array_diff(array_column($writableProps, 'name'), $constructorParamNames);

return new self(...($overridenProps + array_intersect_key(get_object_vars($this), $emptyProps)));
$clone = new self(...array_fill_from($constructorParamNames, get_object_vars($this), $cloneArgs));
$notPromotedProps = array_fill_from($writablePropNames, get_object_vars($this), $cloneArgs);
foreach ($notPromotedProps as $prop => $value) {
$clone->{$prop} = $value;
}

return $clone;
}
}
8 changes: 4 additions & 4 deletions src/Internal/EtlBuilderTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function extractFrom(ExtractorInterface|callable $extractor): self
$extractor = new CallableExtractor($extractor(...));
}

return $this->clone(['extractor' => $extractor]);
return $this->cloneWith(['extractor' => $extractor]);
}

public function transformWith(TransformerInterface|callable $transformer): self
Expand All @@ -40,7 +40,7 @@ public function transformWith(TransformerInterface|callable $transformer): self
$transformer = new CallableTransformer($transformer(...));
}

return $this->clone(['transformer' => $transformer]);
return $this->cloneWith(['transformer' => $transformer]);
}

public function loadInto(LoaderInterface|callable $loader): self
Expand All @@ -49,12 +49,12 @@ public function loadInto(LoaderInterface|callable $loader): self
$loader = new CallableLoader($loader(...));
}

return $this->clone(['loader' => $loader]);
return $this->cloneWith(['loader' => $loader]);
}

public function withOptions(EtlConfiguration $configuration): self
{
return $this->clone(['options' => $configuration]);
return $this->cloneWith(['options' => $configuration]);
}

public function withRecipe(Recipe|callable $recipe): self
Expand Down
4 changes: 2 additions & 2 deletions src/Internal/EtlEventListenersTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/
trait EtlEventListenersTrait
{
private readonly PrioritizedListenerProvider $listenerProvider;
private PrioritizedListenerProvider $listenerProvider;

/**
* @param callable(InitEvent): void $callback
Expand Down Expand Up @@ -116,7 +116,7 @@ public function onEnd(callable $callback, int $priority = 0): self

private function listenTo(string $eventClass, callable $callback, int $priority = 0): self
{
$clone = $this->clone();
$clone = $this->cloneWith();
$clone->listenerProvider->listenTo($eventClass, $callback, $priority);

return $clone;
Expand Down
24 changes: 24 additions & 0 deletions src/functions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Bentools\ETL;

use function array_fill_keys;
use function array_intersect_key;
use function array_replace;

/**
* @param list<string> $keys
* @param array<string, mixed> $values
* @param array<string, mixed> ...$extraValues
*
* @return array<string, mixed>
*/
function array_fill_from(array $keys, array $values, array ...$extraValues): array
{
$defaults = array_fill_keys($keys, null);
$values = array_replace($values, ...$extraValues);

return array_intersect_key($values, $defaults);
}
27 changes: 27 additions & 0 deletions tests/Unit/FunctionsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace BenTools\ETL\Tests\Unit;

use function Bentools\ETL\array_fill_from;
use function expect;

it('produces a new array with the provided keys', function () {
// Given
$food = [
'a' => 'Apple',
'b' => 'Banana',
'c' => 'Carrot',
'd' => 'Dill',
];

// When
$result = array_fill_from(['a', 'b', 'e'], $food, ['b' => 'banana', 'f' => 'Fig']);

// Then
expect($result)->toBe([
'a' => 'Apple',
'b' => 'banana',
]);
});
18 changes: 12 additions & 6 deletions tests/Unit/Recipe/RecipeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,24 @@

it('uses a recipe', function () {
// Given
$toggle = 'off';
$hasReceivedInitEvent = false;
$hasReceivedEndEvent = false;
$executor = (new EtlExecutor())->withRecipe(
function (EtlExecutor $executor) use (&$toggle) {
return $executor->onInit(function () use (&$toggle) {
$toggle = 'on';
});
function (EtlExecutor $executor) use (&$hasReceivedInitEvent, &$hasReceivedEndEvent) {
return $executor
->onInit(function () use (&$hasReceivedInitEvent) {
$hasReceivedInitEvent = true;
})
->onEnd(function () use (&$hasReceivedEndEvent) {
$hasReceivedEndEvent = true;
});
},
);

// When
$executor->process([]);

// Then
expect($toggle)->toBe('on');
expect($hasReceivedInitEvent)->toBeTrue()
->and($hasReceivedEndEvent)->toBeTrue();
});

0 comments on commit edb0366

Please sign in to comment.