Skip to content

Commit 3faf950

Browse files
committed
Generate backed enums from XSD
1 parent 25a2f2e commit 3faf950

30 files changed

+815
-107
lines changed

UPGRADING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ Change the engine inside your (generated) ClientFactory:
5454
$engine = DefaultEngineFactory::create(
5555
EngineOptions::defaults($wsdl)
5656
->withEncoderRegistry(
57-
EncoderRegistry::default()->addClassMapCollection(
58-
CalcClassmap::getCollection()
59-
)
57+
EncoderRegistry::default()
58+
->addClassMapCollection(CalcClassmap::types())
59+
->addBackedEnumClassMapCollection(CalcClassmap::enums())
6060
)
6161
// If you want to enable WSDL caching:
6262
// ->withCache($yourPsr6CachePool)

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"laminas/laminas-code": "^4.14.0",
1818
"php-soap/cached-engine": "~0.3",
1919
"php-soap/engine": "^2.14.0",
20-
"php-soap/encoding": "~0.14",
20+
"php-soap/encoding": "~0.15",
2121
"php-soap/psr18-transport": "^1.7",
22-
"php-soap/wsdl-reader": "~0.20",
22+
"php-soap/wsdl-reader": "~0.21",
2323
"psr/event-dispatcher": "^1.0",
2424
"psr/log": "^1.0 || ^2.0 || ^3.0",
2525
"symfony/console": "~5.4 || ~6.0 || ~7.0",

docs/code-generation/configuration.md

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@ The code generation commands require a configuration file to determine how the S
66
<?php
77
// my-soap-config.php
88

9-
use Phpro\SoapClient\CodeGenerator\Config\Config;
10-
use Phpro\SoapClient\CodeGenerator\Rules;
119
use Phpro\SoapClient\CodeGenerator\Assembler;
12-
use Phpro\SoapClient\Soap\DefaultEngineFactory;
10+
use Phpro\SoapClient\CodeGenerator\Rules;
11+
use Phpro\SoapClient\CodeGenerator\Config\Config;
1312
use Phpro\SoapClient\Soap\EngineOptions;
14-
use Phpro\SoapClient\Soap\Metadata\Manipulators\DuplicateTypes\IntersectDuplicateTypesStrategy;
15-
use Phpro\SoapClient\Soap\Metadata\MetadataOptions;
16-
use Soap\Wsdl\Loader\FlatteningLoader;
17-
use Soap\Wsdl\Loader\StreamWrapperLoader;
13+
use Phpro\SoapClient\Soap\DefaultEngineFactory;
1814

1915
return Config::create()
2016
->setEngine(DefaultEngineFactory::create(
21-
EngineOptions::defaults('wsdl.xml')
17+
EngineOptions::defaults($wsdl)
2218
->withWsdlLoader(new FlatteningLoader(new StreamWrapperLoader()))
19+
->withEncoderRegistry(
20+
EncoderRegistry::default()
21+
->addClassMapCollection(SomeClassmap::types())
22+
->addBackedEnumClassMapCollection(SomeClassmap::enums())
23+
)
2324
))
2425
->setTypeDestination('src/SoapTypes')
2526
->setTypeNamespace('SoapTypes')
@@ -29,23 +30,39 @@ return Config::create()
2930
->setClassMapNamespace('Acme\\Classmap')
3031
->setClassMapDestination('src/acme/classmap')
3132
->setClassMapName('AcmeClassmap')
32-
->setTypeMetadataOptions(
33-
MetadataOptions::empty()
34-
->withTypesManipulator(new IntersectDuplicateTypesStrategy())
35-
)
36-
->addRule(new Rules\AssembleRule(new Assembler\GetterAssembler(
37-
(new Assembler\GetterAssemblerOptions())
38-
->withReturnType()
39-
->withBoolGetters()
33+
->addRule(new Rules\AssembleRule(new Assembler\GetterAssembler(new Assembler\GetterAssemblerOptions())))
34+
->addRule(new Rules\AssembleRule(new Assembler\ImmutableSetterAssembler(
35+
new Assembler\ImmutableSetterAssemblerOptions()
4036
)))
41-
->addRule(new Rules\TypenameMatchesRule(
42-
new Rules\AssembleRule(new Assembler\RequestAssembler()),
43-
'/Request$/'
44-
))
45-
->addRule(new Rules\TypenameMatchesRule(
46-
new Rules\AssembleRule(new Assembler\ResultAssembler()),
47-
'/Response$/'
48-
))
37+
->addRule(
38+
new Rules\IsRequestRule(
39+
$engine->getMetadata(),
40+
new Rules\MultiRule([
41+
new Rules\AssembleRule(new Assembler\RequestAssembler()),
42+
new Rules\AssembleRule(new Assembler\ConstructorAssembler(new Assembler\ConstructorAssemblerOptions())),
43+
])
44+
)
45+
)
46+
->addRule(
47+
new Rules\IsResultRule(
48+
$engine->getMetadata(),
49+
new Rules\MultiRule([
50+
new Rules\AssembleRule(new Assembler\ResultAssembler()),
51+
])
52+
)
53+
)
54+
->addRule(
55+
new Rules\IsExtendingTypeRule(
56+
$engine->getMetadata(),
57+
new Rules\AssembleRule(new Assembler\ExtendingTypeAssembler())
58+
)
59+
)
60+
->addRule(
61+
new Rules\IsAbstractTypeRule(
62+
$engine->getMetadata(),
63+
new Rules\AssembleRule(new Assembler\AbstractClassAssembler())
64+
)
65+
)
4966
;
5067
```
5168

@@ -64,6 +81,29 @@ and provide additional options like the preferred SOAP version.
6481

6582
[Read more about engines.](https://github.com/php-soap/engine)
6683

84+
```php
85+
use Phpro\SoapClient\Soap\EngineOptions;
86+
use Phpro\SoapClient\Soap\DefaultEngineFactory;
87+
88+
DefaultEngineFactory::create(
89+
EngineOptions::defaults($wsdl)
90+
->withWsdlLoader(new FlatteningLoader(new StreamWrapperLoader()))
91+
->withEncoderRegistry(
92+
EncoderRegistry::default()
93+
->addClassMapCollection(SomeClassmap::types())
94+
->addBackedEnumClassMapCollection(SomeClassmap::enums())
95+
)
96+
// If you want to enable WSDL caching:
97+
// ->withCache()
98+
// If you want to use Alternate HTTP settings:
99+
// ->withWsdlLoader()
100+
// ->withTransport()
101+
// If you want specific SOAP setting:
102+
// ->withWsdlParserContext()
103+
// ->withWsdlServiceSelectionCriteria()
104+
);
105+
```
106+
67107
**type destination**
68108

69109
String - REQUIRED
@@ -128,3 +168,43 @@ Config::create()
128168
)
129169
)
130170
```
171+
172+
**Metadata manipulations**
173+
174+
The metadata manipulations are a set of strategies that can be applied to the metadata before the code generation starts.
175+
You can read more about this in the documentation in the section [metadata](../drivers/metadata.md).
176+
177+
Examples:
178+
179+
```php
180+
use Phpro\SoapClient\CodeGenerator\Config\Config;
181+
use Phpro\SoapClient\Soap\Metadata\Manipulators\DuplicateTypes\IntersectDuplicateTypesStrategy;
182+
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacers;
183+
184+
Config::create()
185+
->setDuplicateTypeIntersectStrategy(new IntersectDuplicateTypesStrategy())
186+
->setTypeReplacementStrategy(TypeReplacers::defaults()->add(new MyDateReplacer()));
187+
```
188+
189+
**Enumeration options**
190+
191+
You can configure how the code generator deals with XSD enumeration types.
192+
There are 2 type of XSD enumerations:
193+
194+
- `global`: Are available as a global simpletype inside the XSD.
195+
- `local`: Are configured as an internal type on an element or attribute and don't really have a name.
196+
197+
The default behavior is to generate a PHP Enum for global enumerations only because
198+
We want to avoid naming conflicts with other types for local enumerations.
199+
200+
It is possible to opt-in into using these local enumerations as well:
201+
202+
```php
203+
use Phpro\SoapClient\CodeGenerator\Config\Config;
204+
use Phpro\SoapClient\CodeGenerator\Config\EnumerationGenerationStrategy;
205+
206+
Config::create()
207+
->setEnumerationGenerationStrategy(EnumerationGenerationStrategy::LocalAndGlobal);
208+
```
209+
210+
**Note**: This will dynamically add some extra type replacements and type manipulations to the metadata before the code generation starts.

spec/Phpro/SoapClient/CodeGenerator/Util/NormalizerSpec.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@ function it_noramizes_properties()
6060
$this->normalizeProperty('My-./final*prop_123')->shouldReturn('MyFinalProp_123');
6161
}
6262

63+
function it_normalizes_enum_cases()
64+
{
65+
$this->normalizeEnumCaseName('')->shouldReturn('Empty');
66+
67+
$this->normalizeEnumCaseName('-1')->shouldReturn('Value_Minus_1');
68+
$this->normalizeEnumCaseName('0')->shouldReturn('Value_0');
69+
$this->normalizeEnumCaseName('1')->shouldReturn('Value_1');
70+
$this->normalizeEnumCaseName('10000')->shouldReturn('Value_10000');
71+
72+
$this->normalizeEnumCaseName('final')->shouldReturn('final');
73+
$this->normalizeEnumCaseName('Final')->shouldReturn('Final');
74+
$this->normalizeEnumCaseName('UpperCased')->shouldReturn('UpperCased');
75+
$this->normalizeEnumCaseName('my-./*prop_123')->shouldReturn('myProp_123');
76+
$this->normalizeEnumCaseName('My-./*prop_123')->shouldReturn('MyProp_123');
77+
$this->normalizeEnumCaseName('My-./final*prop_123')->shouldReturn('MyFinalProp_123');
78+
79+
$this->normalizeEnumCaseName('1 specific option')->shouldReturn('Value_1SpecificOption');
80+
}
81+
6382
function it_normalizes_datatypes()
6483
{
6584
$this->normalizeDataType('string')->shouldReturn('string');

src/Phpro/SoapClient/CodeGenerator/Assembler/ClassMapAssembler.php

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Phpro\SoapClient\CodeGenerator\Context\ClassMapContext;
66
use Phpro\SoapClient\CodeGenerator\Context\ContextInterface;
7+
use Phpro\SoapClient\CodeGenerator\Model\Type;
78
use Phpro\SoapClient\CodeGenerator\Model\TypeMap;
89
use Phpro\SoapClient\Exception\AssemblerException;
910
use Laminas\Code\Generator\ClassGenerator;
@@ -48,31 +49,66 @@ public function assemble(ContextInterface $context)
4849
$file->setUse(ClassMapCollection::class);
4950
$file->setUse(ClassMap::class);
5051
$linefeed = $file::LINE_FEED;
51-
$classMap = $this->assembleClassMap($typeMap, $linefeed, $file->getIndentation());
52-
$code = $this->assembleClassMapCollection($classMap, $linefeed).$linefeed;
53-
$class->addMethodFromGenerator(
54-
(new MethodGenerator('getCollection'))
55-
->setStatic(true)
56-
->setBody('return '.$code)
57-
->setReturnType(ClassMapCollection::class)
58-
);
52+
$indentation = $file->getIndentation();
53+
54+
$class->addMethodFromGenerator($this->generateTypes($typeMap, $linefeed, $indentation));
55+
$class->addMethodFromGenerator($this->generateEnums($typeMap, $linefeed, $indentation));
5956
} catch (\Exception $e) {
6057
throw AssemblerException::fromException($e);
6158
}
6259
}
6360

64-
/***
65-
* @param TypeMap $typeMap
66-
* @param string $linefeed
67-
* @param string $indentation
68-
*
69-
* @return string
61+
private function generateTypes(
62+
TypeMap $typeMap,
63+
string $linefeed,
64+
string $indentation,
65+
): MethodGenerator {
66+
$classMap = $this->assembleClassMap(
67+
$typeMap,
68+
$linefeed,
69+
$indentation,
70+
static fn (Type $type) => !(new IsConsideredScalarType())($type->getMeta())
71+
);
72+
$code = $this->assembleClassMapCollection($classMap, $linefeed).$linefeed;
73+
74+
return (new MethodGenerator('types'))
75+
->setStatic(true)
76+
->setBody('return '.$code)
77+
->setReturnType(ClassMapCollection::class);
78+
}
79+
80+
private function generateEnums(
81+
TypeMap $typeMap,
82+
string $linefeed,
83+
string $indentation,
84+
): MethodGenerator {
85+
$classMap = $this->assembleClassMap(
86+
$typeMap,
87+
$linefeed,
88+
$indentation,
89+
static fn (Type $type) => (new IsConsideredScalarType())($type->getMeta())
90+
&& $type->getMeta()->enums()->isSome()
91+
);
92+
$code = $this->assembleClassMapCollection($classMap, $linefeed).$linefeed;
93+
94+
return (new MethodGenerator('enums'))
95+
->setStatic(true)
96+
->setBody('return '.$code)
97+
->setReturnType(ClassMapCollection::class);
98+
}
99+
100+
/**
101+
* @param \Closure(Type): bool $predicate
70102
*/
71-
private function assembleClassMap(TypeMap $typeMap, string $linefeed, string $indentation): string
72-
{
103+
private function assembleClassMap(
104+
TypeMap $typeMap,
105+
string $linefeed,
106+
string $indentation,
107+
\Closure $predicate
108+
): string {
73109
$classMap = [];
74110
foreach ($typeMap->getTypes() as $type) {
75-
if ((new IsConsideredScalarType())($type->getMeta())) {
111+
if (!$predicate($type)) {
76112
continue;
77113
}
78114

src/Phpro/SoapClient/CodeGenerator/ClassMapGenerator.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
use Phpro\SoapClient\CodeGenerator\Context\ClassMapContext;
66
use Phpro\SoapClient\CodeGenerator\Context\FileContext;
7+
use Phpro\SoapClient\CodeGenerator\Model\Type;
78
use Phpro\SoapClient\CodeGenerator\Model\TypeMap;
89
use Phpro\SoapClient\CodeGenerator\Rules\RuleSetInterface;
910
use Laminas\Code\Generator\FileGenerator;
1011

1112
/**
12-
* Class ClassMapGenerator
13-
*
14-
* @package Phpro\SoapClient\CodeGenerator
13+
* @template-implements GeneratorInterface<TypeMap>
1514
*/
1615
class ClassMapGenerator implements GeneratorInterface
1716
{

src/Phpro/SoapClient/CodeGenerator/ClientFactoryGenerator.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Phpro\SoapClient\Caller\EngineCaller;
88
use Phpro\SoapClient\Caller\EventDispatchingCaller;
99
use Phpro\SoapClient\CodeGenerator\Context\ClientFactoryContext;
10+
use Phpro\SoapClient\CodeGenerator\Model\Type;
1011
use Phpro\SoapClient\Soap\DefaultEngineFactory;
1112
use Phpro\SoapClient\Soap\EngineOptions;
1213
use Soap\Encoding\EncoderRegistry;
@@ -16,19 +17,17 @@
1617
use Laminas\Code\Generator\MethodGenerator;
1718

1819
/**
19-
* Class ClientBuilderGenerator
20-
*
21-
* @package Phpro\SoapClient\CodeGenerator
20+
* @template-implements GeneratorInterface<ClientFactoryContext>
2221
*/
2322
class ClientFactoryGenerator implements GeneratorInterface
2423
{
2524
const BODY = <<<BODY
2625
\$engine = DefaultEngineFactory::create(
2726
EngineOptions::defaults(\$wsdl)
2827
->withEncoderRegistry(
29-
EncoderRegistry::default()->addClassMapCollection(
30-
%2\$s::getCollection()
31-
)
28+
EncoderRegistry::default()
29+
->addClassMapCollection(%2\$s::types())
30+
->addBackedEnumClassMapCollection(%2\$s::enums())
3231
)
3332
// If you want to enable WSDL caching:
3433
// ->withCache()

src/Phpro/SoapClient/CodeGenerator/ClientGenerator.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
use Laminas\Code\Generator\FileGenerator;
1616

1717
/**
18-
* Class ClientGenerator
19-
*
20-
* @package Phpro\SoapClient\CodeGenerator
18+
* @template-implements GeneratorInterface<Client>
2119
*/
2220
class ClientGenerator implements GeneratorInterface
2321
{

0 commit comments

Comments
 (0)