Problem with WSDL AutoDiscovery

I am using WSDL AutoDiscovery to generate a WSDL based on my system classes. I have to follow a structure given by an XML, in some cases of this XML, I have an array of objects and I don’t know how to define that in the docblocks, I upload an example:
<"AvailDestinations>
<"Destination type = “CTY” code = “BUE”/>
<"Destination type = “CTY” code = “MIA”/>
</"AvailDestinations>

In that case I would have to create an object that does not have a specific number of destinations defined and each destination must have the structure already defined. If I create the AvailDestinations class, what attributes should it have and what type should they be? It is possible to create an array of defined objects (like Destination[-] or something like that). Any help will be appreciated.

Hey @stefbortolin ,

could you show the xsd definition of the AvailDestinations element? Normally it equals a specific type. I guess this type has an element Destination defined, that could occur zero or more than one times. In this case the euivalent property of a php class is an array. Without knowing the exact xsd definition it is only guessing. So … it would be nice to see the xsd definition.

Hi! I think the definition of AvailDestinations is something like that, i want to know how i do this in php classes and atributes with WSDL AutoDiscovery:
<xs:element name=“AvailDestinations”>
<"xs:complexType>
<"xs:sequence>
<"xs:element name=“Destination” minOccurs=“0” maxOccurs=“unbounded”>
<"xs:complexType>
<"xs:attribute name=“type” type=“xs:string” />
<"xs:attribute name=“code” type=“xs:string” />
</"xs:complexType>
</"xs:element>
</"xs:sequence>
</"xs:complexType>
</"xs:element>

I use the " in the start of the xml to evade errors, ignore them.

Hey @stefbortolin ,

I 'm sorry for the late reply. But … weekend, barbeque and whisky … you know. :wink:

Edit the wsdl file and store it locally

It is always difficult for autodiscovery to discover anonymous complex type declarations. The results are often unsatisfactory and are not sufficient for smooth implementation. You should save the content of the WSDL file locally so that you can make changes to the definitions. Don’t worry, we will not fundamentally change the web service. We will just give the unnamed complex types a name. The XSD definitions have to be minimally adapted for this. You don 't have to do this. But for your comfort, I 'd suggest, that you keep the xsd pretty much the same as your PHP data objects.

Just change your element definition as follows:

<!-- element definition with type attribute -->
<xs:element name="AvailDestinations" type="ns:AvailableDestinations" />

<!-- definition for the type AvailableDestinations" -->
<xs:complexType name="AvailableDestinations">
    <xs:sequence>
        <!-- add the type attribut for the Destination complex type -->
        <xs:element name="Destination" type="ns:Destination" minOccurs="0" maxOccurs="unbounded" />
    </xs:sequence>
</xs:complexType>

<!-- definition of the Destination complex type -->
<xs:complexType name="Destination">
    <xs:attribute name="type" type="xs:string" />
    <xs:attribute name="code" type="xs:string" />
</xs:complexType>

Please be aware of existing complex types. If there are already declarations with the given namens from the example, just change them. The given example namespace ns: must be the target namespace of your document. Somewhere in the wsdl file there should be a targetNamespace attribute and a xmlns: namespace definition with a shorthand. Use this for your new complex Type definitions.

The PHP data objects

Since we have edited the definitions a bit, we can code our own PHP data objects.

readonly class AvailableDestinations
{
    public function __construct(
        public array $Destination = [],
    ) {}
}

This is the first data object AvailableDestinations. As we can see in the XSD definitions, the element Destination inside this object is unbounded. It might occur. It can occure more than one time. Therefore it is always an array on PHP side. The type hint is the PHP data object is Destination and follows our xsd declaration.

readonly class Destination
{
    public function __construct(
        public string $type,
        public string $code,
    ) {}
}

The xsd definition for Destination defines attributes only. It is a bit misleading on PHP side, because xsd attributes are PHP properties, too. Confusing, because xsd elements of a complex type are also PHP class properties.

BTW: Readonly classes, because they only exist to transport data. Once initialized you won 't change their contents.

Use it all together with the PHP soap client

Don 't get confused here. The laminas/laminas-soap package also uses the native PHP SoapClient class. You can initialize it as follows:

$wsdl = __DIR__ . '/path/to/your/local/wsdl/file.wsdl';
$client = new \SoapClient($wsdl, [
    ...
    'classmap' => [
        ...
        'AvailableDestinations' => \Fully\Qualified\Namespace\AvailableDestinations::class,
        'Destinations' => \Fully\Qualified\Namespace\Destinations::class,
        ...
    ],
    ...
]);

The classmap option should take all PHP dataobjects as an associative array. The name of the XSD complex types are the keys. The values are the fully qualified names of the PHP data objects. Initializing the soap client with this option makes it pretty easy from now on.

$destination1 = new Destination('CTY', 'BUE');
$destination2 = new Destination('CTY', 'MIA');
$availableDestinations = new AvailableDestinations([
    $destination1,
    $destination2,
]);

$result = $client->someWebserviceMethod($availableDestinations);

Initialize the PHP data objects with the wanted values and just send them with your webservice method.

Useful tipps and tricks

Wie Du vielleicht auch schon bemerkt hast, ist PHP SOAP eine Geschichte voller Geheimnisse. Ich möchte Dir ein paar Tipps mit auf den Weg geben.

  • Always initialize your soap client with the exceptions options on true. You can catch every error and exception occuring while executing your webservice action.
  • Use the option trace and enjoy the comfort of the __lastRequest(), __lastResponse(), __lastRequestHeaders() and __lastResponseHeaders() functions. For example: You can see your last sent XML request from now on.
  • Use the features option of your soap client, when the response contains unbounded elements, which can appear zero or more times. With SOAP_SINGLE_ELEMENT_ARRAYS your PHP property will even be an array, when there are zero or only one element in the response.
  • If the attributes aren 't formed well in the request try the PHP SoapVar class with your PHP data objects.
  • When using the classmap option you should know, that the constructor of your PHP data objects is not used by the soap client. Therefore it uses the magic methods __get() and __set(). If you want to recognize these methods, I 'd suggest a parent class from which the data objects inherit and implements these two methods.
1 Like