diff --git a/src/Metadata/Converter/SchemaToTypesConverter.php b/src/Metadata/Converter/SchemaToTypesConverter.php index 089df38..e1f3583 100644 --- a/src/Metadata/Converter/SchemaToTypesConverter.php +++ b/src/Metadata/Converter/SchemaToTypesConverter.php @@ -22,7 +22,10 @@ public function __invoke(Schema $schema, TypesConverterContext $context): TypeCo return $context->visit($schema, function () use ($schema, $context): TypeCollection { return new TypeCollection( ...filter_nulls([ - ...map($schema->getTypes(), static fn (Type $type): SoapType => (new TypeVisitor())($type, $context)), + ...flat_map( + $schema->getTypes(), + static fn (Type $type): TypeCollection => (new TypeVisitor())($type, $context) + ), ...map($schema->getElements(), static fn (ElementDef $element): ?SoapType => (new ElementVisitor())($element, $context)), ...flat_map( $schema->getSchemas(), diff --git a/src/Metadata/Converter/Types/Visitor/ElementVisitor.php b/src/Metadata/Converter/Types/Visitor/ElementVisitor.php index ce1eaa3..04e6f4c 100644 --- a/src/Metadata/Converter/Types/Visitor/ElementVisitor.php +++ b/src/Metadata/Converter/Types/Visitor/ElementVisitor.php @@ -3,7 +3,7 @@ namespace Soap\WsdlReader\Metadata\Converter\Types\Visitor; -use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef; +use GoetasWebservices\XML\XSDReader\Schema\Element\AbstractElementSingle; use Soap\Engine\Metadata\Model\Type as EngineType; use Soap\Engine\Metadata\Model\XsdType as MetaType; use Soap\WsdlReader\Metadata\Converter\Types\Configurator; @@ -12,7 +12,7 @@ final class ElementVisitor { - public function __invoke(ElementDef $element, TypesConverterContext $context): ?EngineType + public function __invoke(AbstractElementSingle $element, TypesConverterContext $context): ?EngineType { // When there is no type set on the element, we cannot do anything with it here. There should always be one somehow. $xsdType = $element->getType(); diff --git a/src/Metadata/Converter/Types/Visitor/TypeVisitor.php b/src/Metadata/Converter/Types/Visitor/TypeVisitor.php index 052aaa7..f4abdb8 100644 --- a/src/Metadata/Converter/Types/Visitor/TypeVisitor.php +++ b/src/Metadata/Converter/Types/Visitor/TypeVisitor.php @@ -3,24 +3,72 @@ namespace Soap\WsdlReader\Metadata\Converter\Types\Visitor; +use GoetasWebservices\XML\XSDReader\Schema\Element\AbstractElementSingle; +use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem; +use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef; +use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType; use GoetasWebservices\XML\XSDReader\Schema\Type\Type; +use Soap\Engine\Metadata\Collection\TypeCollection; use Soap\Engine\Metadata\Model\Type as EngineType; use Soap\Engine\Metadata\Model\XsdType as MetaType; use Soap\WsdlReader\Metadata\Converter\Types\Configurator; use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext; use function Psl\Fun\pipe; +use function Psl\Vec\filter_nulls; +use function Psl\Vec\map; final class TypeVisitor { - public function __invoke(Type $xsdType, TypesConverterContext $context): EngineType + public function __invoke(Type $xsdType, TypesConverterContext $context): TypeCollection { $configure = pipe( static fn (MetaType $metaType): MetaType => (new Configurator\TypeConfigurator())($metaType, $xsdType, $context), ); - return new EngineType( - $configure(MetaType::guess($xsdType->getName() ?? '')), - (new PropertiesVisitor())($xsdType, $context) + return new TypeCollection( + new EngineType( + $configure(MetaType::guess($xsdType->getName() ?? '')), + (new PropertiesVisitor())($xsdType, $context) + ), + ...$this->parseNestedInlineElements($xsdType, $context), + ); + } + + /** + * Complex types may contain nested inline types. + * Let's unwrap them: + */ + private function parseNestedInlineElements(Type $xsdType, TypesConverterContext $context): TypeCollection + { + if (!$xsdType instanceof ComplexType) { + return new TypeCollection(); + } + + $elementVisitor = new ElementVisitor(); + + return new TypeCollection( + ...filter_nulls( + map( + $xsdType->getElements(), + static function (ElementItem $element) use ($elementVisitor, $context): ?EngineType { + if (!$element instanceof AbstractElementSingle || $element instanceof ElementRef) { + return null; + } + + // There is no need to create types for simple elements like strings. + if (!$element->getType() instanceof ComplexType) { + return null; + } + + // If the element links to a named type, we already know about it. + if ($element->getType()?->getName()) { + return null; + } + + return $elementVisitor($element, $context); + } + ) + ) ); } } diff --git a/tests/PhpCompatibility/schema036.phpt b/tests/PhpCompatibility/schema036.phpt index eb07c55..1105014 100644 --- a/tests/PhpCompatibility/schema036.phpt +++ b/tests/PhpCompatibility/schema036.phpt @@ -27,4 +27,7 @@ Types: > http://test-uri/:testType { int $int testType2 $testType2 - } \ No newline at end of file + } + > http://test-uri/:testType2 { + int $int + } diff --git a/tests/PhpCompatibility/schema1002.phpt b/tests/PhpCompatibility/schema1002.phpt new file mode 100644 index 0000000..e27b1c3 --- /dev/null +++ b/tests/PhpCompatibility/schema1002.phpt @@ -0,0 +1,51 @@ +--TEST-- +SOAP XML Schema 18: union with list +--FILE-- + + + Specifies charges and/or penalties associated with making ticket changes after purchase. + + + + + Specifies penalty charges as either a currency amount or a percentage of the fare + + + + + Indicates the type of penalty involved in the search or response. + + + + + Identifier used to indicate whether the change occurs before or after departure from the origin city. + + + + + + + + Indicator used to specify whether voluntary change and other penalties are involved in the search or response. + + + +EOF; +test_schema($schema,'type="tns:VoluntaryChangesType"'); +?> +--EXPECT-- +Methods: + > test(VoluntaryChangesType $testParam): void + +Types: + > http://test-uri/:VoluntaryChangesType { + ?Penalty $Penalty + @boolean $VolChangeInd + } + > http://test-uri/:Penalty { + @string $PenaltyType + @string $DepartureStatus + }