Skip to content

Commit 6b3bbe1

Browse files
authored
Added transformers to serialize/unserialize entire row into/from entry (#1358)
1 parent 79cdd40 commit 6b3bbe1

File tree

6 files changed

+318
-2
lines changed

6 files changed

+318
-2
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Transformer;
6+
7+
use function Flow\ETL\DSL\{ref, row, str_entry};
8+
use Flow\ETL\Row\Reference;
9+
use Flow\ETL\{FlowContext, Row, Rows, Transformer};
10+
11+
final readonly class SerializeTransformer implements Transformer
12+
{
13+
public function __construct(private Reference|string $target, private bool $standalone = false)
14+
{
15+
}
16+
17+
public function transform(Rows $rows, FlowContext $context) : Rows
18+
{
19+
$target = $this->target instanceof Reference ? $this->target : ref($this->target);
20+
21+
return $rows->map(
22+
fn (Row $row) => $this->standalone
23+
? row(str_entry($target->name(), $context->config->serializer()->serialize($row)))
24+
: $row->add(str_entry($target->name(), $context->config->serializer()->serialize($row)))
25+
);
26+
}
27+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Transformer;
6+
7+
use function Flow\ETL\DSL\ref;
8+
use Flow\ETL\Row\Reference;
9+
use Flow\ETL\{FlowContext, Row, Rows, Transformer};
10+
use Flow\Serializer\Exception\SerializationException;
11+
12+
final readonly class UnserializeTransformer implements Transformer
13+
{
14+
/**
15+
* @param Reference|string $source
16+
* @param bool $merge
17+
* @param string $mergePrefix - used only when merge is set to true
18+
*/
19+
public function __construct(private Reference|string $source, private bool $merge = true, private string $mergePrefix = '')
20+
{
21+
}
22+
23+
public function transform(Rows $rows, FlowContext $context) : Rows
24+
{
25+
$source = $this->source instanceof Reference ? $this->source : ref($this->source);
26+
27+
return $rows->map(
28+
function (Row $row) use ($source, $context) : Row {
29+
if (!$row->has($source->name())) {
30+
return $row;
31+
}
32+
33+
$serialized = $row->valueOf($source->name());
34+
35+
if (!\is_string($serialized)) {
36+
return $row;
37+
}
38+
39+
try {
40+
return $this->merge
41+
? $row->merge($context->config->serializer()->unserialize($serialized, [Row::class]), $this->mergePrefix)
42+
: $context->config->serializer()->unserialize($serialized, [Row::class]);
43+
} catch (SerializationException) {
44+
return $row;
45+
}
46+
}
47+
);
48+
}
49+
}

src/core/etl/src/Flow/Serializer/Base64Serializer.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Flow\Serializer;
66

7+
use Flow\Serializer\Exception\SerializationException;
8+
79
final readonly class Base64Serializer implements Serializer
810
{
911
public function __construct(private Serializer $serializer)
@@ -17,7 +19,12 @@ public function serialize(object $serializable) : string
1719

1820
public function unserialize(string $serialized, array $classes) : object
1921
{
20-
/** @phpstan-ignore-next-line */
21-
return $this->serializer->unserialize(\base64_decode($serialized, true), $classes);
22+
$decodedString = \base64_decode($serialized, true);
23+
24+
if ($decodedString === false) {
25+
throw new SerializationException('Base64Serializer::unserialize failed to decode string');
26+
}
27+
28+
return $this->serializer->unserialize($decodedString, $classes);
2229
}
2330
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\Serializer\Exception;
6+
7+
use Flow\ETL\Exception\Exception;
8+
9+
final class SerializationException extends Exception
10+
{
11+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Tests\Unit\Transformer;
6+
7+
use function Flow\ETL\DSL\{bool_entry, flow_context, int_entry, list_entry, row, rows, str_entry, type_list, type_string};
8+
use Flow\ETL\Tests\FlowTestCase;
9+
use Flow\ETL\Transformer\SerializeTransformer;
10+
use Flow\Serializer\{Base64Serializer, NativePHPSerializer};
11+
12+
final class SerializeTransformerTest extends FlowTestCase
13+
{
14+
public function test_serializing_empty_row_under_one_entry() : void
15+
{
16+
$rows = rows(
17+
$row1 = row(),
18+
);
19+
20+
$transformer = new SerializeTransformer('serialized');
21+
$transformedRows = $transformer->transform($rows, flow_context());
22+
23+
self::assertEquals(
24+
[
25+
[
26+
'serialized' => (new Base64Serializer(new NativePHPSerializer()))->serialize($row1),
27+
],
28+
],
29+
$transformedRows->toArray(),
30+
);
31+
}
32+
33+
public function test_serializing_row_under_one_entry() : void
34+
{
35+
$rows = rows(
36+
$row1 = row(
37+
int_entry('id', 1),
38+
str_entry('name', 'John'),
39+
bool_entry('active', true),
40+
list_entry('tags', ['tag1', 'tag2'], type_list(type_string())),
41+
),
42+
$row2 = row(
43+
int_entry('id', 2),
44+
str_entry('name', 'Jane'),
45+
bool_entry('active', false),
46+
list_entry('tags', ['tag3', 'tag4'], type_list(type_string())),
47+
),
48+
);
49+
50+
$transformer = new SerializeTransformer('serialized');
51+
52+
$transformedRows = $transformer->transform($rows, flow_context());
53+
54+
self::assertEquals(
55+
[
56+
[
57+
'id' => 1,
58+
'name' => 'John',
59+
'active' => true,
60+
'tags' => ['tag1', 'tag2'],
61+
'serialized' => (new Base64Serializer(new NativePHPSerializer()))->serialize($row1),
62+
],
63+
[
64+
'id' => 2,
65+
'name' => 'Jane',
66+
'active' => false,
67+
'tags' => ['tag3', 'tag4'],
68+
'serialized' => (new Base64Serializer(new NativePHPSerializer()))->serialize($row2),
69+
],
70+
],
71+
$transformedRows->toArray(),
72+
);
73+
}
74+
75+
public function test_serializing_row_under_standalone_entry() : void
76+
{
77+
$rows = rows(
78+
$row1 = row(
79+
int_entry('id', 1),
80+
str_entry('name', 'John'),
81+
bool_entry('active', true),
82+
list_entry('tags', ['tag1', 'tag2'], type_list(type_string())),
83+
),
84+
$row2 = row(
85+
int_entry('id', 2),
86+
str_entry('name', 'Jane'),
87+
bool_entry('active', false),
88+
list_entry('tags', ['tag3', 'tag4'], type_list(type_string())),
89+
),
90+
);
91+
92+
$transformer = new SerializeTransformer('serialized', true);
93+
94+
$transformedRows = $transformer->transform($rows, flow_context());
95+
96+
self::assertEquals(
97+
[
98+
[
99+
'serialized' => (new Base64Serializer(new NativePHPSerializer()))->serialize($row1),
100+
],
101+
[
102+
'serialized' => (new Base64Serializer(new NativePHPSerializer()))->serialize($row2),
103+
],
104+
],
105+
$transformedRows->toArray(),
106+
);
107+
}
108+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flow\ETL\Tests\Unit\Transformer;
6+
7+
use function Flow\ETL\DSL\{bool_entry, flow_context, int_entry, list_entry, row, rows, str_entry, type_list, type_string};
8+
use Flow\ETL\Tests\FlowTestCase;
9+
use Flow\ETL\Transformer\UnserializeTransformer;
10+
use Flow\Serializer\{Base64Serializer, NativePHPSerializer};
11+
12+
final class UnserializeTransformerTest extends FlowTestCase
13+
{
14+
public function test_unserializing_row_from_entry() : void
15+
{
16+
$row1 = row(
17+
int_entry('id', 1),
18+
str_entry('name', 'John'),
19+
bool_entry('active', true),
20+
list_entry('tags', ['tag1', 'tag2'], type_list(type_string())),
21+
);
22+
$row2 = row(
23+
int_entry('id', 2),
24+
str_entry('name', 'Jane'),
25+
bool_entry('active', false),
26+
list_entry('tags', ['tag3', 'tag4'], type_list(type_string())),
27+
);
28+
29+
$rows = rows(
30+
row(str_entry('serialized', (new Base64Serializer(new NativePHPSerializer()))->serialize($row1))),
31+
row(str_entry('serialized', (new Base64Serializer(new NativePHPSerializer()))->serialize($row2))),
32+
);
33+
34+
$transformer = new UnserializeTransformer('serialized');
35+
36+
$transformedRows = $transformer->transform($rows, flow_context());
37+
38+
self::assertEquals(
39+
[
40+
[
41+
'serialized' => (new Base64Serializer(new NativePHPSerializer()))->serialize($row1),
42+
'id' => 1,
43+
'name' => 'John',
44+
'active' => true,
45+
'tags' => ['tag1', 'tag2'],
46+
],
47+
[
48+
'serialized' => (new Base64Serializer(new NativePHPSerializer()))->serialize($row2),
49+
'id' => 2,
50+
'name' => 'Jane',
51+
'active' => false,
52+
'tags' => ['tag3', 'tag4'],
53+
],
54+
],
55+
$transformedRows->toArray(),
56+
);
57+
}
58+
59+
public function test_unserializing_something_that_is_not_serialized_row() : void
60+
{
61+
$rows = rows(
62+
row(str_entry('serialized', 'not-serialized')),
63+
);
64+
65+
$transformer = new UnserializeTransformer('serialized');
66+
67+
$transformedRows = $transformer->transform($rows, flow_context());
68+
69+
self::assertEquals($rows, $transformedRows);
70+
}
71+
72+
public function test_unserializing_without_merge() : void
73+
{
74+
$row1 = row(
75+
int_entry('id', 1),
76+
str_entry('name', 'John'),
77+
bool_entry('active', true),
78+
list_entry('tags', ['tag1', 'tag2'], type_list(type_string())),
79+
);
80+
$row2 = row(
81+
int_entry('id', 2),
82+
str_entry('name', 'Jane'),
83+
bool_entry('active', false),
84+
list_entry('tags', ['tag3', 'tag4'], type_list(type_string())),
85+
);
86+
87+
$rows = rows(
88+
row(str_entry('serialized', (new Base64Serializer(new NativePHPSerializer()))->serialize($row1))),
89+
row(str_entry('serialized', (new Base64Serializer(new NativePHPSerializer()))->serialize($row2))),
90+
);
91+
92+
$transformer = new UnserializeTransformer('serialized', false);
93+
94+
$transformedRows = $transformer->transform($rows, flow_context());
95+
96+
self::assertEquals(
97+
[
98+
[
99+
'id' => 1,
100+
'name' => 'John',
101+
'active' => true,
102+
'tags' => ['tag1', 'tag2'],
103+
],
104+
[
105+
'id' => 2,
106+
'name' => 'Jane',
107+
'active' => false,
108+
'tags' => ['tag3', 'tag4'],
109+
],
110+
],
111+
$transformedRows->toArray(),
112+
);
113+
}
114+
}

0 commit comments

Comments
 (0)