Skip to content

Commit 2d84d9f

Browse files
committed
Prefix attribute type-names with parent type name.
1 parent 1128124 commit 2d84d9f

File tree

7 files changed

+149
-6
lines changed

7 files changed

+149
-6
lines changed

src/Metadata/Converter/SchemaToTypesConverter.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use GoetasWebservices\XML\XSDReader\Schema\Schema;
88
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
99
use Soap\Engine\Metadata\Collection\TypeCollection;
10+
use Soap\WsdlReader\Metadata\Converter\Types\ParentContext;
1011
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;
1112
use Soap\WsdlReader\Metadata\Converter\Types\Visitor\ElementVisitor;
1213
use Soap\WsdlReader\Metadata\Converter\Types\Visitor\TypeVisitor;
@@ -22,15 +23,24 @@ public function __invoke(Schema $schema, TypesConverterContext $context): TypeCo
2223
...filter_nulls([
2324
...flat_map(
2425
$schema->getTypes(),
25-
static fn (Type $type): TypeCollection => (new TypeVisitor())($type, $context)
26+
static fn (Type $type): TypeCollection => (new TypeVisitor())(
27+
$type,
28+
$context->onParent(new ParentContext($type))
29+
)
2630
),
2731
...flat_map(
2832
$schema->getElements(),
29-
static fn (ElementDef $element): TypeCollection => (new ElementVisitor())($element, $context)
33+
static fn (ElementDef $element): TypeCollection => (new ElementVisitor())(
34+
$element,
35+
$context->onParent(new ParentContext($element))
36+
)
3037
),
3138
...flat_map(
3239
$schema->getSchemas(),
33-
fn (Schema $childSchema): TypeCollection => $this->__invoke($childSchema, $context)
40+
fn (Schema $childSchema): TypeCollection => $this->__invoke(
41+
$childSchema,
42+
$context->onParent(null)
43+
)
3444
)
3545
])
3646
);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Soap\WsdlReader\Metadata\Converter\Types\Detector;
4+
5+
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
6+
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
7+
use GoetasWebservices\XML\XSDReader\Schema\Item;
8+
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
9+
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
10+
use Psl\Option\Option;
11+
use function Psl\Option\none;
12+
use function Psl\Option\some;
13+
14+
final class AttributeDeclaringParentTypeDetector
15+
{
16+
/**
17+
* This class detects the declaring parent type of an attribute.
18+
* It can be used together with the ParentContext and works as followed
19+
*
20+
* - If the parent is an AttributeContainer, it will check if the parent has the attribute
21+
* - If the parent is not declaring the attribute, it will check if the parent is extending another type and test this extended type.
22+
*
23+
* @return Option<Type>
24+
*/
25+
public function __invoke(AttributeItem $item, ?SchemaItem $parent): Option
26+
{
27+
$parent = match(true) {
28+
$parent instanceof Item => $parent->getType(),
29+
default => $parent,
30+
};
31+
32+
if (!$parent instanceof Type) {
33+
return none();
34+
}
35+
36+
if ($parent instanceof AttributeContainer) {
37+
foreach ($parent->getAttributes() as $parentAttribute) {
38+
if ($parentAttribute->getName() === $item->getName()) {
39+
/** @var Option<Type> */
40+
return some($parent);
41+
}
42+
}
43+
}
44+
45+
$extensionBase = $parent->getExtension()?->getBase();
46+
if ($extensionBase) {
47+
return $this->__invoke($item, $extensionBase);
48+
}
49+
50+
return none();
51+
}
52+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Soap\WsdlReader\Metadata\Converter\Types;
4+
5+
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
6+
7+
final class ParentContext
8+
{
9+
public function __construct(
10+
public readonly SchemaItem $item,
11+
) {
12+
}
13+
}

src/Metadata/Converter/Types/TypesConverterContext.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ final class TypesConverterContext
2020
*/
2121
private array $visited = [];
2222

23+
private ?ParentContext $parentContext = null;
24+
2325
private function __construct(
2426
public readonly Namespaces $knownNamespaces
2527
) {
@@ -57,4 +59,16 @@ public function visit(Schema $schema, callable $visitor): TypeCollection
5759

5860
return $visitor($schema);
5961
}
62+
63+
public function onParent(?ParentContext $parentContext): self
64+
{
65+
$this->parentContext = $parentContext;
66+
67+
return $this;
68+
}
69+
70+
public function parent(): ?ParentContext
71+
{
72+
return $this->parentContext;
73+
}
6074
}

