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 8f73a12
Show file tree
Hide file tree
Showing 28 changed files with 747 additions and 83 deletions.
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
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
47 changes: 40 additions & 7 deletions src/Phpro/SoapClient/CodeGenerator/Config/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@
use Phpro\SoapClient\CodeGenerator\Rules\RuleSetInterface;
use Phpro\SoapClient\CodeGenerator\Util\Normalizer;
use Phpro\SoapClient\Exception\InvalidArgumentException;
use Phpro\SoapClient\Soap\Metadata\Detector\LocalEnumDetector;
use Phpro\SoapClient\Soap\Metadata\Manipulators\DuplicateTypes\IntersectDuplicateTypesStrategy;
use Phpro\SoapClient\Soap\Metadata\Manipulators\MethodsManipulatorChain;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\LocalToGlobalEnumReplacer;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\AppendTypesManipulator;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\ReplaceMethodTypesManipulator;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\ReplaceTypesManipulator;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacer;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypeReplacer\TypeReplacers;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypesManipulatorChain;
use Phpro\SoapClient\Soap\Metadata\Manipulators\TypesManipulatorInterface;
use Phpro\SoapClient\Soap\Metadata\MetadataFactory;
use Phpro\SoapClient\Soap\Metadata\MetadataOptions;
use Soap\Engine\Engine;
use Soap\Engine\Metadata\Collection\TypeCollection;
use Soap\Engine\Metadata\Metadata;

final class Config
Expand Down Expand Up @@ -56,9 +59,9 @@ final class Config

protected TypesManipulatorInterface $duplicateTypeIntersectStrategy;

protected TypeReplacer $typeReplacementStrategy;
protected TypeReplacers $typeReplacementStrategy;

protected ?MetadataOptions $metadataOptions;
protected ?MetadataOptions $metadataOptions = null;

/**
* @var RuleSetInterface
Expand All @@ -80,6 +83,8 @@ final class Config
*/
protected $classMapDestination;

protected EnumerationGenerationStrategy $enumerationGenerationStrategy;

public function __construct()
{
$this->typeReplacementStrategy = TypeReplacers::defaults();
Expand All @@ -89,6 +94,9 @@ public function __construct()
// The resulting type will always be usable, but might contain some additional empty properties.
$this->duplicateTypeIntersectStrategy = new IntersectDuplicateTypesStrategy();

// By default, we only generate global enumerations to avoid naming conflicts.
$this->enumerationGenerationStrategy = EnumerationGenerationStrategy::default();

$this->ruleSet = new RuleSet([
new Rules\AssembleRule(new Assembler\PropertyAssembler()),
new Rules\AssembleRule(new Assembler\ClassMapAssembler()),
Expand Down Expand Up @@ -270,15 +278,28 @@ public function setTypeDestination($typeDestination): self

public function getMetadataOptions(): MetadataOptions
{
return $this->metadataOptions ?? MetadataOptions::empty()
if ($this->metadataOptions) {
return $this->metadataOptions;
}

$typeReplacementStrategy = new TypeReplacers(...$this->typeReplacementStrategy);
$appendTypes = static fn () => new TypeCollection();

if ($this->enumerationGenerationStrategy === EnumerationGenerationStrategy::LocalAndGlobal) {
$typeReplacementStrategy = $typeReplacementStrategy->add(new LocalToGlobalEnumReplacer());
$appendTypes = new LocalEnumDetector();
}

return MetadataOptions::empty()
->withTypesManipulator(
new TypesManipulatorChain(
new AppendTypesManipulator($appendTypes),
$this->duplicateTypeIntersectStrategy,
new ReplaceTypesManipulator($this->typeReplacementStrategy),
new ReplaceTypesManipulator($typeReplacementStrategy),
)
)->withMethodsManipulator(
new MethodsManipulatorChain(
new ReplaceMethodTypesManipulator($this->typeReplacementStrategy)
new ReplaceMethodTypesManipulator($typeReplacementStrategy)
)
);
}
Expand All @@ -291,7 +312,7 @@ public function getManipulatedMetadata(): Metadata
);
}

public function setTypeReplacementStrategy(TypeReplacer $typeReplacementStrategy): self
public function setTypeReplacementStrategy(TypeReplacers $typeReplacementStrategy): self
{
$this->typeReplacementStrategy = $typeReplacementStrategy;

Expand Down Expand Up @@ -380,4 +401,16 @@ public function setClassMapDestination(string $classMapDestination): self

return $this;
}

public function setEnumerationGenerationStrategy(EnumerationGenerationStrategy $enumerationGenerationStrategy): self
{
$this->enumerationGenerationStrategy = $enumerationGenerationStrategy;

return $this;
}

public function getEnumerationGenerationStrategy(): EnumerationGenerationStrategy
{
return $this->enumerationGenerationStrategy;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Phpro\SoapClient\CodeGenerator\Config;

enum EnumerationGenerationStrategy
{

/**
* Only generates and uses globally accessible XSD enumerations.
*/
case GlobalOnly;

/**
* Tries to find properties that have local XSD enumerations and copies them as global enumerations.
*/
case LocalAndGlobal;

public static function default(): self
{
return self::GlobalOnly;
}
}
5 changes: 2 additions & 3 deletions src/Phpro/SoapClient/CodeGenerator/ConfigGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\CodeGenerator\Context\ConfigContext;
use Laminas\Code\Generator\FileGenerator;
use Phpro\SoapClient\CodeGenerator\Model\Type;
use Phpro\SoapClient\Soap\DefaultEngineFactory;
use Phpro\SoapClient\Soap\EngineOptions;

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

0 comments on commit 8f73a12

Please sign in to comment.