Skip to content

Commit

Permalink
Generate backed enums from XSD
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Dec 20, 2024
1 parent 25a2f2e commit e6959a3
Show file tree
Hide file tree
Showing 31 changed files with 854 additions and 111 deletions.
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

0 comments on commit e6959a3

Please sign in to comment.