From 1136622ea6d56b2e579fdcbc18c7431f14264f72 Mon Sep 17 00:00:00 2001 From: Norbert Orzechowicz Date: Sun, 12 Jul 2026 12:49:11 +0200 Subject: [PATCH] feat(flow-php/etl): schema-driven constructor-less entry instantiation - move Floe entry instantiation into core as EntryInstantiator with generic per-class Instantiators cache - add EntryFactory::instantiate() creating entries without casting or validation - add Definition::entryClass() to all schema definitions; UnionDefinition resolves it from the left union member, unwrapping optional - rename Floe HydratorColumn to ColumnBlueprint - parquet: new ValueHydrator restores Json/Uuid value objects from raw strings; extractor builds entries via instantiate() instead of array_to_row() - update flow_php extension to relocated core classes --- .../ETL/Adapter/Parquet/ParquetExtractor.php | 29 +++-- .../ETL/Adapter/Parquet/ValueHydrator.php | 37 ++++++ .../Parquet/Tests/Unit/ValueHydratorTest.php | 112 ++++++++++++++++++ .../Row/Entry/EntryInstantiator.php} | 27 +++-- .../src/Flow/ETL/Row/Entry/Instantiators.php | 34 ++++++ .../etl/src/Flow/ETL/Row/Entry/ListEntry.php | 10 -- .../etl/src/Flow/ETL/Row/Entry/MapEntry.php | 12 -- .../src/Flow/ETL/Row/Entry/StructureEntry.php | 12 -- .../etl/src/Flow/ETL/Row/EntryFactory.php | 15 +++ .../etl/src/Flow/ETL/Schema/Definition.php | 5 + .../Schema/Definition/BooleanDefinition.php | 8 ++ .../ETL/Schema/Definition/DateDefinition.php | 8 ++ .../Schema/Definition/DateTimeDefinition.php | 8 ++ .../ETL/Schema/Definition/EnumDefinition.php | 8 ++ .../ETL/Schema/Definition/FloatDefinition.php | 8 ++ .../ETL/Schema/Definition/HTMLDefinition.php | 8 ++ .../Definition/HTMLElementDefinition.php | 8 ++ .../Schema/Definition/IntegerDefinition.php | 8 ++ .../ETL/Schema/Definition/JsonDefinition.php | 8 ++ .../ETL/Schema/Definition/ListDefinition.php | 8 ++ .../ETL/Schema/Definition/MapDefinition.php | 8 ++ .../Schema/Definition/StringDefinition.php | 8 ++ .../Schema/Definition/StructureDefinition.php | 8 ++ .../ETL/Schema/Definition/TimeDefinition.php | 8 ++ .../ETL/Schema/Definition/UnionDefinition.php | 16 +++ .../ETL/Schema/Definition/UuidDefinition.php | 8 ++ .../ETL/Schema/Definition/XMLDefinition.php | 8 ++ .../Definition/XMLElementDefinition.php | 8 ++ .../etl/src/Flow/Floe/ColumnBlueprint.php | 39 ++++++ .../etl/src/Flow/Floe/EntryInstantiator.php | 29 ----- src/core/etl/src/Flow/Floe/FloeFile.php | 5 +- src/core/etl/src/Flow/Floe/HydratorColumn.php | 24 ---- src/core/etl/src/Flow/Floe/RowHydrator.php | 4 +- src/core/etl/src/Flow/Floe/RowPadding.php | 2 +- src/core/etl/src/Flow/Floe/SchemaDecoder.php | 11 +- .../Tests/Unit/Row/Entry/ListEntryTest.php | 47 -------- .../ETL/Tests/Unit/Row/Entry/MapEntryTest.php | 52 -------- .../Unit/Row/Entry/StructureEntryTest.php | 13 -- .../ETL/Tests/Unit/Row/EntryFactoryTest.php | 87 ++++++++++++++ .../Definition/BooleanDefinitionTest.php | 6 + .../Schema/Definition/DateDefinitionTest.php | 6 + .../Definition/DateTimeDefinitionTest.php | 6 + .../Schema/Definition/EnumDefinitionTest.php | 6 + .../Schema/Definition/FloatDefinitionTest.php | 6 + .../Schema/Definition/HTMLDefinitionTest.php | 6 + .../Definition/HTMLElementDefinitionTest.php | 6 + .../Definition/IntegerDefinitionTest.php | 6 + .../Schema/Definition/JsonDefinitionTest.php | 6 + .../Schema/Definition/ListDefinitionTest.php | 6 + .../Schema/Definition/MapDefinitionTest.php | 6 + .../Definition/StringDefinitionTest.php | 6 + .../Definition/StructureDefinitionTest.php | 9 ++ .../Schema/Definition/TimeDefinitionTest.php | 6 + .../Schema/Definition/UnionDefinitionTest.php | 31 +++++ .../Schema/Definition/UuidDefinitionTest.php | 6 + .../Schema/Definition/XMLDefinitionTest.php | 6 + .../Definition/XMLElementDefinitionTest.php | 6 + .../Flow/Floe/Tests/Unit/EntryFactoryTest.php | 6 +- .../Floe/Tests/Unit/EntryInstantiatorTest.php | 9 +- .../Flow/Floe/Tests/Unit/RowHydratorTest.php | 14 +-- .../Flow/Floe/Tests/Unit/RowPaddingTest.php | 10 +- .../Floe/Tests/Unit/SchemaDecoderTest.php | 12 +- src/extension/flow-php-ext/src/ctx.rs | 12 +- .../flow-php-ext/tests/phpt/bootstrap.php | 4 +- .../src/Flow/Types/Type/Logical/ListType.php | 4 - .../Flow/Types/Type/Logical/StructureType.php | 4 - src/lib/types/src/Flow/Types/Value/Uuid.php | 21 +--- 67 files changed, 699 insertions(+), 291 deletions(-) create mode 100644 src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ValueHydrator.php create mode 100644 src/adapter/etl-adapter-parquet/tests/Flow/ETL/Adapter/Parquet/Tests/Unit/ValueHydratorTest.php rename src/core/etl/src/Flow/{Floe/EntryFactory.php => ETL/Row/Entry/EntryInstantiator.php} (63%) create mode 100644 src/core/etl/src/Flow/ETL/Row/Entry/Instantiators.php create mode 100644 src/core/etl/src/Flow/Floe/ColumnBlueprint.php delete mode 100644 src/core/etl/src/Flow/Floe/EntryInstantiator.php delete mode 100644 src/core/etl/src/Flow/Floe/HydratorColumn.php diff --git a/src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ParquetExtractor.php b/src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ParquetExtractor.php index 363467729d..de226e83d5 100644 --- a/src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ParquetExtractor.php +++ b/src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ParquetExtractor.php @@ -21,8 +21,10 @@ use Flow\Parquet\Reader; use Generator; -use function Flow\ETL\DSL\array_to_row; +use function Flow\ETL\DSL\ref; +use function Flow\ETL\DSL\row; use function Flow\ETL\DSL\rows; +use function Flow\Types\DSL\type_string; final class ParquetExtractor implements Extractor, FileExtractor, LimitableExtractor { @@ -42,6 +44,8 @@ final class ParquetExtractor implements Extractor, FileExtractor, LimitableExtra private SchemaConverter $schemaConverter; + private ValueHydrator $valueHydrator; + /** * @param Path $path */ @@ -50,6 +54,7 @@ public function __construct( ) { $this->resetLimit(); $this->schemaConverter = new SchemaConverter(); + $this->valueHydrator = new ValueHydrator(); $this->options = Options::default(); } @@ -79,16 +84,24 @@ public function extract(FlowContext $context): Generator } foreach ($fileData['file']->values($this->columns, $this->limit(), $fileOffset) as $row) { + $entries = []; + if ($shouldPutInputIntoRows) { - $row['_input_file_uri'] = $uri; + $entries[] = $context->entryFactory()->createAs('_input_file_uri', $uri, type_string()); + } + + // @mago-ignore analysis:mixed-assignment + foreach ($row as $entryName => $entryValue) { + $definition = $flowSchema->get(ref($entryName)); + + $entries[] = $context->entryFactory()->instantiate( + $entryName, + $this->valueHydrator->hydrate($entryValue, $definition), + $definition, + ); } - $signal = yield rows(array_to_row( - $row, - $context->entryFactory(), - $fileData['stream']->path()->partitions(), - $flowSchema, - )); + $signal = yield rows(row(...$entries)); $this->incrementReturnedRows(); diff --git a/src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ValueHydrator.php b/src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ValueHydrator.php new file mode 100644 index 0000000000..ce2cada3dc --- /dev/null +++ b/src/adapter/etl-adapter-parquet/src/Flow/ETL/Adapter/Parquet/ValueHydrator.php @@ -0,0 +1,37 @@ + $definition + */ + public function hydrate(mixed $value, Definition $definition): mixed + { + if ($value === null) { + return null; + } + + $type = $definition->type(); + + if ($type instanceof OptionalType) { + $type = $type->base(); + } + + if ($type instanceof JsonType || $type instanceof UuidType) { + return $type->cast($value); + } + + return $value; + } +} diff --git a/src/adapter/etl-adapter-parquet/tests/Flow/ETL/Adapter/Parquet/Tests/Unit/ValueHydratorTest.php b/src/adapter/etl-adapter-parquet/tests/Flow/ETL/Adapter/Parquet/Tests/Unit/ValueHydratorTest.php new file mode 100644 index 0000000000..6c2ec5bb2e --- /dev/null +++ b/src/adapter/etl-adapter-parquet/tests/Flow/ETL/Adapter/Parquet/Tests/Unit/ValueHydratorTest.php @@ -0,0 +1,112 @@ +hydrate($json, json_schema('json'))); + static::assertSame($uuid, (new ValueHydrator())->hydrate($uuid, uuid_schema('uuid'))); + } + + public function test_hydrating_json_string_into_json_value(): void + { + static::assertEquals( + new Json('{"id":1,"name":"flow"}'), + (new ValueHydrator())->hydrate('{"id":1,"name":"flow"}', json_schema('json')), + ); + } + + public function test_hydrating_json_string_into_json_value_for_nullable_definition(): void + { + static::assertEquals( + new Json('{"id":1}'), + (new ValueHydrator())->hydrate('{"id":1}', json_schema('json', true)), + ); + } + + public function test_hydrating_null(): void + { + static::assertNull((new ValueHydrator())->hydrate(null, int_schema('int', true))); + static::assertNull((new ValueHydrator())->hydrate(null, json_schema('json', true))); + static::assertNull((new ValueHydrator())->hydrate(null, uuid_schema('uuid', true))); + static::assertNull((new ValueHydrator())->hydrate(null, int_schema('int'))); + } + + public function test_hydrating_uuid_string_into_uuid_value(): void + { + static::assertEquals( + new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479'), + (new ValueHydrator())->hydrate('f47ac10b-58cc-4372-a567-0e02b2c3d479', uuid_schema('uuid')), + ); + } + + public function test_hydrating_uuid_string_into_uuid_value_for_nullable_definition(): void + { + static::assertEquals( + new Uuid('f47ac10b-58cc-4372-a567-0e02b2c3d479'), + (new ValueHydrator())->hydrate('f47ac10b-58cc-4372-a567-0e02b2c3d479', uuid_schema('uuid', true)), + ); + } + + public function test_values_of_types_without_flow_value_objects_are_passed_through_without_casting(): void + { + $datetime = new DateTimeImmutable('2024-04-01 10:00:00 UTC'); + $time = new DateInterval('PT2H30M'); + $list = [1, 2, 3]; + $map = ['a' => 1, 'b' => 2]; + $structure = ['lat' => 1.5, 'lon' => 2.5]; + + static::assertSame(1, (new ValueHydrator())->hydrate(1, int_schema('int'))); + static::assertSame(1.5, (new ValueHydrator())->hydrate(1.5, float_schema('float'))); + static::assertTrue((new ValueHydrator())->hydrate(true, bool_schema('bool'))); + static::assertSame('flow', (new ValueHydrator())->hydrate('flow', string_schema('string'))); + static::assertSame($datetime, (new ValueHydrator())->hydrate($datetime, datetime_schema('datetime'))); + static::assertSame($datetime, (new ValueHydrator())->hydrate($datetime, date_schema('date'))); + static::assertSame($time, (new ValueHydrator())->hydrate($time, time_schema('time'))); + static::assertSame($list, (new ValueHydrator())->hydrate($list, list_schema( + 'list', + type_list(type_integer()), + ))); + static::assertSame($map, (new ValueHydrator())->hydrate($map, map_schema('map', type_map( + type_string(), + type_integer(), + )))); + static::assertSame($structure, (new ValueHydrator())->hydrate($structure, structure_schema('struct', type_structure([ + 'lat' => type_float(), + 'lon' => type_float(), + ])))); + } +} diff --git a/src/core/etl/src/Flow/Floe/EntryFactory.php b/src/core/etl/src/Flow/ETL/Row/Entry/EntryInstantiator.php similarity index 63% rename from src/core/etl/src/Flow/Floe/EntryFactory.php rename to src/core/etl/src/Flow/ETL/Row/Entry/EntryInstantiator.php index 0315d0794a..1c0515824d 100644 --- a/src/core/etl/src/Flow/Floe/EntryFactory.php +++ b/src/core/etl/src/Flow/ETL/Row/Entry/EntryInstantiator.php @@ -2,22 +2,25 @@ declare(strict_types=1); -namespace Flow\Floe; +namespace Flow\ETL\Row\Entry; use Closure; use Flow\ETL\Row\Entry; use Flow\ETL\Schema\Definition; use ReflectionClass; -final class EntryFactory +/** + * @template-covariant T of Entry + */ +final readonly class EntryInstantiator { /** - * @var \Closure(string, mixed, Definition): Entry + * @var \Closure(string, mixed, Definition): T */ - private readonly Closure $instantiate; + private Closure $instantiate; /** - * @param \Closure(string, mixed, Definition): Entry $instantiate + * @param \Closure(string, mixed, Definition): T $instantiate */ private function __construct(Closure $instantiate) { @@ -25,13 +28,17 @@ private function __construct(Closure $instantiate) } /** - * @param class-string> $entryClass + * @template TEntry of Entry + * + * @param class-string $entryClass + * + * @return self */ - public static function forEntryClass(string $entryClass): self + public static function forClass(string $entryClass): self { $reflection = new ReflectionClass($entryClass); - /** @var \Closure(string, mixed, Definition): Entry $instantiate */ + /** @var \Closure(string, mixed, Definition): TEntry $instantiate */ $instantiate = Closure::bind( static function (string $name, mixed $value, Definition $definition) use ($reflection): Entry { $entry = $reflection->newInstanceWithoutConstructor(); @@ -54,9 +61,9 @@ static function (string $name, mixed $value, Definition $definition) use ($refle /** * @param Definition $definition * - * @return Entry + * @return T */ - public function create(string $name, mixed $value, Definition $definition): Entry + public function instantiate(string $name, mixed $value, Definition $definition): Entry { return ($this->instantiate)($name, $value, $definition); } diff --git a/src/core/etl/src/Flow/ETL/Row/Entry/Instantiators.php b/src/core/etl/src/Flow/ETL/Row/Entry/Instantiators.php new file mode 100644 index 0000000000..29e0567e44 --- /dev/null +++ b/src/core/etl/src/Flow/ETL/Row/Entry/Instantiators.php @@ -0,0 +1,34 @@ +>, EntryInstantiator>> + */ + private array $instantiators = []; + + /** + * @template T of Entry + * + * @param class-string $entryClass + * + * @return EntryInstantiator + */ + public function for(string $entryClass): EntryInstantiator + { + if (!array_key_exists($entryClass, $this->instantiators)) { + $this->instantiators[$entryClass] = EntryInstantiator::forClass($entryClass); + } + + /** @var EntryInstantiator */ + return $this->instantiators[$entryClass]; + } +} diff --git a/src/core/etl/src/Flow/ETL/Row/Entry/ListEntry.php b/src/core/etl/src/Flow/ETL/Row/Entry/ListEntry.php index 4db94581dc..dc997f5af2 100644 --- a/src/core/etl/src/Flow/ETL/Row/Entry/ListEntry.php +++ b/src/core/etl/src/Flow/ETL/Row/Entry/ListEntry.php @@ -11,7 +11,6 @@ use Flow\ETL\Schema\Definition\ListDefinition; use Flow\ETL\Schema\Metadata; use Flow\Types\Type\Logical\ListType; -use Flow\Types\Type\TypeDetector; use function Flow\Types\DSL\type_equals; use function json_encode; @@ -54,15 +53,6 @@ public function __construct( $this->value = $value; - // @mago-ignore analysis:redundant-type-comparison - if ($this->value !== null && !$type->isValid($this->value)) { - throw InvalidArgumentException::because( - 'Expected ' . $type->toString() . ' got different types: ' . (new TypeDetector()) - ->detectType($this->value) - ->toString(), - ); - } - $this->definition = new ListDefinition( $this->name, $type, diff --git a/src/core/etl/src/Flow/ETL/Row/Entry/MapEntry.php b/src/core/etl/src/Flow/ETL/Row/Entry/MapEntry.php index edeca4767c..9184b10d4c 100644 --- a/src/core/etl/src/Flow/ETL/Row/Entry/MapEntry.php +++ b/src/core/etl/src/Flow/ETL/Row/Entry/MapEntry.php @@ -11,7 +11,6 @@ use Flow\ETL\Schema\Definition\MapDefinition; use Flow\ETL\Schema\Metadata; use Flow\Types\Type\Logical\MapType; -use Flow\Types\Type\TypeDetector; use function Flow\Types\DSL\type_equals; use function json_encode; @@ -55,17 +54,6 @@ public function __construct( $this->value = $value; - // @mago-ignore analysis:redundant-type-comparison - if ($this->value !== null && !$type->isValid($this->value)) { - throw InvalidArgumentException::because( - 'Expected ' . $type->toString() . ' got different types: ' - . (new TypeDetector()) - // @mago-ignore analysis:no-value - ->detectType($this->value) - ->toString(), - ); - } - $this->definition = new MapDefinition( $this->name, $type, diff --git a/src/core/etl/src/Flow/ETL/Row/Entry/StructureEntry.php b/src/core/etl/src/Flow/ETL/Row/Entry/StructureEntry.php index 0568b80863..7827e3d4a3 100644 --- a/src/core/etl/src/Flow/ETL/Row/Entry/StructureEntry.php +++ b/src/core/etl/src/Flow/ETL/Row/Entry/StructureEntry.php @@ -11,7 +11,6 @@ use Flow\ETL\Schema\Definition\StructureDefinition; use Flow\ETL\Schema\Metadata; use Flow\Types\Type\Logical\StructureType; -use Flow\Types\Type\TypeDetector; use function count; use function Flow\Types\DSL\type_equals; @@ -60,17 +59,6 @@ public function __construct( $this->value = $value; - // @mago-ignore analysis:redundant-type-comparison - if ($this->value !== null && !$type->isValid($this->value)) { - throw InvalidArgumentException::because( - 'Expected ' . $type->toString() . ' got different types: ' - . (new TypeDetector()) - // @mago-ignore analysis:no-value - ->detectType($this->value) - ->toString(), - ); - } - $this->definition = new StructureDefinition( $this->name, $type, diff --git a/src/core/etl/src/Flow/ETL/Row/EntryFactory.php b/src/core/etl/src/Flow/ETL/Row/EntryFactory.php index c244468f3d..d530618f0c 100644 --- a/src/core/etl/src/Flow/ETL/Row/EntryFactory.php +++ b/src/core/etl/src/Flow/ETL/Row/EntryFactory.php @@ -6,6 +6,7 @@ use Flow\ETL\Exception\InvalidArgumentException; use Flow\ETL\Exception\SchemaDefinitionNotFoundException; +use Flow\ETL\Row\Entry\Instantiators; use Flow\ETL\Row\Entry\StringEntry; use Flow\ETL\Schema; use Flow\ETL\Schema\Definition; @@ -52,6 +53,7 @@ use function Flow\ETL\DSL\list_entry; use function Flow\ETL\DSL\map_entry; use function Flow\ETL\DSL\null_entry; +use function Flow\ETL\DSL\ref; use function Flow\ETL\DSL\string_entry; use function Flow\ETL\DSL\struct_entry; use function Flow\ETL\DSL\structure_entry; @@ -66,10 +68,12 @@ final readonly class EntryFactory { private EntryTypeResolver $typeResolver; + private Instantiators $instantiators; public function __construct() { $this->typeResolver = new EntryTypeResolver(); + $this->instantiators = new Instantiators(); } /** @@ -222,4 +226,15 @@ public function createAs(string $entryName, mixed $value, Type $type, ?Metadata ); } } + + public function instantiate(string $entryName, mixed $value, Schema|Definition $schema): Entry + { + if ($schema instanceof Schema) { + $definition = $schema->get(ref($entryName)); + } else { + $definition = $schema; + } + + return $this->instantiators->for($definition->entryClass())->instantiate($entryName, $value, $definition); + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition.php b/src/core/etl/src/Flow/ETL/Schema/Definition.php index c984c7b08c..bc8646af15 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition.php @@ -76,4 +76,9 @@ public function setMetadata(Metadata $metadata): static; * @return Type */ public function type(): Type; + + /** + * @return class-string + */ + public function entryClass(): string; } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/BooleanDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/BooleanDefinition.php index d51ef65f96..d7b50447f0 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/BooleanDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/BooleanDefinition.php @@ -194,4 +194,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\BooleanEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/DateDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/DateDefinition.php index 9a50144b1d..06394e162d 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/DateDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/DateDefinition.php @@ -218,4 +218,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\DateEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/DateTimeDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/DateTimeDefinition.php index d85f968cea..676d3cb02c 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/DateTimeDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/DateTimeDefinition.php @@ -214,4 +214,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\DateTimeEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/EnumDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/EnumDefinition.php index 88927f6f53..d2120ce201 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/EnumDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/EnumDefinition.php @@ -220,4 +220,12 @@ public function type(): EnumType { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\EnumEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/FloatDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/FloatDefinition.php index 4ed7df538c..e5920f9bfd 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/FloatDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/FloatDefinition.php @@ -210,4 +210,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\FloatEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLDefinition.php index f172dc43b7..3271823887 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLDefinition.php @@ -195,4 +195,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\HTMLEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLElementDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLElementDefinition.php index a9ad438499..9b74788f67 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLElementDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/HTMLElementDefinition.php @@ -195,4 +195,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\HTMLElementEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/IntegerDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/IntegerDefinition.php index 52206e1c63..1525d7b0b0 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/IntegerDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/IntegerDefinition.php @@ -210,4 +210,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\IntegerEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/JsonDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/JsonDefinition.php index 2a13d95758..9b20a235b1 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/JsonDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/JsonDefinition.php @@ -195,4 +195,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\JsonEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/ListDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/ListDefinition.php index 260e9f2f94..f6773ec617 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/ListDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/ListDefinition.php @@ -257,4 +257,12 @@ public function type(): ListType { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\ListEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/MapDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/MapDefinition.php index 52eb367b2a..a2bfe4e643 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/MapDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/MapDefinition.php @@ -239,4 +239,12 @@ public function type(): MapType { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\MapEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/StringDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/StringDefinition.php index 6768a0950e..4924d96ae5 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/StringDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/StringDefinition.php @@ -182,4 +182,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\StringEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/StructureDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/StructureDefinition.php index b2a126c872..111a5d00fa 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/StructureDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/StructureDefinition.php @@ -255,4 +255,12 @@ public function type(): StructureType { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\StructureEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/TimeDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/TimeDefinition.php index 3c203a2081..c5d5f166b6 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/TimeDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/TimeDefinition.php @@ -202,4 +202,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\TimeEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/UnionDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/UnionDefinition.php index eebfac74c7..6dc3854ecb 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/UnionDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/UnionDefinition.php @@ -10,8 +10,10 @@ use Flow\ETL\Row\Reference; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Metadata; +use Flow\Types\Type\Logical\OptionalType; use Flow\Types\Type\Native\UnionType; +use function Flow\ETL\DSL\definition_from_type; use function Flow\Types\DSL\type_equals; use function sprintf; @@ -194,4 +196,18 @@ public function type(): UnionType { return $this->type; } + + public function entryClass(): string + { + $left = $this->type->types()->first() ?? throw new RuntimeException(sprintf( + 'Union type of "%s" has no member types', + $this->ref->name(), + )); + + if ($left instanceof OptionalType) { + $left = $left->base(); + } + + return definition_from_type($this->ref, $left)->entryClass(); + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/UuidDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/UuidDefinition.php index dca4355a5f..dbb454cd47 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/UuidDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/UuidDefinition.php @@ -195,4 +195,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\UuidEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/XMLDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/XMLDefinition.php index 17f1c9e2cb..1e96af066f 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/XMLDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/XMLDefinition.php @@ -195,4 +195,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\XMLEntry::class; + } } diff --git a/src/core/etl/src/Flow/ETL/Schema/Definition/XMLElementDefinition.php b/src/core/etl/src/Flow/ETL/Schema/Definition/XMLElementDefinition.php index f4c39c6262..423dc35d65 100644 --- a/src/core/etl/src/Flow/ETL/Schema/Definition/XMLElementDefinition.php +++ b/src/core/etl/src/Flow/ETL/Schema/Definition/XMLElementDefinition.php @@ -194,4 +194,12 @@ public function type(): Type { return $this->type; } + + /** + * @return class-string + */ + public function entryClass(): string + { + return Entry\XMLElementEntry::class; + } } diff --git a/src/core/etl/src/Flow/Floe/ColumnBlueprint.php b/src/core/etl/src/Flow/Floe/ColumnBlueprint.php new file mode 100644 index 0000000000..092eba7b1e --- /dev/null +++ b/src/core/etl/src/Flow/Floe/ColumnBlueprint.php @@ -0,0 +1,39 @@ + + */ + public Definition $nullableDefinition; + + /** + * @var Definition + */ + public Definition $fromNullDefinition; + + /** + * @param Definition $definition + * @param EntryInstantiator> $instantiator + */ + public function __construct( + public string $name, + public Definition $definition, + public Decoding\ValueDecoder $decoder, + public EntryInstantiator $instantiator, + ) { + $this->fromNullDefinition = $definition + ->makeNullable() + ->setMetadata($definition->metadata()->merge(Metadata::fromArray([Metadata::FROM_NULL => true]))); + $this->nullableDefinition = $definition->makeNullable(); + } +} diff --git a/src/core/etl/src/Flow/Floe/EntryInstantiator.php b/src/core/etl/src/Flow/Floe/EntryInstantiator.php deleted file mode 100644 index 5d826cfbae..0000000000 --- a/src/core/etl/src/Flow/Floe/EntryInstantiator.php +++ /dev/null @@ -1,29 +0,0 @@ - - */ - private array $factories = []; - - /** - * @param class-string> $entryClass - */ - public function factoryFor(string $entryClass): EntryFactory - { - if (array_key_exists($entryClass, $this->factories)) { - return $this->factories[$entryClass]; - } - - return $this->factories[$entryClass] = EntryFactory::forEntryClass($entryClass); - } -} diff --git a/src/core/etl/src/Flow/Floe/FloeFile.php b/src/core/etl/src/Flow/Floe/FloeFile.php index 52e3de094b..9c1088681c 100644 --- a/src/core/etl/src/Flow/Floe/FloeFile.php +++ b/src/core/etl/src/Flow/Floe/FloeFile.php @@ -6,6 +6,7 @@ use Closure; use Flow\ETL\Row; +use Flow\ETL\Row\Entry\Instantiators; use Flow\ETL\Rows; use Flow\ETL\Schema; use Flow\ETL\Schema\Metadata; @@ -52,7 +53,7 @@ public function __construct( private readonly int $chunkSize, ?bool $useExtension, ) { - $this->schemaDecoder = new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()); + $this->schemaDecoder = new SchemaDecoder(new ValueDecoder(), new Instantiators()); $this->rowHydrator = new RowHydrator(); $this->useExtension = $useExtension ?? extension_loaded('flow_php'); } @@ -945,7 +946,7 @@ private function rowsFromOffset(int $batchSize, int $offset, ?int $limit): Gener } /** - * @param null|array $plan + * @param null|array $plan * * @throws FloeException */ diff --git a/src/core/etl/src/Flow/Floe/HydratorColumn.php b/src/core/etl/src/Flow/Floe/HydratorColumn.php deleted file mode 100644 index 4644ba7a0e..0000000000 --- a/src/core/etl/src/Flow/Floe/HydratorColumn.php +++ /dev/null @@ -1,24 +0,0 @@ - $definition - * @param Definition $nullableDefinition - * @param Definition $fromNullDefinition - */ - public function __construct( - public string $name, - public Definition $definition, - public Definition $nullableDefinition, - public Definition $fromNullDefinition, - public Decoding\ValueDecoder $decoder, - public EntryFactory $entryFactory, - ) {} -} diff --git a/src/core/etl/src/Flow/Floe/RowHydrator.php b/src/core/etl/src/Flow/Floe/RowHydrator.php index d5b3a7795e..49d440d104 100644 --- a/src/core/etl/src/Flow/Floe/RowHydrator.php +++ b/src/core/etl/src/Flow/Floe/RowHydrator.php @@ -14,7 +14,7 @@ final class RowHydrator { /** - * @param array $plan + * @param array $plan */ public function hydrate(array $plan, string $data, int &$position): Row { @@ -42,7 +42,7 @@ public function hydrate(array $plan, string $data, int &$position): Row throw new SerializationException(sprintf('Floe found unknown value flag 0x%02X', $flag)); } - $entries[$column->name] = $column->entryFactory->create($column->name, $value, $definition); + $entries[$column->name] = $column->instantiator->instantiate($column->name, $value, $definition); } return new Row(Entries::recreate($entries)); diff --git a/src/core/etl/src/Flow/Floe/RowPadding.php b/src/core/etl/src/Flow/Floe/RowPadding.php index 4483c93a74..739975d4ce 100644 --- a/src/core/etl/src/Flow/Floe/RowPadding.php +++ b/src/core/etl/src/Flow/Floe/RowPadding.php @@ -34,7 +34,7 @@ public static function forFileSchema(Schema $fileSchema, SchemaDecoder $decoder) $name = $definition->entry()->name(); $order[] = $name; $column = $decoder->decode(json_encode([$definition->normalize()], JSON_THROW_ON_ERROR))[0]; - $nulls[$name] = $column->entryFactory->create($name, null, clone $column->fromNullDefinition); + $nulls[$name] = $column->instantiator->instantiate($name, null, clone $column->fromNullDefinition); } return new self($order, $nulls); diff --git a/src/core/etl/src/Flow/Floe/SchemaDecoder.php b/src/core/etl/src/Flow/Floe/SchemaDecoder.php index beaa4be228..03443094ca 100644 --- a/src/core/etl/src/Flow/Floe/SchemaDecoder.php +++ b/src/core/etl/src/Flow/Floe/SchemaDecoder.php @@ -12,6 +12,7 @@ use Flow\ETL\Row\Entry\FloatEntry; use Flow\ETL\Row\Entry\HTMLElementEntry; use Flow\ETL\Row\Entry\HTMLEntry; +use Flow\ETL\Row\Entry\Instantiators; use Flow\ETL\Row\Entry\IntegerEntry; use Flow\ETL\Row\Entry\JsonEntry; use Flow\ETL\Row\Entry\ListEntry; @@ -76,11 +77,11 @@ final class SchemaDecoder public function __construct( private readonly ValueDecoder $valueDecoder, - private readonly EntryInstantiator $entryInstantiator, + private readonly Instantiators $instantiators, ) {} /** - * @return array + * @return array */ public function decode(string $schemaJson): array { @@ -126,13 +127,11 @@ public function decode(string $schemaJson): array ); /** @var class-string> $entryClass */ - $plan[] = new HydratorColumn( + $plan[] = new ColumnBlueprint( $normalized['ref'], $definition, - $definition->makeNullable(), - $fromNullDefinition, $this->valueDecoder->decoderFor($definition->type()), - $this->entryInstantiator->factoryFor($entryClass), + $this->instantiators->for($entryClass), ); } diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/ListEntryTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/ListEntryTest.php index 11d3cb4e33..376cf7c8e1 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/ListEntryTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/ListEntryTest.php @@ -5,17 +5,13 @@ namespace Flow\ETL\Tests\Unit\Row\Entry; use DateTimeImmutable; -use DateTimeInterface; use Flow\ETL\Exception\InvalidArgumentException; use Flow\ETL\Schema\Metadata; use Flow\ETL\Tests\FlowTestCase; use function Flow\ETL\DSL\list_entry; use function Flow\ETL\DSL\list_schema; -use function Flow\Types\DSL\type_boolean; use function Flow\Types\DSL\type_datetime; -use function Flow\Types\DSL\type_float; -use function Flow\Types\DSL\type_instance_of; use function Flow\Types\DSL\type_integer; use function Flow\Types\DSL\type_list; use function Flow\Types\DSL\type_string; @@ -29,49 +25,6 @@ public function test_create_with_empty_name(): void list_entry('', ['one', 'two', 'three'], type_list(type_string())); } - public function test_creating_boolean_list_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected list got different types: array'); - list_entry('list', ['string', false], type_list(type_boolean())); - } - - public function test_creating_datetime_list_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected list> got different types: array'); - list_entry('list', ['string', new DateTimeImmutable()], type_list(type_instance_of(DateTimeInterface::class))); - } - - public function test_creating_float_list_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected list got different types: array'); - list_entry('list', ['string', 1.3], type_list(type_float())); - } - - public function test_creating_integer_list_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected list got different types: array'); - list_entry('list', ['string', 1], type_list(type_integer())); - } - - public function test_creating_list_from_not_list_array(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected list got different types: map'); - // @mago-ignore analysis:invalid-argument - list_entry('list', ['a' => 1, 'b' => 2], type_list(type_integer())); - } - - public function test_creating_string_list_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected list got different types: array'); - list_entry('list', ['string', 1], type_list(type_string())); - } - public function test_definition(): void { static::assertEquals( diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/MapEntryTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/MapEntryTest.php index 839f2f3c8b..72bf2d3e20 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/MapEntryTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/MapEntryTest.php @@ -4,16 +4,12 @@ namespace Flow\ETL\Tests\Unit\Row\Entry; -use DateTimeImmutable; use Flow\ETL\Exception\InvalidArgumentException; use Flow\ETL\Schema\Metadata; use Flow\ETL\Tests\FlowTestCase; use function Flow\ETL\DSL\map_entry; use function Flow\ETL\DSL\map_schema; -use function Flow\Types\DSL\type_boolean; -use function Flow\Types\DSL\type_datetime; -use function Flow\Types\DSL\type_float; use function Flow\Types\DSL\type_integer; use function Flow\Types\DSL\type_map; use function Flow\Types\DSL\type_string; @@ -28,54 +24,6 @@ public function test_create_with_empty_name(): void map_entry('', ['one', 'two', 'three'], type_map(type_integer(), type_string())); } - public function test_creating_boolean_map_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected map got different types: array'); - - map_entry('map', ['string', false], type_map(type_integer(), type_boolean())); - } - - public function test_creating_datetime_map_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected map got different types: array'); - - map_entry('map', ['string', new DateTimeImmutable()], type_map(type_integer(), type_datetime())); - } - - public function test_creating_float_map_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected map got different types: array'); - - map_entry('map', ['string', 1.3], type_map(type_integer(), type_float())); - } - - public function test_creating_integer_map_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected map got different types: array'); - - map_entry('map', ['string', 1], type_map(type_integer(), type_integer())); - } - - public function test_creating_map_from_not_map_array(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected map got different types: map'); - - map_entry('map', ['a' => 1, 'b' => 2], type_map(type_integer(), type_integer())); - } - - public function test_creating_string_map_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected map got different types: array'); - - map_entry('map', ['string', 1], type_map(type_integer(), type_string())); - } - public function test_definition(): void { static::assertEquals( diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/StructureEntryTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/StructureEntryTest.php index f67d465d38..81fbb79f68 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/StructureEntryTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/Entry/StructureEntryTest.php @@ -55,19 +55,6 @@ public static function is_equal_data_provider(): Generator ]; } - public function test_creating_string_structure_from_wrong_value_types(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'Expected structure{id: integer, name: string} got different types: list', - ); - // @mago-ignore analysis:invalid-argument - structure_entry('test', [1, 2, 3], type_structure([ - 'id' => type_integer(), - 'name' => type_string(), - ])); - } - public function test_definition(): void { $entry = struct_entry( diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/EntryFactoryTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/EntryFactoryTest.php index e1076b7264..791023a055 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/EntryFactoryTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Row/EntryFactoryTest.php @@ -16,8 +16,11 @@ use Flow\ETL\Row\Entry\StringEntry; use Flow\ETL\Row\Entry\TimeEntry; use Flow\ETL\Row\EntryFactory; +use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Metadata; use Flow\ETL\Tests\Fixtures\Enum\BackedIntEnum; +use Flow\Types\Value\Json; +use Flow\Types\Value\Uuid as FlowUuid; use Generator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\RequiresPhp; @@ -48,11 +51,14 @@ use function Flow\ETL\DSL\json_schema; use function Flow\ETL\DSL\list_entry; use function Flow\ETL\DSL\list_schema; +use function Flow\ETL\DSL\map_entry; +use function Flow\ETL\DSL\map_schema; use function Flow\ETL\DSL\schema; use function Flow\ETL\DSL\str_entry; use function Flow\ETL\DSL\string_entry; use function Flow\ETL\DSL\string_schema; use function Flow\ETL\DSL\structure_entry; +use function Flow\ETL\DSL\structure_schema; use function Flow\ETL\DSL\time_entry; use function Flow\ETL\DSL\time_schema; use function Flow\ETL\DSL\union_schema; @@ -75,6 +81,52 @@ final class EntryFactoryTest extends TestCase { private EntryFactory $entryFactory; + public static function provide_instantiate_cases(): Generator + { + yield 'boolean' => [bool_schema('e'), true, bool_entry('e', true)]; + yield 'date' => [date_schema('e'), $date = new DateTimeImmutable('2024-04-01'), date_entry('e', $date)]; + yield 'datetime' => [ + datetime_schema('e'), + $datetime = new DateTimeImmutable('2024-04-01 10:00:00 UTC'), + datetime_entry('e', $datetime), + ]; + yield 'enum' => [ + enum_schema('e', BackedIntEnum::class), + BackedIntEnum::one, + enum_entry('e', BackedIntEnum::one), + ]; + yield 'float' => [float_schema('e'), 1.5, float_entry('e', 1.5)]; + yield 'integer' => [integer_schema('e'), 1, int_entry('e', 1)]; + yield 'json' => [json_schema('e'), $json = new Json('{"id":1}'), json_entry('e', $json)]; + yield 'list' => [ + list_schema('e', type_list(type_integer())), + [1, 2, 3], + list_entry('e', [1, 2, 3], type_list(type_integer())), + ]; + yield 'map' => [ + map_schema('e', type_map(type_string(), type_integer())), + ['a' => 1], + map_entry('e', ['a' => 1], type_map(type_string(), type_integer())), + ]; + yield 'string' => [string_schema('e'), 'flow', string_entry('e', 'flow')]; + yield 'structure' => [ + structure_schema('e', type_structure(['a' => type_integer()])), + ['a' => 1], + structure_entry('e', ['a' => 1], type_structure(['a' => type_integer()])), + ]; + yield 'time' => [time_schema('e'), $time = new DateInterval('PT1H'), time_entry('e', $time)]; + yield 'uuid' => [ + uuid_schema('e'), + $uuid = new FlowUuid('f47ac10b-58cc-4372-a567-0e02b2c3d479'), + uuid_entry('e', $uuid), + ]; + + $xmlDocument = new DOMDocument(); + $xmlDocument->loadXML('1'); + + yield 'xml' => [xml_schema('e'), $xmlDocument, xml_entry('e', $xmlDocument)]; + } + public static function provide_recognized_data(): Generator { yield 'json' => [ @@ -295,6 +347,41 @@ public function test_html_string_with_html_definition_provided(): void ); } + #[DataProvider('provide_instantiate_cases')] + public function test_instantiate_with_definition(Definition $definition, mixed $value, Entry $expected): void + { + static::assertEquals($expected, $this->entryFactory->instantiate('e', $value, $definition)); + } + + public function test_instantiate_does_not_cast_values(): void + { + $datetime = new DateTimeImmutable('2024-04-01 10:00:00 UTC'); + + static::assertSame($datetime, $this->entryFactory->instantiate('e', $datetime, datetime_schema('e'))->value()); + } + + public function test_instantiate_preserves_definition_instance(): void + { + $definition = integer_schema('e'); + + static::assertSame($definition, $this->entryFactory->instantiate('e', 1, $definition)->definition()); + } + + public function test_instantiate_with_schema(): void + { + static::assertEquals( + int_entry('id', 1), + $this->entryFactory->instantiate('id', 1, schema(integer_schema('id'), string_schema('name'))), + ); + } + + public function test_instantiate_with_schema_without_definition_for_entry(): void + { + $this->expectException(SchemaDefinitionNotFoundException::class); + + $this->entryFactory->instantiate('unknown', 1, schema(integer_schema('id'))); + } + public function test_int(): void { static::assertEquals(int_entry('e', 1), $this->entryFactory->create('e', 1)); diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/BooleanDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/BooleanDefinitionTest.php index 8347543b13..590c2b9b9d 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/BooleanDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/BooleanDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\BooleanEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\IntegerDefinition; @@ -92,6 +93,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(BooleanEntry::class, bool_schema('flag')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateDefinitionTest.php index d2fce25b90..6b5c7a89e3 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\DateEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\DateDefinition; @@ -110,6 +111,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(DateEntry::class, date_schema('created_at')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateTimeDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateTimeDefinitionTest.php index 4b0018b66f..1bc9d49fbf 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateTimeDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/DateTimeDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\DateTimeEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\DateTimeDefinition; @@ -109,6 +110,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(DateTimeEntry::class, datetime_schema('created_at')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/EnumDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/EnumDefinitionTest.php index ac190054bd..1ef15ac436 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/EnumDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/EnumDefinitionTest.php @@ -6,6 +6,7 @@ use Flow\ETL\Exception\InvalidArgumentException; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\EnumEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\EnumDefinition; @@ -95,6 +96,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(EnumEntry::class, enum_schema('status', BackedStringEnum::class)->entryClass()); + } + public function test_enum_class_accessor(): void { $def = enum_schema('status', BackedStringEnum::class); diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/FloatDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/FloatDefinitionTest.php index 57c6bc0c44..f9b052c06f 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/FloatDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/FloatDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\FloatEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\FloatDefinition; @@ -102,6 +103,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(FloatEntry::class, float_schema('amount')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLDefinitionTest.php index f462905af0..f05b432886 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\HTMLEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\HTMLDefinition; @@ -94,6 +95,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(HTMLEntry::class, html_schema('content')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLElementDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLElementDefinitionTest.php index 592ee62bf1..10521874a9 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLElementDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/HTMLElementDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\HTMLElementEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\HTMLElementDefinition; @@ -94,6 +95,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(HTMLElementEntry::class, html_element_schema('element')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/IntegerDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/IntegerDefinitionTest.php index a05bce9fd6..8fbb62527f 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/IntegerDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/IntegerDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\IntegerEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\FloatDefinition; @@ -103,6 +104,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(str_entry('col', 'value'))); } + public function test_entry_class(): void + { + static::assertSame(IntegerEntry::class, int_schema('id')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/JsonDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/JsonDefinitionTest.php index 3cdd26c9a5..5de1feeebb 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/JsonDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/JsonDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\JsonEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\JsonDefinition; @@ -92,6 +93,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(JsonEntry::class, json_schema('data')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/ListDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/ListDefinitionTest.php index c8ec8f1c5e..4e08d59e40 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/ListDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/ListDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\ListEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\JsonDefinition; @@ -118,6 +119,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(ListEntry::class, list_schema('items', type_list(type_integer()))->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/MapDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/MapDefinitionTest.php index e0138244cc..00c936deca 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/MapDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/MapDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\MapEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\JsonDefinition; @@ -117,6 +118,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(MapEntry::class, map_schema('data', type_map(type_string(), type_integer()))->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StringDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StringDefinitionTest.php index 5affd5454d..56b9577770 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StringDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StringDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\StringEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\StringDefinition; use Flow\ETL\Schema\Metadata; @@ -91,6 +92,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(StringEntry::class, string_schema('name')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StructureDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StructureDefinitionTest.php index d4794c97a8..7828549add 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StructureDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/StructureDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\StructureEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\JsonDefinition; @@ -119,6 +120,14 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame( + StructureEntry::class, + structure_schema('data', type_structure(['name' => type_string()]))->entryClass(), + ); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/TimeDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/TimeDefinitionTest.php index 0ea05d4eaa..6eff8580bf 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/TimeDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/TimeDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\TimeEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\DateTimeDefinition; @@ -110,6 +111,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(TimeEntry::class, time_schema('duration')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UnionDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UnionDefinitionTest.php index f080d0049f..e89ea81a5f 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UnionDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UnionDefinitionTest.php @@ -5,6 +5,8 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\IntegerEntry; +use Flow\ETL\Row\Entry\StringEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\JsonDefinition; @@ -23,6 +25,8 @@ use function Flow\ETL\DSL\union_schema; use function Flow\Types\DSL\type_boolean; use function Flow\Types\DSL\type_integer; +use function Flow\Types\DSL\type_null; +use function Flow\Types\DSL\type_optional; use function Flow\Types\DSL\type_string; use function Flow\Types\DSL\type_union; @@ -114,6 +118,33 @@ public function test_does_not_match_entry_with_value_outside_of_union(): void static::assertFalse($def->matches(bool_entry('col', true))); } + public function test_entry_class_taken_from_left_union_member(): void + { + static::assertSame( + StringEntry::class, + union_schema('col', type_union(type_string(), type_integer()))->entryClass(), + ); + static::assertSame( + IntegerEntry::class, + union_schema('col', type_union(type_integer(), type_string()))->entryClass(), + ); + } + + public function test_entry_class_with_left_union_member_without_related_entry_class(): void + { + $this->expectException(RuntimeException::class); + + union_schema('col', type_union(type_null(), type_string()))->entryClass(); + } + + public function test_entry_class_with_optional_left_union_member(): void + { + static::assertSame( + StringEntry::class, + union_schema('col', type_union(type_optional(type_string()), type_integer()))->entryClass(), + ); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UuidDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UuidDefinitionTest.php index 1bd518e345..605e97fb2b 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UuidDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/UuidDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\UuidEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\UuidDefinition; @@ -92,6 +93,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(UuidEntry::class, uuid_schema('id')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLDefinitionTest.php index f213e5d7e0..64e88605df 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\XMLEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\XMLDefinition; @@ -92,6 +93,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(XMLEntry::class, xml_schema('document')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLElementDefinitionTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLElementDefinitionTest.php index 8508a21b1b..fcadc188a4 100644 --- a/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLElementDefinitionTest.php +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Schema/Definition/XMLElementDefinitionTest.php @@ -5,6 +5,7 @@ namespace Flow\ETL\Tests\Unit\Schema\Definition; use Flow\ETL\Exception\RuntimeException; +use Flow\ETL\Row\Entry\XMLElementEntry; use Flow\ETL\Schema\Definition; use Flow\ETL\Schema\Definition\BooleanDefinition; use Flow\ETL\Schema\Definition\XMLElementDefinition; @@ -92,6 +93,11 @@ public function test_does_not_match_entry_with_different_type(): void static::assertFalse($def->matches(int_entry('col', 1))); } + public function test_entry_class(): void + { + static::assertSame(XMLElementEntry::class, xml_element_schema('element')->entryClass()); + } + /** * @param Definition $definition * @param Definition $other diff --git a/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryFactoryTest.php b/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryFactoryTest.php index 342c081aab..b2cf90f2e8 100644 --- a/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryFactoryTest.php +++ b/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryFactoryTest.php @@ -4,11 +4,11 @@ namespace Flow\Floe\Tests\Unit; +use Flow\ETL\Row\Entry\EntryInstantiator; use Flow\ETL\Row\Entry\IntegerEntry; use Flow\ETL\Row\Entry\StringEntry; use Flow\ETL\Schema\Definition\IntegerDefinition; use Flow\ETL\Schema\Definition\StringDefinition; -use Flow\Floe\EntryFactory; use PHPUnit\Framework\TestCase; use function Flow\ETL\DSL\int_entry; @@ -18,7 +18,7 @@ final class EntryFactoryTest extends TestCase { public function test_created_entry_equals_constructed_entry(): void { - $entry = EntryFactory::forEntryClass(IntegerEntry::class)->create('id', 42, new IntegerDefinition('id')); + $entry = EntryInstantiator::forClass(IntegerEntry::class)->instantiate('id', 42, new IntegerDefinition('id')); static::assertEquals(int_entry('id', 42), $entry); static::assertSame(serialize(int_entry('id', 42)), serialize($entry)); @@ -26,7 +26,7 @@ public function test_created_entry_equals_constructed_entry(): void public function test_creating_entry_with_null_value(): void { - $entry = EntryFactory::forEntryClass(StringEntry::class)->create( + $entry = EntryInstantiator::forClass(StringEntry::class)->instantiate( 'name', null, new StringDefinition('name', true), diff --git a/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryInstantiatorTest.php b/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryInstantiatorTest.php index 9d62dd52a2..b816606fd0 100644 --- a/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryInstantiatorTest.php +++ b/src/core/etl/tests/Flow/Floe/Tests/Unit/EntryInstantiatorTest.php @@ -4,19 +4,16 @@ namespace Flow\Floe\Tests\Unit; +use Flow\ETL\Row\Entry\Instantiators; use Flow\ETL\Row\Entry\IntegerEntry; -use Flow\Floe\EntryInstantiator; use PHPUnit\Framework\TestCase; final class EntryInstantiatorTest extends TestCase { public function test_factories_are_memoized_per_class(): void { - $instantiator = new EntryInstantiator(); + $instantiator = new Instantiators(); - static::assertSame( - $instantiator->factoryFor(IntegerEntry::class), - $instantiator->factoryFor(IntegerEntry::class), - ); + static::assertSame($instantiator->for(IntegerEntry::class), $instantiator->for(IntegerEntry::class)); } } diff --git a/src/core/etl/tests/Flow/Floe/Tests/Unit/RowHydratorTest.php b/src/core/etl/tests/Flow/Floe/Tests/Unit/RowHydratorTest.php index d961193c82..b5b6e3121e 100644 --- a/src/core/etl/tests/Flow/Floe/Tests/Unit/RowHydratorTest.php +++ b/src/core/etl/tests/Flow/Floe/Tests/Unit/RowHydratorTest.php @@ -4,7 +4,7 @@ namespace Flow\Floe\Tests\Unit; -use Flow\Floe\EntryInstantiator; +use Flow\ETL\Row\Entry\Instantiators; use Flow\Floe\FloeWriter; use Flow\Floe\RowEncoder; use Flow\Floe\RowHydrator; @@ -28,10 +28,7 @@ public function test_hydrated_row_equals_original_row(): void $encoderPlan = FloeWriter::growSectionPlan(null, $original); $body = (new RowEncoder())->encode($encoderPlan, $original); - $hydratorPlan = (new SchemaDecoder( - new ValueDecoder(), - new EntryInstantiator(), - ))->decode($encoderPlan->schemaBody); + $hydratorPlan = (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode($encoderPlan->schemaBody); $position = 0; $hydrated = (new RowHydrator())->hydrate($hydratorPlan, $body, $position); @@ -47,10 +44,7 @@ public function test_hydrated_definitions_are_not_shared_between_rows(): void $encoderPlan = FloeWriter::growSectionPlan(null, $original); $body = (new RowEncoder())->encode($encoderPlan, $original); - $hydratorPlan = (new SchemaDecoder( - new ValueDecoder(), - new EntryInstantiator(), - ))->decode($encoderPlan->schemaBody); + $hydratorPlan = (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode($encoderPlan->schemaBody); $hydrator = new RowHydrator(); $position = 0; @@ -65,7 +59,7 @@ public function test_hydrating_row_with_unknown_value_flag_throws(): void { $original = row(int_entry('id', 1)); $schemaJson = FloeWriter::growSectionPlan(null, $original)->schemaBody; - $hydratorPlan = (new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()))->decode($schemaJson); + $hydratorPlan = (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode($schemaJson); $position = 0; $this->expectException(SerializationException::class); diff --git a/src/core/etl/tests/Flow/Floe/Tests/Unit/RowPaddingTest.php b/src/core/etl/tests/Flow/Floe/Tests/Unit/RowPaddingTest.php index 59a35cc905..7e12e9d3cf 100644 --- a/src/core/etl/tests/Flow/Floe/Tests/Unit/RowPaddingTest.php +++ b/src/core/etl/tests/Flow/Floe/Tests/Unit/RowPaddingTest.php @@ -4,8 +4,8 @@ namespace Flow\Floe\Tests\Unit; +use Flow\ETL\Row\Entry\Instantiators; use Flow\ETL\Schema\Metadata; -use Flow\Floe\EntryInstantiator; use Flow\Floe\RowPadding; use Flow\Floe\SchemaDecoder; use Flow\Floe\ValueDecoder; @@ -24,7 +24,7 @@ public function test_absent_columns_are_padded_with_from_null_entries(): void { $padding = RowPadding::forFileSchema( schema(int_schema('id'), str_schema('email', nullable: true)), - new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()), + new SchemaDecoder(new ValueDecoder(), new Instantiators()), ); $padded = $padding->apply(row(int_entry('id', 1))); @@ -41,7 +41,7 @@ public function test_padding_entries_are_shared_between_rows(): void { $padding = RowPadding::forFileSchema( schema(int_schema('id'), str_schema('email', nullable: true)), - new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()), + new SchemaDecoder(new ValueDecoder(), new Instantiators()), ); static::assertSame( @@ -54,7 +54,7 @@ public function test_row_is_rebuilt_in_file_schema_order(): void { $padding = RowPadding::forFileSchema( schema(int_schema('id'), str_schema('name')), - new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()), + new SchemaDecoder(new ValueDecoder(), new Instantiators()), ); $padded = $padding->apply(row(str_entry('name', 'x'), int_entry('id', 1))); @@ -68,7 +68,7 @@ public function test_present_columns_are_kept_as_written(): void { $padding = RowPadding::forFileSchema( schema(int_schema('id'), str_schema('name')), - new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()), + new SchemaDecoder(new ValueDecoder(), new Instantiators()), ); $original = row(int_entry('id', 1), str_entry('name', 'x')); diff --git a/src/core/etl/tests/Flow/Floe/Tests/Unit/SchemaDecoderTest.php b/src/core/etl/tests/Flow/Floe/Tests/Unit/SchemaDecoderTest.php index 9fcc301f31..bf80cce44a 100644 --- a/src/core/etl/tests/Flow/Floe/Tests/Unit/SchemaDecoderTest.php +++ b/src/core/etl/tests/Flow/Floe/Tests/Unit/SchemaDecoderTest.php @@ -4,9 +4,9 @@ namespace Flow\Floe\Tests\Unit; +use Flow\ETL\Row\Entry\Instantiators; use Flow\ETL\Row\Entry\StringEntry; use Flow\ETL\Schema\Metadata; -use Flow\Floe\EntryInstantiator; use Flow\Floe\Exception\FloeException; use Flow\Floe\FloeWriter; use Flow\Floe\SchemaDecoder; @@ -23,7 +23,7 @@ public function test_decoded_plan_provides_nullable_and_from_null_definition_var { $schemaJson = FloeWriter::growSectionPlan(null, row(str_entry('name', 'x')))->schemaBody; - $plan = (new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()))->decode($schemaJson); + $plan = (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode($schemaJson); static::assertCount(1, $plan); static::assertSame('name', $plan[0]->name); @@ -38,7 +38,7 @@ public function test_decoded_plan_strips_from_null_marker_from_base_definition() { $schemaJson = FloeWriter::growSectionPlan(null, row(StringEntry::fromNull('name')))->schemaBody; - $plan = (new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()))->decode($schemaJson); + $plan = (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode($schemaJson); static::assertFalse($plan[0]->definition->metadata()->has(Metadata::FROM_NULL)); static::assertTrue($plan[0]->fromNullDefinition->metadata()->has(Metadata::FROM_NULL)); @@ -50,7 +50,7 @@ public function test_decoded_plan_preserves_custom_metadata(): void 'custom' => 'meta', ]))))->schemaBody; - $plan = (new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()))->decode($schemaJson); + $plan = (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode($schemaJson); static::assertSame('meta', $plan[0]->definition->metadata()->get('custom')); } @@ -60,7 +60,7 @@ public function test_decoding_invalid_json_throws(): void $this->expectException(FloeException::class); $this->expectExceptionMessage('failed to decode schema JSON'); - (new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()))->decode('{invalid'); + (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode('{invalid'); } public function test_decoding_non_list_json_throws(): void @@ -68,6 +68,6 @@ public function test_decoding_non_list_json_throws(): void $this->expectException(FloeException::class); $this->expectExceptionMessage('expected schema JSON to be a list'); - (new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()))->decode('"scalar"'); + (new SchemaDecoder(new ValueDecoder(), new Instantiators()))->decode('"scalar"'); } } diff --git a/src/extension/flow-php-ext/src/ctx.rs b/src/extension/flow-php-ext/src/ctx.rs index b9b88dfb80..b9f770298e 100644 --- a/src/extension/flow-php-ext/src/ctx.rs +++ b/src/extension/flow-php-ext/src/ctx.rs @@ -750,20 +750,20 @@ impl Ctx { vec![], "Flow\\Floe\\ValueDecoder", )?; - // EntryInstantiator declares no constructor; defaults apply on init. - let mut entry_instantiator = - ZendObject::new(find_class("Flow\\Floe\\EntryInstantiator")?); + // Instantiators declares no constructor; defaults apply on init. + let mut instantiators = + ZendObject::new(find_class("Flow\\ETL\\Row\\Entry\\Instantiators")?); let mut value_decoder_zv = Zval::new(); value_decoder_zv.set_object(&mut value_decoder); - let mut entry_instantiator_zv = Zval::new(); - entry_instantiator_zv.set_object(&mut entry_instantiator); + let mut instantiators_zv = Zval::new(); + instantiators_zv.set_object(&mut instantiators); self.schema_decoder = Some(construct_object( find_class("Flow\\Floe\\SchemaDecoder")?, vec![ &value_decoder_zv as &dyn IntoZvalDyn, - &entry_instantiator_zv as &dyn IntoZvalDyn, + &instantiators_zv as &dyn IntoZvalDyn, ], "Flow\\Floe\\SchemaDecoder", )?); diff --git a/src/extension/flow-php-ext/tests/phpt/bootstrap.php b/src/extension/flow-php-ext/tests/phpt/bootstrap.php index 97ed8002ee..fadcc561e2 100644 --- a/src/extension/flow-php-ext/tests/phpt/bootstrap.php +++ b/src/extension/flow-php-ext/tests/phpt/bootstrap.php @@ -5,8 +5,8 @@ require __DIR__ . '/../../../../../vendor/autoload.php'; use Flow\ETL\Row; +use Flow\ETL\Row\Entry\Instantiators; use Flow\ETL\Rows; -use Flow\Floe\EntryInstantiator; use Flow\Floe\FloeWriter; use Flow\Floe\Format; use Flow\Floe\RowEncoder; @@ -52,7 +52,7 @@ function php_frames(Rows $rows): array */ function php_decode_frames(array $frames): array { - $schemaDecoder = new SchemaDecoder(new ValueDecoder(), new EntryInstantiator()); + $schemaDecoder = new SchemaDecoder(new ValueDecoder(), new Instantiators()); $rowHydrator = new RowHydrator(); $plan = null; $rows = []; diff --git a/src/lib/types/src/Flow/Types/Type/Logical/ListType.php b/src/lib/types/src/Flow/Types/Type/Logical/ListType.php index c574c62b7a..112e441bbc 100644 --- a/src/lib/types/src/Flow/Types/Type/Logical/ListType.php +++ b/src/lib/types/src/Flow/Types/Type/Logical/ListType.php @@ -64,10 +64,6 @@ public function assert(mixed $value): array public function cast(mixed $value): array { - if ($this->isValid($value)) { - return $value; - } - try { if ($value instanceof Json) { $value = $value->toArray(); diff --git a/src/lib/types/src/Flow/Types/Type/Logical/StructureType.php b/src/lib/types/src/Flow/Types/Type/Logical/StructureType.php index 25235f4618..12c2352524 100644 --- a/src/lib/types/src/Flow/Types/Type/Logical/StructureType.php +++ b/src/lib/types/src/Flow/Types/Type/Logical/StructureType.php @@ -122,10 +122,6 @@ public function assert(mixed $value): array public function cast(mixed $value): array { - if ($this->isValid($value)) { - return $value; - } - try { if ($value instanceof Json) { $value = $value->toArray(); diff --git a/src/lib/types/src/Flow/Types/Value/Uuid.php b/src/lib/types/src/Flow/Types/Value/Uuid.php index 0c69127fc3..ca29283897 100644 --- a/src/lib/types/src/Flow/Types/Value/Uuid.php +++ b/src/lib/types/src/Flow/Types/Value/Uuid.php @@ -6,13 +6,10 @@ use Flow\Types\Exception\InvalidArgumentException; use Flow\Types\Exception\RuntimeException; -use InvalidArgumentException as BaseInvalidArgumentException; -use Ramsey\Uuid\Uuid as RamseyUuid; use Ramsey\Uuid\UuidInterface; use Stringable; use Symfony\Component\Uid\Uuid as SymfonyUuid; -use function class_exists; use function is_string; use function preg_match; use function strlen; @@ -33,20 +30,10 @@ public function __construct(string|UuidInterface|SymfonyUuid $value) { if (is_string($value)) { - try { - if (class_exists(RamseyUuid::class)) { - $this->value = (string) RamseyUuid::fromString($value); - } elseif (class_exists(SymfonyUuid::class)) { - $this->value = SymfonyUuid::fromString($value)->toRfc4122(); - } elseif (self::isValid($value)) { - $this->value = $value; - } else { - throw new RuntimeException( - "RamseyUuid nor SymfonyUuid class not found, please add 'ramsey/uuid' or 'symfony/uid' as a dependency to the project first.", - ); - } - } catch (BaseInvalidArgumentException $e) { - throw new InvalidArgumentException("Invalid UUID: '{$value}'", (int) $e->getCode(), $e); + if (self::isValid($value)) { + $this->value = $value; + } else { + throw new InvalidArgumentException("Invalid UUID: '{$value}'"); } } elseif ($value instanceof UuidInterface) { $this->value = $value->toString();