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

Generate backed enums from XSD #561

Merged
merged 1 commit into from
Jan 3, 2025
Merged

Generate backed enums from XSD #561

merged 1 commit into from
Jan 3, 2025

Conversation

veewee
Copy link
Contributor

@veewee veewee commented Dec 20, 2024

This PR will generate PHP backed enum types for XSD enumerations:

<simpleType name="PhoneTypeEnum">
    <annotation>
        <documentation xml:lang="en">All phone types</documentation>
    </annotation>
    <restriction base="string">
        <enumeration value="Home"/>
        <enumeration value="Office"/>
        <enumeration value="Gsm"/>
        <enumeration value="123_énvalid  qsdf sqdf qsd f"/>
    </restriction>
</simpleType>
<simpleType name="IntEnum">
    <restriction base="int">
        <enumeration value="0"/>
        <enumeration value="1"/>
        <enumeration value="2"/>
    </restriction>
</simpleType>
<element name="GetCustomerDetailsRequest">
    <complexType>
        <sequence>
            <element name="phone" type="tns:PhoneTypeEnum" />
            <element name="int" type="tns:IntEnum" />
        </sequence>
    </complexType>
</element>
enum IntEnum: int {
    case Value_0 = 0;
    case Value_1 = 1;
    case Value_2 = 2;
}


enum PhoneTypeEnum: string {
    case Empty = ''
    case Home = 'Home';
    case Office = 'Office';
    case Mobile = 'Mobile';
    case Value_123_NvalidQsdfSqdfQsdF = '123_énvalid  qsdf sqdf qsd f';
}

It splits the generated TypeMap into 2 sections:

class AppClassmap
{
    public static function types() : \Soap\Encoding\ClassMap\ClassMapCollection
    {
        return new ClassMapCollection(
            new ClassMap('http://example.com/customerdetails', 'GetCustomerDetailsRequest', Type\GetCustomerDetailsRequest::class),
            new ClassMap('http://example.com/customerdetails', 'GetCustomerDetailsResponse', Type\GetCustomerDetailsResponse::class),
        );
    }

    public static function enums() : \Soap\Encoding\ClassMap\ClassMapCollection
    {
        return new ClassMapCollection(
            new ClassMap('http://example.com/customerdetails', 'PhoneTypeEnum', Type\PhoneTypeEnum::class),
            new ClassMap('http://example.com/customerdetails', 'IntEnum', Type\IntEnum::class),
        );
    }
}

You can regenerate your existing classmap like this:

./vendor/bin/soap-client generate:classmap --config=config/soap-client.php

Which will be used by the generated Client Factory:

$engine = DefaultEngineFactory::create(
    EngineOptions::defaults($wsdl)
        ->withEncoderRegistry(
            EncoderRegistry::default()
                ->addClassMapCollection(AppClassmap::types())
                ->addBackedEnumClassMapCollection(AppClassmap::enums())
        )
);

The generated property types will now contain a link to the generated backed enum type:

class GetCustomerDetailsRequest implements RequestInterface
{
    /**
     * All phone types
     *
-     * @var 'Home' | 'Office' | 'Gsm' | '123_énvalid  qsdf sqdf qsd f_Gsm'
+     * @var \App\Type\PhoneTypeEnum
     */
-    private string $phone;
+    private \App\Type\PhoneTypeEnum $phone;

    /**
-      * @var '0' | '1' | '2'
+     * @var \App\Type\IntEnum
     */
-    private int $int;
+    private \App\Type\IntEnum $int;
}

Which will then be used to power the encoding package:

$encoded = $iso->to(
    new Type\GetCustomerDetailsRequest(
        Type\PhoneTypeEnum::Gsm,
        Type\IntEnum::Value_1
    )
);
dump(Document::fromXmlString($encoded, pretty_print())->toXmlString());

$decoded = <<<EOXML
    <tns:GetCustomerDetailsRequest xmlns:tns="http://example.com/customerdetails">
      <phone>Gsm</phone>
      <int>1</int>
    </tns:GetCustomerDetailsRequest>
EOXML;

dump(iso->from($decoded));
^ """
<?xml version="1.0"?>\n
<GetCustomerDetailsRequest>\n
  <phone>Gsm</phone>\n
  <int>1</int>\n
</GetCustomerDetailsRequest>\n
"""
^ App\Type\GetCustomerDetailsRequest^ {#1765
  -phone: App\Type\PhoneTypeEnum^ {#1740
    +name: "Gsm"
    +value: "Gsm"
  }
  -int: App\Type\IntEnum^ {#1747
    +name: "Value_1"
    +value: 1
  }
}

Local enumerations

Your XSD might contain types that contains their own enumerations that are not available as global schema simple-types:

<complexType name="VehicleCoreType">
    <attribute name="DriveType" use="optional">
        <simpleType>
            <restriction base="NMTOKEN">
                <enumeration value="AWD" />
                <enumeration value="4WD" />
                <enumeration value="Unspecified" />
            </restriction>
        </simpleType>
    </attribute>
</complexType>

Since these types don't really have a global name and might conflict with other types in your schema, we decided to only generate global types by default. In this case, the code will keep on generating the list of possible values inside the docblocks on the property.

If you DO want to opt-in to local enumeration generation, you can do so from within the code generation config. As mentioned before : this might be conflicting with other parts of the XSD, so use with care !

use Phpro\SoapClient\CodeGenerator\Config\Config;
use Phpro\SoapClient\CodeGenerator\Config\EnumerationGenerationStrategy;

Config::create()
    ->setEnumerationGenerationStrategy(EnumerationGenerationStrategy::LocalAndGlobal);

@veewee veewee force-pushed the enum-generator branch 5 times, most recently from ae96bd9 to e6959a3 Compare December 20, 2024 13:02
@veewee veewee added this to the 4.0.0 milestone Dec 20, 2024
@veewee
Copy link
Contributor Author

veewee commented Dec 20, 2024

@rauanmayemir

This PR makes it possible to generate PHP enums directly from the WSDL.
I've tested it on a couple of webservices and the results are looking quite good for both global and inline (local) enumerations. (for more info, see the initial PR comment on how you can opt-in to generating even more enums)

Can you try to run it on your services to see how that works for you?

@rauanmayemir
Copy link

Tried regenerating with enums and it works. I don't have complex schemas with 'local' enums, but what I do have works fine. And enum case generation is rather beautiful. 😁

@MudrakIvan
Copy link

Hi,

I've tested the code on the scheme with multiple local enums and ran into some name conflicts (unlucky, the WSDL is not public). The only other framework that generates local enums (what I know) is svcutil. To reduce conflicts, they prepend the name of the parent type to the enum name.

Example XSD:

<xs:element name="Person">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Name" type="xs:string"/>
        <xs:element name="Gender">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="Male"/>
              <xs:enumeration value="Female"/>
              <xs:enumeration value="Other"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
        <xs:element name="Age" type="xs:integer"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

The output enum would be called PersonGender.

Nevertheless, it's an awesome addition to this framework.

@rauanmayemir
Copy link

Prepending the parent type’s name is actally a nice strategy.

@veewee
Copy link
Contributor Author

veewee commented Dec 29, 2024

It should be fairly easy to add indeed.
Will play around with the idea soon. Thanks for your feedback.

@veewee
Copy link
Contributor Author

veewee commented Jan 2, 2025

@MudrakIvan It turns out prepending the type name is not as straight forward as I first though...
Can you validate this PR in combination with the changes in php-soap/wsdl-reader#41 ?

@rauanmayemir The TypeReplacer got reverted as discussed.

@veewee veewee merged commit 0031c3d into phpro:v4.x Jan 3, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants