Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate backed enums from XSD #561

Merged
merged 1 commit into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ Change the engine inside your (generated) ClientFactory:
$engine = DefaultEngineFactory::create(
EngineOptions::defaults($wsdl)
->withEncoderRegistry(
EncoderRegistry::default()->addClassMapCollection(
CalcClassmap::getCollection()
)
EncoderRegistry::default()
->addClassMapCollection(CalcClassmap::types())
->addBackedEnumClassMapCollection(CalcClassmap::enums())
)
// If you want to enable WSDL caching:
// ->withCache($yourPsr6CachePool)
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"laminas/laminas-code": "^4.14.0",
"php-soap/cached-engine": "~0.3",
"php-soap/engine": "^2.14.0",
"php-soap/encoding": "~0.14",
"php-soap/encoding": "~0.15",
"php-soap/psr18-transport": "^1.7",
"php-soap/wsdl-reader": "~0.20",
"php-soap/wsdl-reader": "~0.21",
"psr/event-dispatcher": "^1.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/console": "~5.4 || ~6.0 || ~7.0",
Expand Down
128 changes: 104 additions & 24 deletions docs/code-generation/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ The code generation commands require a configuration file to determine how the S
<?php
// my-soap-config.php

use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\CodeGenerator\Rules;
use Phpro\SoapClient\CodeGenerator\Assembler;
use Phpro\SoapClient\Soap\DefaultEngineFactory;
use Phpro\SoapClient\CodeGenerator\Rules;
use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\Soap\EngineOptions;
use Phpro\SoapClient\Soap\Metadata\Manipulators\DuplicateTypes\IntersectDuplicateTypesStrategy;
use Phpro\SoapClient\Soap\Metadata\MetadataOptions;
use Soap\Wsdl\Loader\FlatteningLoader;
use Soap\Wsdl\Loader\StreamWrapperLoader;
use Phpro\SoapClient\Soap\DefaultEngineFactory;

return Config::create()
->setEngine(DefaultEngineFactory::create(
EngineOptions::defaults('wsdl.xml')
EngineOptions::defaults($wsdl)
->withWsdlLoader(new FlatteningLoader(new StreamWrapperLoader()))
->withEncoderRegistry(
EncoderRegistry::default()
->addClassMapCollection(SomeClassmap::types())
->addBackedEnumClassMapCollection(SomeClassmap::enums())
)
))
->setTypeDestination('src/SoapTypes')
->setTypeNamespace('SoapTypes')
Expand All @@ -29,23 +30,39 @@ return Config::create()
->setClassMapNamespace('Acme\\Classmap')
->setClassMapDestination('src/acme/classmap')
->setClassMapName('AcmeClassmap')
->setTypeMetadataOptions(
MetadataOptions::empty()
->withTypesManipulator(new IntersectDuplicateTypesStrategy())
)
->addRule(new Rules\AssembleRule(new Assembler\GetterAssembler(
(new Assembler\GetterAssemblerOptions())
->withReturnType()
->withBoolGetters()
->addRule(new Rules\AssembleRule(new Assembler\GetterAssembler(new Assembler\GetterAssemblerOptions())))
->addRule(new Rules\AssembleRule(new Assembler\ImmutableSetterAssembler(
new Assembler\ImmutableSetterAssemblerOptions()
)))
->addRule(new Rules\TypenameMatchesRule(
new Rules\AssembleRule(new Assembler\RequestAssembler()),
'/Request$/'
))
->addRule(new Rules\TypenameMatchesRule(
new Rules\AssembleRule(new Assembler\ResultAssembler()),
'/Response$/'
))
->addRule(
new Rules\IsRequestRule(
$engine->getMetadata(),
new Rules\MultiRule([
new Rules\AssembleRule(new Assembler\RequestAssembler()),
new Rules\AssembleRule(new Assembler\ConstructorAssembler(new Assembler\ConstructorAssemblerOptions())),
])
)
)
->addRule(
new Rules\IsResultRule(
$engine->getMetadata(),
new Rules\MultiRule([
new Rules\AssembleRule(new Assembler\ResultAssembler()),
])
)
)
->addRule(
new Rules\IsExtendingTypeRule(
$engine->getMetadata(),
new Rules\AssembleRule(new Assembler\ExtendingTypeAssembler())
)
)
->addRule(
new Rules\IsAbstractTypeRule(
$engine->getMetadata(),
new Rules\AssembleRule(new Assembler\AbstractClassAssembler())
)
)
;
```

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

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

```php
use Phpro\SoapClient\Soap\EngineOptions;
use Phpro\SoapClient\Soap\DefaultEngineFactory;

DefaultEngineFactory::create(
EngineOptions::defaults($wsdl)
->withWsdlLoader(new FlatteningLoader(new StreamWrapperLoader()))
->withEncoderRegistry(
EncoderRegistry::default()
->addClassMapCollection(SomeClassmap::types())
->addBackedEnumClassMapCollection(SomeClassmap::enums())
)
// If you want to enable WSDL caching:
// ->withCache()
// If you want to use Alternate HTTP settings:
// ->withWsdlLoader()
// ->withTransport()
// If you want specific SOAP setting:
// ->withWsdlParserContext()
// ->withWsdlServiceSelectionCriteria()
);
```

**type destination**

String - REQUIRED
Expand Down Expand Up @@ -128,3 +168,43 @@ Config::create()
)
)
```

**Metadata manipulations**

The metadata manipulations are a set of strategies that can be applied to the metadata before the code generation starts.
You can read more about this in the documentation in the section [metadata](../drivers/metadata.md).

Examples:

```php
use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\Soap\Metadata\Manipulators\DuplicateTypes\IntersectDuplicateTypesStrategy;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacers;

Config::create()
->setDuplicateTypeIntersectStrategy(new IntersectDuplicateTypesStrategy())
->setTypeReplacementStrategy(TypeReplacers::defaults()->add(new MyDateReplacer()));
```

**Enumeration options**

You can configure how the code generator deals with XSD enumeration types.
There are 2 type of XSD enumerations:

- `global`: Are available as a global simpletype inside the XSD.
- `local`: Are configured as an internal type on an element or attribute and don't really have a name.

The default behavior is to generate a PHP Enum for global enumerations only because
We want to avoid naming conflicts with other types for local enumerations.

It is possible to opt-in into using these local enumerations as well:

```php
use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\CodeGenerator\Config\EnumerationGenerationStrategy;

Config::create()
->setEnumerationGenerationStrategy(EnumerationGenerationStrategy::LocalAndGlobal);
```

**Note**: This will dynamically add some extra type replacements and type manipulations to the metadata before the code generation starts.
19 changes: 19 additions & 0 deletions spec/Phpro/SoapClient/CodeGenerator/Util/NormalizerSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,25 @@ function it_noramizes_properties()
$this->normalizeProperty('My-./final*prop_123')->shouldReturn('MyFinalProp_123');
}

function it_normalizes_enum_cases()
{
$this->normalizeEnumCaseName('')->shouldReturn('Empty');

$this->normalizeEnumCaseName('-1')->shouldReturn('Value_Minus_1');
$this->normalizeEnumCaseName('0')->shouldReturn('Value_0');
$this->normalizeEnumCaseName('1')->shouldReturn('Value_1');
$this->normalizeEnumCaseName('10000')->shouldReturn('Value_10000');

$this->normalizeEnumCaseName('final')->shouldReturn('final');
$this->normalizeEnumCaseName('Final')->shouldReturn('Final');
$this->normalizeEnumCaseName('UpperCased')->shouldReturn('UpperCased');
$this->normalizeEnumCaseName('my-./*prop_123')->shouldReturn('myProp_123');
$this->normalizeEnumCaseName('My-./*prop_123')->shouldReturn('MyProp_123');
$this->normalizeEnumCaseName('My-./final*prop_123')->shouldReturn('MyFinalProp_123');

$this->normalizeEnumCaseName('1 specific option')->shouldReturn('Value_1SpecificOption');
}

function it_normalizes_datatypes()
{
$this->normalizeDataType('string')->shouldReturn('string');
Expand Down
70 changes: 53 additions & 17 deletions src/Phpro/SoapClient/CodeGenerator/Assembler/ClassMapAssembler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Phpro\SoapClient\CodeGenerator\Context\ClassMapContext;
use Phpro\SoapClient\CodeGenerator\Context\ContextInterface;
use Phpro\SoapClient\CodeGenerator\Model\Type;
use Phpro\SoapClient\CodeGenerator\Model\TypeMap;
use Phpro\SoapClient\Exception\AssemblerException;
use Laminas\Code\Generator\ClassGenerator;
Expand Down Expand Up @@ -48,31 +49,66 @@ public function assemble(ContextInterface $context)
$file->setUse(ClassMapCollection::class);
$file->setUse(ClassMap::class);
$linefeed = $file::LINE_FEED;
$classMap = $this->assembleClassMap($typeMap, $linefeed, $file->getIndentation());
$code = $this->assembleClassMapCollection($classMap, $linefeed).$linefeed;
$class->addMethodFromGenerator(
(new MethodGenerator('getCollection'))
->setStatic(true)
->setBody('return '.$code)
->setReturnType(ClassMapCollection::class)
);
$indentation = $file->getIndentation();

$class->addMethodFromGenerator($this->generateTypes($typeMap, $linefeed, $indentation));
$class->addMethodFromGenerator($this->generateEnums($typeMap, $linefeed, $indentation));
} catch (\Exception $e) {
throw AssemblerException::fromException($e);
}
}

/***
* @param TypeMap $typeMap
* @param string $linefeed
* @param string $indentation
*
* @return string
private function generateTypes(
TypeMap $typeMap,
string $linefeed,
string $indentation,
): MethodGenerator {
$classMap = $this->assembleClassMap(
$typeMap,
$linefeed,
$indentation,
static fn (Type $type) => !(new IsConsideredScalarType())($type->getMeta())
);
$code = $this->assembleClassMapCollection($classMap, $linefeed).$linefeed;

return (new MethodGenerator('types'))
->setStatic(true)
->setBody('return '.$code)
->setReturnType(ClassMapCollection::class);
}

private function generateEnums(
TypeMap $typeMap,
string $linefeed,
string $indentation,
): MethodGenerator {
$classMap = $this->assembleClassMap(
$typeMap,
$linefeed,
$indentation,
static fn (Type $type) => (new IsConsideredScalarType())($type->getMeta())
&& $type->getMeta()->enums()->isSome()
);
$code = $this->assembleClassMapCollection($classMap, $linefeed).$linefeed;

return (new MethodGenerator('enums'))
->setStatic(true)
->setBody('return '.$code)
->setReturnType(ClassMapCollection::class);
}

/**
* @param \Closure(Type): bool $predicate
*/
private function assembleClassMap(TypeMap $typeMap, string $linefeed, string $indentation): string
{
private function assembleClassMap(
TypeMap $typeMap,
string $linefeed,
string $indentation,
\Closure $predicate
): string {
$classMap = [];
foreach ($typeMap->getTypes() as $type) {
if ((new IsConsideredScalarType())($type->getMeta())) {
if (!$predicate($type)) {
continue;
}

Expand Down
5 changes: 2 additions & 3 deletions src/Phpro/SoapClient/CodeGenerator/ClassMapGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@

use Phpro\SoapClient\CodeGenerator\Context\ClassMapContext;
use Phpro\SoapClient\CodeGenerator\Context\FileContext;
use Phpro\SoapClient\CodeGenerator\Model\Type;
use Phpro\SoapClient\CodeGenerator\Model\TypeMap;
use Phpro\SoapClient\CodeGenerator\Rules\RuleSetInterface;
use Laminas\Code\Generator\FileGenerator;

/**
* Class ClassMapGenerator
*
* @package Phpro\SoapClient\CodeGenerator
* @template-implements GeneratorInterface<TypeMap>
*/
class ClassMapGenerator implements GeneratorInterface
{
Expand Down
11 changes: 5 additions & 6 deletions src/Phpro/SoapClient/CodeGenerator/ClientFactoryGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Phpro\SoapClient\Caller\EngineCaller;
use Phpro\SoapClient\Caller\EventDispatchingCaller;
use Phpro\SoapClient\CodeGenerator\Context\ClientFactoryContext;
use Phpro\SoapClient\CodeGenerator\Model\Type;
use Phpro\SoapClient\Soap\DefaultEngineFactory;
use Phpro\SoapClient\Soap\EngineOptions;
use Soap\Encoding\EncoderRegistry;
Expand All @@ -16,19 +17,17 @@
use Laminas\Code\Generator\MethodGenerator;

/**
* Class ClientBuilderGenerator
*
* @package Phpro\SoapClient\CodeGenerator
* @template-implements GeneratorInterface<ClientFactoryContext>
*/
class ClientFactoryGenerator implements GeneratorInterface
{
const BODY = <<<BODY
\$engine = DefaultEngineFactory::create(
EngineOptions::defaults(\$wsdl)
->withEncoderRegistry(
EncoderRegistry::default()->addClassMapCollection(
%2\$s::getCollection()
)
EncoderRegistry::default()
->addClassMapCollection(%2\$s::types())
->addBackedEnumClassMapCollection(%2\$s::enums())
)
// If you want to enable WSDL caching:
// ->withCache()
Expand Down
4 changes: 1 addition & 3 deletions src/Phpro/SoapClient/CodeGenerator/ClientGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
use Laminas\Code\Generator\FileGenerator;

/**
* Class ClientGenerator
*
* @package Phpro\SoapClient\CodeGenerator
* @template-implements GeneratorInterface<Client>
*/
class ClientGenerator implements GeneratorInterface
{
Expand Down
Loading
Loading