src/Metadata/Converter/Types/Visitor/AttributeContainerVisitor.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeSingle;
99
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group;
1010
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
11+
use Psl\Option\Option;
1112
use Soap\Engine\Metadata\Collection\PropertyCollection;
1213
use Soap\Engine\Metadata\Model\Property;
1314
use Soap\Engine\Metadata\Model\TypeMeta;
1415
use Soap\Engine\Metadata\Model\XsdType as EngineType;
1516
use Soap\WsdlReader\Metadata\Converter\Types\Configurator;
17+
use Soap\WsdlReader\Metadata\Converter\Types\Detector\AttributeDeclaringParentTypeDetector;
1618
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;
1719
use function Psl\Fun\pipe;
20+
use function Psl\Option\from_nullable;
1821
use function Psl\Result\wrap;
1922
use function Psl\Type\instance_of;
2023
use function Psl\Vec\flat_map;
@@ -88,8 +91,26 @@ private function parseAttribute(AttributeItem $attribute, TypesConverterContext
8891
return $this->parseAttributes($attribute, $context);
8992
}
9093

94+
// Detecting the type-name for an attribute is complex.
95+
// We first try to use the type name,
96+
// Next up is the base type of the restriction if there aren't any restriction checks configured.
97+
// Finally there is a fallback to the attribute name
9198
$attributeType = $attribute instanceof AttributeSingle ? $attribute->getType() : null;
92-
$typeName = $attributeType?->getName() ?: $attribute->getName();
99+
$attributeRestriction = $attributeType?->getRestriction();
100+
$attributeTypeName = $attributeType?->getName();
101+
$attributeRestrictionName = ($attributeRestriction && !$attributeRestriction->getChecks()) ? $attributeRestriction->getBase()?->getName() : null;
102+
103+
$typeName = $attributeTypeName ?: ($attributeRestrictionName ?: $attribute->getName());
104+
$engineType = EngineType::guess($typeName);
105+
106+
// If a name cannot be determined from the type, we fallback to the attribute name:
107+
// Prefix the attribute name with the parent element name resulting in a more unique type-name.
108+
if (!$attributeTypeName && !$attributeRestrictionName) {
109+
$engineType = (new AttributeDeclaringParentTypeDetector())($attribute, $context->parent()?->item)
110+
->andThen(static fn (Type $parent): Option => from_nullable($parent->getName()))
111+
->map(static fn (string $parentName): EngineType => $engineType->copy($parentName . ucfirst($typeName)))
112+
->unwrapOr($engineType);
113+
}
93114

94115
$configure = pipe(
95116
static fn (EngineType $engineType): EngineType => (new Configurator\AttributeConfigurator())($engineType, $attribute, $context),
@@ -98,7 +119,7 @@ private function parseAttribute(AttributeItem $attribute, TypesConverterContext
98119
return new PropertyCollection(
99120
new Property(
100121
$attribute->getName(),
101-
$configure(EngineType::guess($typeName))
122+
$configure($engineType)
102123
)
103124
);
104125
}

src/Metadata/Converter/Types/Visitor/InlineElementTypeVisitor.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
1212
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
1313
use Soap\Engine\Metadata\Collection\TypeCollection;
14+
use Soap\WsdlReader\Metadata\Converter\Types\ParentContext;
1415
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;
1516
use function Psl\Vec\flat_map;
1617

@@ -62,6 +63,6 @@ private function detectInlineTypes(ElementItem $element, TypesConverterContext $
6263
return new TypeCollection();
6364
}
6465

65-
return $elementVisitor($element, $context);
66+
return $elementVisitor($element, $context->onParent(new ParentContext($element)));
6667
}
6768
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
SOAP XML Schema 1001: Prepend element name before attribute type names for more unique type-names.
3+
--FILE--
4+
<?php
5+
include __DIR__."/test_schema.inc";
6+
$schema = <<<EOF
7+
<complexType name="VehicleCoreType">
8+
<sequence>
9+
<element name="VehType" minOccurs="0" type="string" />
10+
</sequence>
11+
<attribute name="DriveType" use="optional">
12+
<simpleType>
13+
<restriction base="NMTOKEN">
14+
<enumeration value="AWD" />
15+
<enumeration value="4WD" />
16+
<enumeration value="Unspecified" />
17+
</restriction>
18+
</simpleType>
19+
</attribute>
20+
</complexType>
21+
EOF;
22+
test_schema($schema,'type="tns:VehicleCoreType"');
23+
?>
24+
--EXPECT--
25+
Methods:
26+
> test(VehicleCoreType $testParam): void
27+
28+
Types:
29+
> http://test-uri/:VehicleCoreType {
30+
?string $VehType
31+
@?VehicleCoreTypeDriveType in (AWD|4WD|Unspecified) $DriveType
32+
}

0 commit comments

Comments
 (0)