diff --git a/src/Metadata/Converter/SchemaToTypesConverter.php b/src/Metadata/Converter/SchemaToTypesConverter.php index 48db279..a002d7c 100644 --- a/src/Metadata/Converter/SchemaToTypesConverter.php +++ b/src/Metadata/Converter/SchemaToTypesConverter.php @@ -7,6 +7,7 @@ use GoetasWebservices\XML\XSDReader\Schema\Schema; use GoetasWebservices\XML\XSDReader\Schema\Type\Type; use Soap\Engine\Metadata\Collection\TypeCollection; +use Soap\WsdlReader\Metadata\Converter\Types\ParentContext; use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext; use Soap\WsdlReader\Metadata\Converter\Types\Visitor\ElementVisitor; use Soap\WsdlReader\Metadata\Converter\Types\Visitor\TypeVisitor; @@ -22,15 +23,24 @@ public function __invoke(Schema $schema, TypesConverterContext $context): TypeCo ...filter_nulls([ ...flat_map( $schema->getTypes(), - static fn (Type $type): TypeCollection => (new TypeVisitor())($type, $context) + static fn (Type $type): TypeCollection => (new TypeVisitor())( + $type, + $context->onParent(ParentContext::create($type)) + ) ), ...flat_map( $schema->getElements(), - static fn (ElementDef $element): TypeCollection => (new ElementVisitor())($element, $context) + static fn (ElementDef $element): TypeCollection => (new ElementVisitor())( + $element, + $context->onParent(ParentContext::create($element)) + ) ), ...flat_map( $schema->getSchemas(), - fn (Schema $childSchema): TypeCollection => $this->__invoke($childSchema, $context) + fn (Schema $childSchema): TypeCollection => $this->__invoke( + $childSchema, + $context->onParent(null) + ) ) ]) ); diff --git a/src/Metadata/Converter/Types/Configurator/XmlTypeInfoConfigurator.php b/src/Metadata/Converter/Types/Configurator/XmlTypeInfoConfigurator.php index 0ae37b2..5a2e0b5 100644 --- a/src/Metadata/Converter/Types/Configurator/XmlTypeInfoConfigurator.php +++ b/src/Metadata/Converter/Types/Configurator/XmlTypeInfoConfigurator.php @@ -3,10 +3,14 @@ namespace Soap\WsdlReader\Metadata\Converter\Types\Configurator; +use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem; +use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem; use GoetasWebservices\XML\XSDReader\Schema\Item; use GoetasWebservices\XML\XSDReader\Schema\SchemaItem; use GoetasWebservices\XML\XSDReader\Schema\Type\Type; use Soap\Engine\Metadata\Model\XsdType as EngineType; +use Soap\WsdlReader\Metadata\Converter\Types\Detector\AttributeTypeNameDetector; +use Soap\WsdlReader\Metadata\Converter\Types\Detector\ElementTypeNameDetector; use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext; final class XmlTypeInfoConfigurator @@ -25,9 +29,16 @@ public function __invoke(EngineType $engineType, mixed $xsdType, TypesConverterC $targetNamespace = $xsdType->getSchema()->getTargetNamespace() ?? ''; $typeNamespace = $type?->getSchema()->getTargetNamespace() ?: $targetNamespace; + $parentContext = $context->parent()->unwrapOr(null); + $xmlTypeName = match(true) { + $parentContext && $item instanceof ElementItem => (new ElementTypeNameDetector())($item, $parentContext), + $parentContext && $item instanceof AttributeItem => (new AttributeTypeNameDetector())($item, $parentContext), + default => $typeName, + }; + return $engineType ->withXmlTargetNodeName($itemName ?: $typeName) - ->withXmlTypeName($typeName ?: $itemName ?: '') + ->withXmlTypeName($xmlTypeName) ->withXmlNamespace($typeNamespace) ->withXmlNamespaceName( $context->knownNamespaces->lookupNameFromNamespace($typeNamespace)->unwrapOr( diff --git a/src/Metadata/Converter/Types/Detector/AttributeDeclaringParentTypeDetector.php b/src/Metadata/Converter/Types/Detector/AttributeDeclaringParentTypeDetector.php new file mode 100644 index 0000000..c523078 --- /dev/null +++ b/src/Metadata/Converter/Types/Detector/AttributeDeclaringParentTypeDetector.php @@ -0,0 +1,52 @@ + + */ + public function __invoke(AttributeItem $item, ?SchemaItem $parent): Option + { + $parent = match(true) { + $parent instanceof Item => $parent->getType(), + default => $parent, + }; + + if (!$parent instanceof Type) { + return none(); + } + + if ($parent instanceof AttributeContainer) { + foreach ($parent->getAttributes() as $parentAttribute) { + if ($parentAttribute->getName() === $item->getName()) { + /** @var Option */ + return some($parent); + } + } + } + + $extensionBase = $parent->getExtension()?->getBase(); + if ($extensionBase) { + return $this->__invoke($item, $extensionBase); + } + + return none(); + } +} diff --git a/src/Metadata/Converter/Types/Detector/AttributeTypeNameDetector.php b/src/Metadata/Converter/Types/Detector/AttributeTypeNameDetector.php new file mode 100644 index 0000000..a3de8cf --- /dev/null +++ b/src/Metadata/Converter/Types/Detector/AttributeTypeNameDetector.php @@ -0,0 +1,34 @@ +getType() : null; + $attributeRestriction = $attributeType?->getRestriction(); + $attributeTypeName = $attributeType?->getName(); + $attributeRestrictionName = ($attributeRestriction && !$attributeRestriction->getChecks()) ? $attributeRestriction->getBase()?->getName() : null; + + $typeName = $attributeTypeName ?: ($attributeRestrictionName ?: $attribute->getName()); + + // If a name cannot be determined from the type, we fallback to the attribute name: + // Prefix the attribute name with the parent element name resulting in a more unique type-name. + if (!$attributeTypeName && !$attributeRestrictionName) { + $typeName = (new AttributeDeclaringParentTypeDetector())($attribute, $parentContext->currentParent()) + ->andThen(static fn (Type $parent): Option => from_nullable($parent->getName())) + ->map(static fn (string $parentName): string => $parentName . ucfirst($typeName)) + ->unwrapOr($typeName); + } + + return $typeName; + } +} diff --git a/src/Metadata/Converter/Types/Detector/ElementTypeNameDetector.php b/src/Metadata/Converter/Types/Detector/ElementTypeNameDetector.php new file mode 100644 index 0000000..fb8db0a --- /dev/null +++ b/src/Metadata/Converter/Types/Detector/ElementTypeNameDetector.php @@ -0,0 +1,31 @@ +getType() : null; + $typeName = $calculatedTypeName ?? ($type?->getName() ?: $element->getName()); + + // For inline simple types, we prefix the name of the element with the name of the parent type. + if ($type instanceof SimpleType && !$type->getName()) { + $parent = $parentContext->currentParent(); + + if ($parent instanceof Type || $parent instanceof ElementItem) { + if ($parentName = $parent->getName()) { + $typeName = $parentName . ucfirst($typeName); + } + } + } + + return $typeName; + } +} diff --git a/src/Metadata/Converter/Types/ParentContext.php b/src/Metadata/Converter/Types/ParentContext.php new file mode 100644 index 0000000..724be71 --- /dev/null +++ b/src/Metadata/Converter/Types/ParentContext.php @@ -0,0 +1,38 @@ + $items + */ + private function __construct( + public readonly array $items, + ) { + } + + public static function create(SchemaItem $item): self + { + return new self([$item]); + } + + public function withNextParent(SchemaItem $item): self + { + return new self([...$this->items, $item]); + } + + public function rootParent(): SchemaItem + { + return first($this->items); + } + + public function currentParent(): SchemaItem + { + return last($this->items); + } +} diff --git a/src/Metadata/Converter/Types/TypesConverterContext.php b/src/Metadata/Converter/Types/TypesConverterContext.php index 857e45a..56590f8 100644 --- a/src/Metadata/Converter/Types/TypesConverterContext.php +++ b/src/Metadata/Converter/Types/TypesConverterContext.php @@ -4,9 +4,12 @@ namespace Soap\WsdlReader\Metadata\Converter\Types; use GoetasWebservices\XML\XSDReader\Schema\Schema; +use Psl\Option\Option; use Soap\Engine\Metadata\Collection\TypeCollection; use Soap\WsdlReader\Model\Definitions\Namespaces; use Soap\WsdlReader\Parser\Definitions\SchemaParser; +use function Psl\Option\from_nullable; +use function Psl\Option\none; final class TypesConverterContext { @@ -20,9 +23,15 @@ final class TypesConverterContext */ private array $visited = []; + /** + * @var Option + */ + private Option $parentContext; + private function __construct( public readonly Namespaces $knownNamespaces ) { + $this->parentContext = none(); } public static function default(Namespaces $knownNamespaces): self @@ -57,4 +66,19 @@ public function visit(Schema $schema, callable $visitor): TypeCollection return $visitor($schema); } + + public function onParent(?ParentContext $parentContext): self + { + $this->parentContext = from_nullable($parentContext); + + return $this; + } + + /** + * @return Option + */ + public function parent(): Option + { + return $this->parentContext; + } } diff --git a/src/Metadata/Converter/Types/Visitor/AttributeContainerVisitor.php b/src/Metadata/Converter/Types/Visitor/AttributeContainerVisitor.php index c6a2a9d..d746da2 100644 --- a/src/Metadata/Converter/Types/Visitor/AttributeContainerVisitor.php +++ b/src/Metadata/Converter/Types/Visitor/AttributeContainerVisitor.php @@ -5,7 +5,6 @@ use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer; use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem; -use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeSingle; use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group; use GoetasWebservices\XML\XSDReader\Schema\Type\Type; use Soap\Engine\Metadata\Collection\PropertyCollection; @@ -13,6 +12,7 @@ use Soap\Engine\Metadata\Model\TypeMeta; use Soap\Engine\Metadata\Model\XsdType as EngineType; use Soap\WsdlReader\Metadata\Converter\Types\Configurator; +use Soap\WsdlReader\Metadata\Converter\Types\Detector\AttributeTypeNameDetector; use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext; use function Psl\Fun\pipe; use function Psl\Result\wrap; @@ -88,9 +88,7 @@ private function parseAttribute(AttributeItem $attribute, TypesConverterContext return $this->parseAttributes($attribute, $context); } - $attributeType = $attribute instanceof AttributeSingle ? $attribute->getType() : null; - $typeName = $attributeType?->getName() ?: $attribute->getName(); - + $typeName = (new AttributeTypeNameDetector())($attribute, $context->parent()->unwrap()); $configure = pipe( static fn (EngineType $engineType): EngineType => (new Configurator\AttributeConfigurator())($engineType, $attribute, $context), ); diff --git a/src/Metadata/Converter/Types/Visitor/ElementContainerVisitor.php b/src/Metadata/Converter/Types/Visitor/ElementContainerVisitor.php index 5820f4b..8b99ef4 100644 --- a/src/Metadata/Converter/Types/Visitor/ElementContainerVisitor.php +++ b/src/Metadata/Converter/Types/Visitor/ElementContainerVisitor.php @@ -6,13 +6,13 @@ use GoetasWebservices\XML\XSDReader\Schema\Element\Choice; use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer; use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem; -use GoetasWebservices\XML\XSDReader\Schema\Element\ElementSingle; use GoetasWebservices\XML\XSDReader\Schema\Element\Group; use GoetasWebservices\XML\XSDReader\Schema\Element\Sequence; use Soap\Engine\Metadata\Collection\PropertyCollection; use Soap\Engine\Metadata\Model\Property; use Soap\Engine\Metadata\Model\XsdType as EngineType; use Soap\WsdlReader\Metadata\Converter\Types\Configurator; +use Soap\WsdlReader\Metadata\Converter\Types\Detector\ElementTypeNameDetector; use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext; use function Psl\Fun\pipe; use function Psl\Vec\flat_map; @@ -35,8 +35,7 @@ private function parseElementItem(ElementItem $element, TypesConverterContext $c return $this->__invoke($element, $context); } - $type = $element instanceof ElementSingle ? $element->getType() : null; - $typeName = $type?->getName() ?: $element->getName(); + $typeName = (new ElementTypeNameDetector())($element, $context->parent()->unwrap()); $configure = pipe( static fn (EngineType $engineType): EngineType => (new Configurator\ElementConfigurator())($engineType, $element, $context), static fn (EngineType $engineType): EngineType => (new Configurator\AnyElementConfigurator())($engineType, $element, $context), diff --git a/src/Metadata/Converter/Types/Visitor/InlineElementTypeVisitor.php b/src/Metadata/Converter/Types/Visitor/InlineElementTypeVisitor.php index 68278f3..05d1d8a 100644 --- a/src/Metadata/Converter/Types/Visitor/InlineElementTypeVisitor.php +++ b/src/Metadata/Converter/Types/Visitor/InlineElementTypeVisitor.php @@ -62,6 +62,8 @@ private function detectInlineTypes(ElementItem $element, TypesConverterContext $ return new TypeCollection(); } - return $elementVisitor($element, $context); + return $elementVisitor($element, $context->onParent( + $context->parent()->unwrap()->withNextParent($element) + )); } } diff --git a/tests/PhpCompatibility/schema1013.phpt b/tests/PhpCompatibility/schema1013.phpt new file mode 100644 index 0000000..3f179f7 --- /dev/null +++ b/tests/PhpCompatibility/schema1013.phpt @@ -0,0 +1,32 @@ +--TEST-- +SOAP XML Schema 1001: Prepend parent type name before attribute type names for more unique type-names. +--FILE-- + + + + + + + + + + + + + + +EOF; +test_schema($schema,'type="tns:VehicleCoreType"'); +?> +--EXPECT-- +Methods: + > test(VehicleCoreType $testParam): void + +Types: + > http://test-uri/:VehicleCoreType { + ?string $VehType + @?VehicleCoreTypeDriveType in (AWD|4WD|Unspecified) $DriveType + } diff --git a/tests/PhpCompatibility/schema1014.phpt b/tests/PhpCompatibility/schema1014.phpt new file mode 100644 index 0000000..4d7af1a --- /dev/null +++ b/tests/PhpCompatibility/schema1014.phpt @@ -0,0 +1,31 @@ +--TEST-- +SOAP XML Schema 1001: Prepend parent type name before local simple element type names for more unique type-names. +--FILE-- + + + + + + + + + + + + + + +EOF; +test_schema($schema,'type="tns:Element"'); +?> +--EXPECT-- +Methods: + > test(Element $testParam): void + +Types: + > http://test-uri/:Element { + ElementEnum in (foo|bar) $Enum + }