Skip to content

Commit

Permalink
Improve bottom type inference
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Sep 3, 2023
1 parent c6450db commit b464a10
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 51 deletions.
7 changes: 6 additions & 1 deletion src/Formatter/LongTypeFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ final class LongTypeFormatter
public function __invoke(Type $type): string
{
$hasProps = (bool) $type->getProperties()->count();
$meta = $type->getXsdType()->getMeta();
$extension = $meta->extends()->map(
static fn ($extends): string => $extends['type']
)->unwrapOr(null);

$declaration = [
$type->getXsdType()->getXmlNamespace() . ':'.$type->getName(),
$type->getXsdType()->getBaseType() ? ' extends '.$type->getXsdType()->getBaseType() : '',
$extension ? ' extends '.$extension : '',
(new EnumFormatter())($type->getXsdType()),
(new UnionFormatter())($type->getXsdType()),
$hasProps ? ' {' : '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeRef;
use Soap\Engine\Metadata\Model\XsdType as EngineType;
use Soap\WsdlReader\Metadata\Converter\Types\Mapper\BaseTypeMapper;
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;

final class AttributeBaseTypeConfigurator
Expand All @@ -24,6 +23,6 @@ public function __invoke(EngineType $engineType, mixed $xsdType, TypesConverterC
default => null,
};

return (new BaseTypeMapper())($engineType, $baseType, $context);
return (new SimpleTypeConfigurator())($engineType, $baseType, $context);
}
}
13 changes: 3 additions & 10 deletions src/Metadata/Converter/Types/Configurator/ExtendsConfigurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,14 @@ public function __invoke(EngineType $engineType, mixed $xsdType, TypesConverterC
return $engineType;
}

$engineType = $engineType
return $engineType
->withMeta(
static fn (TypeMeta $meta): TypeMeta => $meta->withExtends([
'type' => $name,
'namespace' => $base?->getSchema()->getTargetNamespace() ?? '',
'isSimple' => $base instanceof SimpleType,
])
);

// Only set the base-type of the engine-type if current type is not part of the base-types.
// (e.g.: string, integer, ...)
if (!$context->isBaseSchema($xsdType->getSchema())) {
$engineType = $engineType->withBaseType($name);
}

return $engineType;
)
->withBaseType($name);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
<?php
declare(strict_types=1);

namespace Soap\WsdlReader\Metadata\Converter\Types\Mapper;
namespace Soap\WsdlReader\Metadata\Converter\Types\Configurator;

use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
use Soap\Engine\Metadata\Model\TypeMeta;
use Soap\Engine\Metadata\Model\XsdType as EngineType;
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;

