Skip to content

Commit ad67e98

Browse files
authored
Merge pull request #6 from veewee/normalize-object-names
Normalize property names to supported php names in the object encoder
2 parents df563b0 + 925a842 commit ad67e98

File tree

4 files changed

+112
-3
lines changed

4 files changed

+112
-3
lines changed

src/Encoder/ObjectEncoder.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Soap\Encoding\Encoder;
55

66
use Closure;
7+
use Soap\Encoding\Normalizer\PhpPropertyNameNormalizer;
78
use Soap\Encoding\TypeInference\ComplexTypeBuilder;
89
use Soap\Encoding\TypeInference\XsiTypeDetector;
910
use Soap\Encoding\Xml\Reader\DocumentToLookupArrayReader;
@@ -16,6 +17,7 @@
1617
use VeeWee\Reflecta\Iso\Iso;
1718
use VeeWee\Reflecta\Lens\Lens;
1819
use function Psl\Dict\map;
20+
use function Psl\Dict\pull;
1921
use function Psl\Dict\reindex;
2022
use function Psl\Iter\any;
2123
use function Psl\Vec\sort_by;
@@ -97,7 +99,10 @@ private function to(Context $context, array $properties, object|array $data): st
9799
$properties,
98100
function (Property $property) use ($context, $data, $defaultAction) : Closure {
99101
$type = $property->getType();
100-
$lens = $this->decorateLensForType(property($property->getName()), $type);
102+
$lens = $this->decorateLensForType(
103+
property(PhpPropertyNameNormalizer::normalize($property->getName())),
104+
$type
105+
);
101106
/**
102107
* @psalm-var mixed $value
103108
* @psalm-suppress PossiblyInvalidArgument - Psalm gets lost in the lens.
@@ -136,7 +141,7 @@ private function from(Context $context, array $properties, string $data): object
136141
$nodes = (new DocumentToLookupArrayReader())($data);
137142

138143
return object_data($this->className)->from(
139-
map(
144+
pull(
140145
$properties,
141146
function (Property $property) use ($context, $nodes): mixed {
142147
$type = $property->getType();
@@ -158,7 +163,8 @@ function (Property $property) use ($context, $nodes): mixed {
158163
onValue: fn (): mixed => $value !== null ? $this->grabIsoForProperty($context, $property)->from($value) : $defaultValue,
159164
onElements: fn (): mixed => $value !== null ? $this->grabIsoForProperty($context, $property)->from($value) : $defaultValue,
160165
);
161-
}
166+
},
167+
static fn (Property $property) => PhpPropertyNameNormalizer::normalize($property->getName()),
162168
)
163169
);
164170
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Normalizer;
5+
6+
use function Psl\Type\non_empty_string;
7+
8+
final class PhpPropertyNameNormalizer
9+
{
10+
public static function normalize(string $name): string
11+
{
12+
return self::camelCase($name, '{[^a-z0-9_]+}i');
13+
}
14+
15+
/**
16+
* @param literal-string $regexp
17+
*/
18+
private static function camelCase(string $word, string $regexp):string
19+
{
20+
$parts = array_filter(preg_split($regexp, $word));
21+
$keepUnchanged = array_shift($parts);
22+
$parts = array_map('ucfirst', $parts);
23+
array_unshift($parts, $keepUnchanged);
24+
25+
return non_empty_string()->assert(
26+
implode('', $parts)
27+
);
28+
}
29+
}

tests/Unit/Encoder/ObjectEncoderTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,47 @@ public static function provideIsomorphicCases(): iterable
125125
'xml' => '<tns:user xmlns:tns="https://test"><tns:active xmlns:tns="https://test">true</tns:active><tns:hat xmlns:tns="https://test"><tns:color xmlns:tns="https://test">green</tns:color></tns:hat></tns:user>',
126126
'data' => new User(active: true, hat: new Hat('green')),
127127
];
128+
129+
yield 'unsupported-property-chars' => [
130+
...$baseConfig,
131+
'context' => self::createContext(
132+
$xsdType,
133+
new TypeCollection(
134+
new Type(
135+
XsdType::create('user')
136+
->withXmlTypeName('user')
137+
->withXmlNamespace("https://test")
138+
->withXmlNamespaceName('test')
139+
->withXmlTargetNodeName('user')
140+
->withMeta(
141+
static fn (TypeMeta $meta): TypeMeta => $meta
142+
->withIsQualified(true)
143+
->withIsElement(true)
144+
),
145+
new PropertyCollection(
146+
new Property(
147+
'bon-jour',
148+
XsdType::create('bon-jour')
149+
->withXmlTypeName('boolean')
150+
->withXmlTargetNodeName('bon-jour')
151+
->withXmlNamespace(Xmlns::xsd()->value())
152+
->withXmlNamespaceName('xsd')
153+
->withMeta(
154+
static fn (TypeMeta $meta): TypeMeta => $meta
155+
->withIsSimple(true)
156+
->withIsElement(true)
157+
->withIsQualified(true)
158+
)
159+
),
160+
)
161+
)
162+
)
163+
),
164+
'xml' => '<user><bon-jour>true</bon-jour></user>',
165+
'data' => (object)[
166+
'bonJour' => true,
167+
],
168+
];
128169
}
129170

130171

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Soap\Encoding\Test\Unit\Normalizer;
5+
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use PHPUnit\Framework\TestCase;
8+
use Soap\Encoding\Normalizer\PhpPropertyNameNormalizer;
9+
10+
#[CoversClass(PhpPropertyNameNormalizer::class)]
11+
final class PhpPropertyNameNormalizerTest extends TestCase
12+
{
13+
14+
/**
15+
*
16+
* @dataProvider provideCases
17+
*/
18+
public function test_it_can_normalize(string $in, string $expected): void
19+
{
20+
static::assertEquals($expected, PhpPropertyNameNormalizer::normalize($in));
21+
}
22+
23+
public static function provideCases()
24+
{
25+
yield ['prop1', 'prop1'];
26+
yield ['final', 'final'];
27+
yield ['Final', 'Final'];
28+
yield ['UpperCased', 'UpperCased'];
29+
yield ['my-./*prop_123', 'myProp_123'];
30+
yield ['My-./*prop_123', 'MyProp_123'];
31+
yield ['My-./final*prop_123', 'MyFinalProp_123'];
32+
}
33+
}

0 commit comments

Comments
 (0)