Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Number of occurs is ignored for choice #540

Open
MudrakIvan opened this issue Aug 26, 2024 · 2 comments
Open

Number of occurs is ignored for choice #540

MudrakIvan opened this issue Aug 26, 2024 · 2 comments

Comments

@MudrakIvan
Copy link

Bug Report

Q A
BC Break no
Version 4.0.0-alpha1

Summary

Hello,

when generating classes from WSDL I noticed, that choice of complex types were generated as nullable property for each choice but ignoring the number of occurs.

Current behavior

Currenty when generating choice of complexType modifers maxOccurs and minOccurs are ignored.

How to reproduce

This bug is possible to reproduce on this simple WSDL:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://example.com/customerdetails"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://example.com/customerdetails"
    name="CustomerDetailsService">

    <!-- Data Types -->
    <types>
        <schema xmlns="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://example.com/customerdetails"
            xmlns:tns="http://example.com/customerdetails"
            elementFormDefault="qualified">

            <complexType name="Customer">
                <sequence>
                    <element name="customerName" type="string" />
                    <element name="customerEmail" type="string" />
                </sequence>
            </complexType>

            <complexType name="CertifiedCustomer">
                <complexContent>
                    <extension base="tns:Customer">
                        <sequence>
                            <element name="certificationLevel" type="string" />
                        </sequence>
                    </extension>
                </complexContent>
            </complexType>

            <element name="GetCustomerDetailsRequest">
                <complexType>
                    <sequence>
                        <element name="customerId" type="string" />
                    </sequence>
                </complexType>
            </element>
            <element name="GetCustomerDetailsResponse">
                <complexType>
                    <sequence maxOccurs="unbounded">
                        <choice>
                            <element name="Customer" type="tns:Customer" />
                            <element name="CertifedCustomer" type="tns:CertifiedCustomer" />
                        </choice>
                    </sequence>
                </complexType>
            </element>
        </schema>
    </types>

    <!-- Message Definitions -->
    <message name="GetCustomerDetailsRequestMessage">
        <part name="parameters" element="tns:GetCustomerDetailsRequest" />
    </message>
    <message name="GetCustomerDetailsResponseMessage">
        <part name="parameters" element="tns:GetCustomerDetailsResponse" />
    </message>

    <!-- Port Type (Abstract Interface) -->
    <portType name="CustomerDetailsPortType">
        <operation name="GetCustomerDetails">
            <input message="tns:GetCustomerDetailsRequestMessage" />
            <output message="tns:GetCustomerDetailsResponseMessage" />
        </operation>
    </portType>

    <!-- Binding (Concrete Implementation) -->
    <binding name="CustomerDetailsBinding" type="tns:CustomerDetailsPortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
        <operation name="GetCustomerDetails">
            <soap:operation soapAction="http://example.com/GetCustomerDetails" />
            <input>
                <soap:body use="literal" />
            </input>
            <output>
                <soap:body use="literal" />
            </output>
        </operation>
    </binding>

    <!-- Service Definition -->
    <service name="CustomerDetailsService">
        <documentation>This service provides customer details based on customer ID.</documentation>
        <port name="CustomerDetailsPort" binding="tns:CustomerDetailsBinding">
            <soap:address location="http://example.com/customerdetails/service" />
        </port>
    </service>
</definitions>

Class GetCustomerDetailsResponse should be generated. This class will have nullable property of $Customer and $CertifiedCustomer.

class GetCustomerDetailsResponse
{
    private ?Customer $Customer = null;

    private ?CertifiedCustomer $CertifiedCustomer = null;

    ...
}

Expected behavior

Other languages like c# and java genaretes one list with annotatios/attributes however this solution would have impact on other parts like the encoding component. For most of the use-cases mutliple arrays should be sufficient, so that the php class would look like this:

class GetCustomerDetailsResponse
{
    /** @var Customer[] */
    private array $Customer = [];

    /** @var CertifiedCustomer[] */
    private array $CertifiedCustomer = [];

    ...
}
@veewee
Copy link
Contributor

veewee commented Aug 30, 2024

Hi,

Thanks for reporting.
I've spent about 4 hours trying to figure out how this could be achieved, but I haven't found a good solution for this problem so far.

Nested sequence > choice > element with maxOccurs are tricky because they get flattened to a non-nested structure.
To give you an example:

sequence:max(5)
  choice
    element1:max(3)

This gets flattened to element1 that can only be available once, which is not correct because it should be a max of 15.

It even gets more complex:

sequence:max(5)
  choice
    element1:max(3)
    element2:max(3)

This means that there is some kind of order possible:

  • element1 can be set up to 3 times in 1 sequence
  • Once you add an element2, the next sequence is being assumed.

This makes that the maxOccurs are very depending on how many element1's there are. AND given it is a sequence, they should be rendered sequentially. For example:

  • If you have 5 element1's and 3 element2's
  • it is invalid to render the first 5 element1's.
  • It should for example render this way in order to be compatible to the provided XSD:
    • 3 element1's
    • next 3 element2's
    • finally the remaining 2 element1's

This brings me to the acutal problem : The wsdl-reader package is flattening those sequences and choices into a simplified representation of the surrounding complexType. In your very specific case, the GetCustomerDetailsResponse will contain 2 possible elements Customer or CertifiedCustomer since the choice get's flattened into "properties".
For those specific elements a maxOccur of 1 is valid, given it is part of a choice. But given the context of the unbounded sequence, it should actually be -1. This is a right assumption for your specific case, but doesn't work with the more complex examples as described above.

I'm not really sure on how to continue on this problem to be honest. Any idea's are welcome.

In the meantime, you could overwrite those specific types by using https://github.com/phpro/soap-client/blob/v4.x/docs/drivers/metadata.md#type-replacements. Probably by setting:

$meta
      ->withMaxOccurs(-1)
      ->withIsNullable(false)
      ->withIsList(true)
      ->withIsRepeatingElement(true);

@MudrakIvan
Copy link
Author

MudrakIvan commented Aug 30, 2024

Firstly, thanks for thorough answer. Secondly I created similar xsd and tested behaviour of c# tools. In c#, if occurs is greater then 1, then it's treated in same way as maxOccurs=unbounded. With this behaviour there is no longer a problem of checking, if number of occurs of elements is correct. I know it's not a perfect solution, but it seems like it's sufficient enough for most cases.

Nevertheless, here is the xsd I tested on:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xs:element name="Root">
        <xs:complexType>
            <xs:sequence maxOccurs="5">
                <xs:choice>
                    <xs:element name="Element1" type="Element1Type" maxOccurs="3" />
                    <xs:element name="Element2" type="Element2Type" maxOccurs="3" minOccurs="0" />
                </xs:choice>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <!-- Define complex type for Element1 -->
    <xs:complexType name="Element1Type">
        <xs:sequence>
            <xs:element name="SubElement1" type="xs:string" />
            <xs:element name="SubElement2" type="xs:int" />
        </xs:sequence>
    </xs:complexType>

    <!-- Define complex type for Element2 -->
    <xs:complexType name="Element2Type">
        <xs:sequence>
            <xs:element name="SubElementA" type="xs:string" />
            <xs:element name="SubElementB" type="xs:date" />
        </xs:sequence>
    </xs:complexType>

</xs:schema>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants
@veewee @MudrakIvan and others