final class BaseTypeMapper
final class SimpleBottomTypeConfigurator
{
public function __invoke(EngineType $engineType, ?Type $xsdType, TypesConverterContext $context): EngineType
{
if (!$xsdType || $context->isBaseSchema($xsdType->getSchema())) {
return $engineType;
if (!$xsdType || !$xsdType instanceof SimpleType || $context->isBaseSchema($xsdType->getSchema())) {
return $engineType->withBaseType(
$engineType->getBaseType() ?: 'mixed'
);
}

do {
Expand All @@ -26,30 +27,27 @@ public function __invoke(EngineType $engineType, ?Type $xsdType, TypesConverterC
return $engineType;
}

private function detectBaseType(EngineType $engineType, ?Type $xsdType, TypesConverterContext $contex): ?EngineType
private function detectBaseType(EngineType $engineType, ?Type $xsdType, TypesConverterContext $context): ?EngineType
{
if (!$xsdType) {
return $engineType;
return $engineType->withBaseType(
$engineType->getBaseType() ?: 'mixed'
);
}

if ($contex->isBaseSchema($xsdType->getSchema())) {
if ($context->isBaseSchema($xsdType->getSchema())) {
return $engineType->withBaseType(
$xsdType->getName() ?: $engineType->getBaseType()
);
}

if ($xsdType instanceof SimpleType) {
if ($xsdType->getList()) {
return $engineType
->withBaseType('array')
->withMeta(
static fn (TypeMeta $meta): TypeMeta => $meta->withIsList(true)
);
return (new SimpleListConfigurator())($engineType, $xsdType, $context);
}

if ($xsdType->getUnions()) {
return $engineType
->withBaseType('mixed');
return (new SimpleUnionsConfigurator())($engineType, $xsdType, $context);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public function __invoke(EngineType $engineType, mixed $xsdType, TypesConverterC

$configure = pipe(
static fn (EngineType $engineType): EngineType => (new RestrictionsConfigurator())($engineType, $xsdType->getRestriction(), $context),
static fn (EngineType $engineType): EngineType => (new SimpleListConfigurator())($engineType, $xsdType, $context),
static fn (EngineType $engineType): EngineType => (new SimpleUnionsConfigurator())($engineType, $xsdType, $context),
static fn (EngineType $engineType): EngineType => (new SimpleBottomTypeConfigurator())($engineType, $xsdType, $context),
);

return $configure(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function __invoke(MetaType $metaType, mixed $xsdType, TypesConverterConte
$mapUnions = new UnionTypesMapper();

return $metaType
->withBaseType('mixed')
->withMemberTypes(
$mapUnions(
$unions,
Expand Down
5 changes: 5 additions & 0 deletions src/Metadata/Converter/Types/Mapper/UnionTypesMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
namespace Soap\WsdlReader\Metadata\Converter\Types\Mapper;

use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
use Soap\Engine\Metadata\Model\XsdType as EngineType;
use Soap\WsdlReader\Metadata\Converter\Types\Configurator\SimpleListConfigurator;
use Soap\WsdlReader\Metadata\Converter\Types\Configurator\SimpleUnionsConfigurator;
use Soap\WsdlReader\Metadata\Converter\Types\TypesConverterContext;
use function Psl\Vec\filter_nulls;
use function Psl\Vec\map;

Expand Down
53 changes: 34 additions & 19 deletions src/Metadata/Converter/Types/Visitor/InlineElementTypeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
namespace Soap\WsdlReader\Metadata\Converter\Types\Visitor;

use GoetasWebservices\XML\XSDReader\Schema\Element\AbstractElementSingle;
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
use Soap\Engine\Metadata\Collection\TypeCollection;
Expand All @@ -24,29 +26,42 @@ public function __invoke(Type $xsdType, TypesConverterContext $context): TypeCol
return new TypeCollection();
}

$elementVisitor = new ElementVisitor();

return new TypeCollection(
...flat_map(
$xsdType->getElements(),
static function (ElementItem $element) use ($elementVisitor, $context): TypeCollection {
if (!$element instanceof AbstractElementSingle || $element instanceof ElementRef) {
return new TypeCollection();
}

// There is no need to create types for simple elements like strings.
if (!$element->getType() instanceof ComplexType || !$element->isLocal()) {
return new TypeCollection();
}

// If the element links to a named type, we already know about it.
if ($element->getType()?->getName()) {
return new TypeCollection();
}

return $elementVisitor($element, $context);
}
fn (ElementItem $element): TypeCollection => $this->detectInlineTypes($element, $context)
)
);
}

private function detectInlineTypes(ElementItem $element, TypesConverterContext $context): TypeCollection
{
$elementVisitor = new ElementVisitor();

// Handle nested element containers (like "choice" with inline elements)
if ($element instanceof ElementContainer) {
return new TypeCollection(
...flat_map(
$element->getElements(),
fn (ElementItem $child): TypeCollection => $this->detectInlineTypes($child, $context)
)
);
}

if (!$element instanceof AbstractElementSingle || $element instanceof ElementRef) {
return new TypeCollection();
}

// There is no need to create types for simple elements like strings.
if (!$element->getType() instanceof BaseComplexType || !$element->isLocal()) {
return new TypeCollection();
}

// If the element links to a named type, we already know about it.
if ($element->getType()?->getName()) {
return new TypeCollection();
}

return $elementVisitor($element, $context);
}
}
42 changes: 42 additions & 0 deletions tests/PhpCompatibility/schema1005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
SOAP XML Schema 1005: Nested inline elements wrapped in choice element container
--FILE--
<?php
include __DIR__."/test_schema.inc";
$schema = <<<EOF
<complexType name="LoyaltyTravelInfoType">
<choice>
<element name="HotelStayInfo">
<complexType>
<sequence>
<element name="ReservationID" type="string" />
</sequence>
</complexType>
</element>
<element name="AirFlightInfo">
<complexType>
<sequence>
<element name="FlightSegment" type="string"/>
</sequence>
</complexType>
</element>
</choice>
</complexType>
EOF;
test_schema($schema,'type="tns:LoyaltyTravelInfoType"');
?>
--EXPECT--
Methods:
> test(LoyaltyTravelInfoType $testParam): void

Types:
> http://test-uri/:LoyaltyTravelInfoType {
?HotelStayInfo $HotelStayInfo
?AirFlightInfo $AirFlightInfo
}
> http://test-uri/:HotelStayInfo {
?string $ReservationID
}
> http://test-uri/:AirFlightInfo {
?string $FlightSegment
}
16 changes: 16 additions & 0 deletions tests/PhpCompatibility/schema1006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
SOAP XML Schema 1006: simple element types
--FILE--
<?php
include __DIR__."/test_schema.inc";
$schema = <<<EOF
<element name="AcknowledgeReceipt" type="anyType" />
EOF;
test_schema($schema,'type="tns:AcknowledgeReceipt"');
?>
--EXPECT--
Methods:
> test(AcknowledgeReceipt $testParam): void

Types:
> http://test-uri/:AcknowledgeReceipt
49 changes: 49 additions & 0 deletions tests/PhpCompatibility/schema1007.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--TEST--
SOAP XML Schema 1007: Deeply nested complex-type declarations inside complexTypes
--FILE--
<?php
include __DIR__."/test_schema.inc";
$schema = <<<EOF
<complexType name="LocationType">
<simpleContent>
<extension base="string">
<attribute name="LocationCode" type="string" use="optional" />
</extension>
</simpleContent>
</complexType>
<complexType name="VerificationType">
<sequence>
<element name="Email" type="string" minOccurs="0" />
<element name="StartLocation" minOccurs="0">
<complexType>
<simpleContent>
<extension base="tns:LocationType">
<attribute name="AssociatedDateTime" type="dateTime" use="optional" />
</extension>
</simpleContent>
</complexType>
</element>
</sequence>
</complexType>
EOF;
test_schema($schema,'type="tns:VerificationType"');
?>
--EXPECT--
Methods:
> test(VerificationType $testParam): void

Types:
> http://test-uri/:LocationType extends string {
string $_
@string $LocationCode
}
> http://test-uri/:VerificationType {
?string $Email
?StartLocation $StartLocation
}
> http://test-uri/:StartLocation extends LocationType {
string $_
@string $LocationCode
@dateTime $AssociatedDateTime
}
2 changes: 0 additions & 2 deletions tests/PhpCompatibility/test_schema.inc
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ function test_schema($schema, $type, $style="rpc",$use="encoded", $attributeForm
echo implode(PHP_EOL, $metadata->getMethods()->map(fn(Method $method) => ' > ' . (new ShortMethodFormatter())($method)));
echo PHP_EOL . PHP_EOL;

//var_dump($metadata->getTypes()->fetchFirstByName('SpecialEquipPrefs'));
//var_dump($metadata->getTypes()->fetchFirstByName('SpecialEquipPref'));
echo "Types:" . PHP_EOL;
echo implode(PHP_EOL, $metadata->getTypes()->map(fn(Type $type) => ' > ' . (new LongTypeFormatter())($type)));
echo PHP_EOL . PHP_EOL;
Expand Down

0 comments on commit b464a10

Please sign in to comment.