Skip to content

Commit

Permalink
Merge pull request #41 from veewee/attribute-type-names
Browse files Browse the repository at this point in the history
Prefix local types with parent type-name
  • Loading branch information
veewee authored Jan 3, 2025
2 parents 1128124 + b25d608 commit ffb4e82
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 12 deletions.
16 changes: 13 additions & 3 deletions src/Metadata/Converter/SchemaToTypesConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
)
)
])
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Metadata\Converter\Types\Detector;

use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
use GoetasWebservices\XML\XSDReader\Schema\Item;
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
use Psl\Option\Option;
use function Psl\Option\none;
use function Psl\Option\some;

final class AttributeDeclaringParentTypeDetector
{
/**
* This class detects the declaring parent type of an attribute.
* It can be used together with the ParentContext and works as followed
*
* - If the parent is an AttributeContainer, it will check if the parent has the attribute
* - If the parent is not declaring the attribute, it will check if the parent is extending another type and test this extended type.
*
* @return Option<Type>
*/
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<Type> */
return some($parent);
}
}
}

$extensionBase = $parent->getExtension()?->getBase();
if ($extensionBase) {
return $this->__invoke($item, $extensionBase);
}

return none();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Metadata\Converter\Types\Detector;

use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeSingle;
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
use Psl\Option\Option;
use Soap\WsdlReader\Metadata\Converter\Types\ParentContext;
use function Psl\Option\from_nullable;

final class AttributeTypeNameDetector
{
public function __invoke(AttributeItem $attribute, ParentContext $parentContext): string
{
$attributeType = $attribute instanceof AttributeSingle ? $attribute->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;
}
}
31 changes: 31 additions & 0 deletions src/Metadata/Converter/Types/Detector/ElementTypeNameDetector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Metadata\Converter\Types\Detector;

use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementSingle;
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
use Soap\WsdlReader\Metadata\Converter\Types\ParentContext;

final class ElementTypeNameDetector
{
public function __invoke(ElementItem $element, ParentContext $parentContext, ?string $calculatedTypeName = null): string
{
$type = $element instanceof ElementSingle ? $element->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;
}
}
38 changes: 38 additions & 0 deletions src/Metadata/Converter/Types/ParentContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);

namespace Soap\WsdlReader\Metadata\Converter\Types;

use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
use function Psl\Iter\first;
use function Psl\Iter\last;

final class ParentContext
{
/**
* @param non-empty-list<SchemaItem> $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);
}
}
24 changes: 24 additions & 0 deletions src/Metadata/Converter/Types/TypesConverterContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -20,9 +23,15 @@ final class TypesConverterContext
*/
private array $visited = [];

/**
* @var Option<ParentContext>
*/
private Option $parentContext;

private function __construct(
public readonly Namespaces $knownNamespaces
) {
$this->parentContext = none();
}

public static function default(Namespaces $knownNamespaces): self
Expand Down Expand Up @@ -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<ParentContext>
*/
public function parent(): Option
{
return $this->parentContext;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

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;
use Soap\Engine\Metadata\Model\Property;
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;
Expand Down Expand Up @@ -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),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
));
}
}
32 changes: 32 additions & 0 deletions tests/PhpCompatibility/schema1013.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
SOAP XML Schema 1001: Prepend parent type name before attribute type names for more unique type-names.
--FILE--
<?php
include __DIR__."/test_schema.inc";
$schema = <<<EOF
<complexType name="VehicleCoreType">
<sequence>
<element name="VehType" minOccurs="0" type="string" />
</sequence>
<attribute name="DriveType" use="optional">
<simpleType>
<restriction base="NMTOKEN">
<enumeration value="AWD" />
<enumeration value="4WD" />
<enumeration value="Unspecified" />
</restriction>
</simpleType>
</attribute>
</complexType>
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
}
Loading

0 comments on commit ffb4e82

Please sign in to comment.