Skip to content

Commit 8e270a8

Browse files
committed
Use appropriate type hinting to the SnakeCaseToCamelCaseConverter
1 parent 868336a commit 8e270a8

File tree

3 files changed

+109
-19
lines changed

3 files changed

+109
-19
lines changed

src/Firebase/Valinor/Converter/SnakeCaseToCamelCaseConverter.php

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,57 @@
44

55
namespace Kreait\Firebase\Valinor\Converter;
66

7-
use Traversable;
8-
97
/**
108
* @internal
11-
*
12-
* @see https://valinor.cuyz.io/latest/how-to/convert-input/#converting-keys-format-from-snake_case-to-camelcase
139
*/
1410
final class SnakeCaseToCamelCaseConverter
1511
{
16-
public function __invoke(mixed $values, callable $next): object
12+
/**
13+
* @pure
14+
*
15+
* @param iterable<mixed> $source
16+
* @param pure-callable(iterable<mixed>): iterable<mixed> $next
17+
*
18+
* @return iterable<mixed>
19+
*/
20+
public function __invoke(iterable $source, callable $next): iterable
1721
{
18-
if ($values instanceof Traversable) {
19-
$values = iterator_to_array($values);
20-
}
22+
return $next(self::camelCase($source));
23+
}
24+
25+
/**
26+
* @pure
27+
*
28+
* @param iterable<mixed> $source
29+
*
30+
* @return array<array-key, mixed>
31+
*/
32+
private static function camelCase(iterable $source): array
33+
{
34+
$result = [];
2135

22-
if (!is_array($values)) {
23-
return $next($values);
36+
foreach ($source as $key => $value) {
37+
if (is_iterable($value)) {
38+
$value = self::camelCase($value);
39+
}
40+
41+
if (is_string($key) && $key !== '') {
42+
$key = self::convert($key);
43+
}
44+
45+
$result[$key] = $value;
2446
}
2547

26-
$camelCaseConverted = array_combine(
27-
array_map(
28-
fn($key): string => lcfirst(str_replace('_', '', ucwords((string) $key, '_'))),
29-
array_keys($values),
30-
),
31-
$values,
32-
);
48+
return $result;
49+
}
3350

34-
return $next($camelCaseConverted);
51+
/**
52+
* @pure
53+
*
54+
* @param non-empty-string $key
55+
*/
56+
private static function convert(string $key): string
57+
{
58+
return lcfirst(str_replace('_', '', ucwords($key, '_'))); // @phpstan-ignore possiblyImpure.functionCall
3559
}
3660
}

src/Firebase/Valinor/Mapper.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ public function __construct(private readonly mixed $cache = null, ?MapperBuilder
2727
$this->mapperBuilder = $builder;
2828
}
2929

30+
/**
31+
* @param pure-callable $converter
32+
*/
3033
public function withConverter(callable $converter): self
3134
{
32-
$mapperBuilder = $this->mapperBuilder->registerConverter($converter); // @phpstan-ignore-line argument.type
35+
$mapperBuilder = $this->mapperBuilder->registerConverter($converter);
3336

3437
return new self($this->cache, $mapperBuilder);
3538
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Kreait\Firebase\Tests\Unit\Valinor\Converter;
6+
7+
use CuyZ\Valinor\MapperBuilder;
8+
use Kreait\Firebase\Tests\UnitTestCase;
9+
use Kreait\Firebase\Valinor\Converter\SnakeCaseToCamelCaseConverter;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\Attributes\Test;
12+
13+
class SnakeCaseToCamelCaseConverterTest extends UnitTestCase
14+
{
15+
/**
16+
* @param iterable<mixed> $expected
17+
* @param non-empty-string $signature
18+
* @param iterable<mixed> $input
19+
*/
20+
#[DataProvider('dataProvider')]
21+
#[Test]
22+
public function itWorks(iterable $expected, string $signature, iterable $input): void
23+
{
24+
$mapper = (new MapperBuilder())
25+
->registerConverter(new SnakeCaseToCamelCaseConverter())
26+
->mapper()
27+
;
28+
29+
$result = $mapper->map($signature, $input);
30+
31+
$this->assertSame($expected, $result);
32+
}
33+
34+
/**
35+
* @return iterable<mixed>
36+
*/
37+
public static function dataProvider(): iterable
38+
{
39+
yield 'flat' => [
40+
['firstKey' => 'first_value'],
41+
'array<string, string>',
42+
['first_key' => 'first_value'],
43+
];
44+
45+
yield 'nested' => [
46+
[
47+
'firstKey' => 'first_value',
48+
'secondKey' => [
49+
'firstSubKey' => 'first_sub_value',
50+
'secondSubKey' => 'second_sub_value',
51+
],
52+
],
53+
'array<non-empty-string, non-empty-string|array<non-empty-string, non-empty-string>>',
54+
[
55+
'first_key' => 'first_value',
56+
'second_key' => [
57+
'first_sub_key' => 'first_sub_value',
58+
'second_sub_key' => 'second_sub_value',
59+
],
60+
],
61+
];
62+
}
63+
}

0 commit comments

Comments
 (0)