phpro / soap-client

A general purpose SOAP client for PHP
MIT License
855 stars 175 forks source link

The generated class does not correspond to reality #496

Closed h4kuna closed 8 months ago

h4kuna commented 8 months ago

Bug Report

Q A
BC Break no
Version 3.1.1

Summary

Hello, I use WSDL. I try to call method getImmobili. Where is getImmobiliResponse->immobili->immobile described. The gerated class looks like:

<?php

namespace App\ChannelManager\ImmobiNet\Soap\Agenzia\Type;

class Immobile
{
    private ?string $id;

    private ?int $idAgenzia;

    public function getId(): ?string
    {
        return $this->id;
    }

    public function withId(?string $id): static
    {
        $new = clone $this;
        $new->id = $id;

        return $new;
    }

    public function getIdAgenzia(): ?int
    {
        return $this->idAgenzia;
    }

    public function withIdAgenzia(?int $idAgenzia): static
    {
        $new = clone $this;
        $new->idAgenzia = $idAgenzia;

        return $new;
    }
}

And returned object looks like Snímek obrazovky z 2024-01-15 10-01-42

The result is, I can't access to properties. The previous version 2.4.2 was generate all methods and has bug with property name #477.

Generated class with version 2.4.2

<?php

namespace App\ChannelManager\ImmobiNet\Soap\Agenzia\Type;

class Immobile
{
    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\Categoria
     */
    private $categoria;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\Citta
     */
    private $citta;

    /**
     * @var string
     */
    private $indirizzo;

    /**
     * @var string
     */
    private $numero;

    /**
     * @var string
     */
    private $zona;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\GeoCoordinates
     */
    private $coords;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfDescrizione
     */
    private $descrizione;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfAllegato
     */
    private $allegati;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAbitabile
     */
    private $composizione;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAppartamento
     */
    private $appartamento;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileSuperficie
     */
    private $superficie;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileTerreno
     */
    private $terreno;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileCasa
     */
    private $casa;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileVilla
     */
    private $villa;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileCapannone
     */
    private $capannone;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAce
     */
    private $ace;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfAttributo
     */
    private $attributi;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\TipoCucina
     */
    private $cucina;

    /**
     * @var string
     */
    private $giardino;

    /**
     * @var string
     */
    private $parcheggio;

    /**
     * @var string
     */
    private $riscaldamento;

    /**
     * @var string
     */
    private $boxAuto;

    /**
     * @var string
     */
    private $postoAuto;

    /**
     * @var string
     */
    private $impiantoTv;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileTuristico
     */
    private $immobileTuristico;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfDistanzaPoi
     */
    private $distanze;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\CodiceIdentificativo
     */
    private $codiceIdentificativo;

    /**
     * @var \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfInformazioniExtra
     */
    private $informazioniExtra;

    /**
     * @var string
     */
    private $id;

    /**
     * @var int
     */
    private $idAgenzia;

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\Categoria
     */
    public function getCategoria()
    {
        return $this->categoria;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\Categoria $categoria
     *
     * @return Immobile
     */
    public function withCategoria($categoria)
    {
        $new = clone $this;
        $new->categoria = $categoria;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\Citta
     */
    public function getCitta()
    {
        return $this->citta;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\Citta $citta
     *
     * @return Immobile
     */
    public function withCitta($citta)
    {
        $new = clone $this;
        $new->citta = $citta;

        return $new;
    }

    /**
     * @return string
     */
    public function getIndirizzo()
    {
        return $this->indirizzo;
    }

    /**
     * @param string $indirizzo
     *
     * @return Immobile
     */
    public function withIndirizzo($indirizzo)
    {
        $new = clone $this;
        $new->indirizzo = $indirizzo;

        return $new;
    }

    /**
     * @return string
     */
    public function getNumero()
    {
        return $this->numero;
    }

    /**
     * @param string $numero
     *
     * @return Immobile
     */
    public function withNumero($numero)
    {
        $new = clone $this;
        $new->numero = $numero;

        return $new;
    }

    /**
     * @return string
     */
    public function getZona()
    {
        return $this->zona;
    }

    /**
     * @param string $zona
     *
     * @return Immobile
     */
    public function withZona($zona)
    {
        $new = clone $this;
        $new->zona = $zona;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\GeoCoordinates
     */
    public function getCoords()
    {
        return $this->coords;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\GeoCoordinates $coords
     *
     * @return Immobile
     */
    public function withCoords($coords)
    {
        $new = clone $this;
        $new->coords = $coords;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfDescrizione
     */
    public function getDescrizione()
    {
        return $this->descrizione;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfDescrizione $descrizione
     *
     * @return Immobile
     */
    public function withDescrizione($descrizione)
    {
        $new = clone $this;
        $new->descrizione = $descrizione;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfAllegato
     */
    public function getAllegati()
    {
        return $this->allegati;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfAllegato $allegati
     *
     * @return Immobile
     */
    public function withAllegati($allegati)
    {
        $new = clone $this;
        $new->allegati = $allegati;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAbitabile
     */
    public function getComposizione()
    {
        return $this->composizione;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAbitabile $composizione
     *
     * @return Immobile
     */
    public function withComposizione($composizione)
    {
        $new = clone $this;
        $new->composizione = $composizione;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAppartamento
     */
    public function getAppartamento()
    {
        return $this->appartamento;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAppartamento $appartamento
     *
     * @return Immobile
     */
    public function withAppartamento($appartamento)
    {
        $new = clone $this;
        $new->appartamento = $appartamento;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileSuperficie
     */
    public function getSuperficie()
    {
        return $this->superficie;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileSuperficie $superficie
     *
     * @return Immobile
     */
    public function withSuperficie($superficie)
    {
        $new = clone $this;
        $new->superficie = $superficie;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileTerreno
     */
    public function getTerreno()
    {
        return $this->terreno;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileTerreno $terreno
     *
     * @return Immobile
     */
    public function withTerreno($terreno)
    {
        $new = clone $this;
        $new->terreno = $terreno;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileCasa
     */
    public function getCasa()
    {
        return $this->casa;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileCasa $casa
     *
     * @return Immobile
     */
    public function withCasa($casa)
    {
        $new = clone $this;
        $new->casa = $casa;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileVilla
     */
    public function getVilla()
    {
        return $this->villa;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileVilla $villa
     *
     * @return Immobile
     */
    public function withVilla($villa)
    {
        $new = clone $this;
        $new->villa = $villa;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileCapannone
     */
    public function getCapannone()
    {
        return $this->capannone;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileCapannone $capannone
     *
     * @return Immobile
     */
    public function withCapannone($capannone)
    {
        $new = clone $this;
        $new->capannone = $capannone;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAce
     */
    public function getAce()
    {
        return $this->ace;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileAce $ace
     *
     * @return Immobile
     */
    public function withAce($ace)
    {
        $new = clone $this;
        $new->ace = $ace;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfAttributo
     */
    public function getAttributi()
    {
        return $this->attributi;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfAttributo $attributi
     *
     * @return Immobile
     */
    public function withAttributi($attributi)
    {
        $new = clone $this;
        $new->attributi = $attributi;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\TipoCucina
     */
    public function getCucina()
    {
        return $this->cucina;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\TipoCucina $cucina
     *
     * @return Immobile
     */
    public function withCucina($cucina)
    {
        $new = clone $this;
        $new->cucina = $cucina;

        return $new;
    }

    /**
     * @return string
     */
    public function getGiardino()
    {
        return $this->giardino;
    }

    /**
     * @param string $giardino
     *
     * @return Immobile
     */
    public function withGiardino($giardino)
    {
        $new = clone $this;
        $new->giardino = $giardino;

        return $new;
    }

    /**
     * @return string
     */
    public function getParcheggio()
    {
        return $this->parcheggio;
    }

    /**
     * @param string $parcheggio
     *
     * @return Immobile
     */
    public function withParcheggio($parcheggio)
    {
        $new = clone $this;
        $new->parcheggio = $parcheggio;

        return $new;
    }

    /**
     * @return string
     */
    public function getRiscaldamento()
    {
        return $this->riscaldamento;
    }

    /**
     * @param string $riscaldamento
     *
     * @return Immobile
     */
    public function withRiscaldamento($riscaldamento)
    {
        $new = clone $this;
        $new->riscaldamento = $riscaldamento;

        return $new;
    }

    /**
     * @return string
     */
    public function getBoxAuto()
    {
        return $this->boxAuto;
    }

    /**
     * @param string $boxAuto
     *
     * @return Immobile
     */
    public function withBoxAuto($boxAuto)
    {
        $new = clone $this;
        $new->boxAuto = $boxAuto;

        return $new;
    }

    /**
     * @return string
     */
    public function getPostoAuto()
    {
        return $this->postoAuto;
    }

    /**
     * @param string $postoAuto
     *
     * @return Immobile
     */
    public function withPostoAuto($postoAuto)
    {
        $new = clone $this;
        $new->postoAuto = $postoAuto;

        return $new;
    }

    /**
     * @return string
     */
    public function getImpiantoTv()
    {
        return $this->impiantoTv;
    }

    /**
     * @param string $impiantoTv
     *
     * @return Immobile
     */
    public function withImpiantoTv($impiantoTv)
    {
        $new = clone $this;
        $new->impiantoTv = $impiantoTv;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileTuristico
     */
    public function getImmobileTuristico()
    {
        return $this->immobileTuristico;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileTuristico $immobileTuristico
     *
     * @return Immobile
     */
    public function withImmobileTuristico($immobileTuristico)
    {
        $new = clone $this;
        $new->immobileTuristico = $immobileTuristico;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfDistanzaPoi
     */
    public function getDistanze()
    {
        return $this->distanze;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfDistanzaPoi $distanze
     *
     * @return Immobile
     */
    public function withDistanze($distanze)
    {
        $new = clone $this;
        $new->distanze = $distanze;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\CodiceIdentificativo
     */
    public function getCodiceIdentificativo()
    {
        return $this->codiceIdentificativo;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\CodiceIdentificativo $codiceIdentificativo
     *
     * @return Immobile
     */
    public function withCodiceIdentificativo($codiceIdentificativo)
    {
        $new = clone $this;
        $new->codiceIdentificativo = $codiceIdentificativo;

        return $new;
    }

    /**
     * @return \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfInformazioniExtra
     */
    public function getInformazioniExtra()
    {
        return $this->informazioniExtra;
    }

    /**
     * @param \App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ArrayOfInformazioniExtra $informazioniExtra
     *
     * @return Immobile
     */
    public function withInformazioniExtra($informazioniExtra)
    {
        $new = clone $this;
        $new->informazioniExtra = $informazioniExtra;

        return $new;
    }

    /**
     * @return string
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param string $id
     *
     * @return Immobile
     */
    public function withId($id)
    {
        $new = clone $this;
        $new->id = $id;

        return $new;
    }

    /**
     * @return int
     */
    public function getIdAgenzia()
    {
        return $this->idAgenzia;
    }

    /**
     * @param int $idAgenzia
     *
     * @return Immobile
     */
    public function withIdAgenzia($idAgenzia)
    {
        $new = clone $this;
        $new->idAgenzia = $idAgenzia;

        return $new;
    }
}
veewee commented 8 months ago

Thanks for reporting! It's nice to get some real-world feedback from you, pointing out some of the pitfalls of the new wsdl-based type system.

This seems to be a bug in the new WSDL reader package: https://github.com/php-soap/wsdl-reader/issues/19

I'll try to get to it soon.

veewee commented 8 months ago

@h4kuna

Can you retry with the latest version of wsdl-reader? https://github.com/php-soap/wsdl-reader/releases/tag/0.8.0

It should now generate code following the structure below. The - dash separation is still not supported by the encoder, so that will still remain an issue 'til we figure out a way to do encoding from our end (instead of inside PHP's SoapClient)

immobile
========

> http://www.immobinet.it/schema/WebserviceImmobileBundle:immobile {
    categoria $categoria
    citta $citta
    ?string $indirizzo
    ?string $numero
    ?string $zona
    ?geo-coordinates $coords
    ?ArrayOfDescrizione $descrizione
    ?ArrayOfAllegato $allegati
    ?immobile-abitabile $composizione
    ?immobile-appartamento $appartamento
    ?immobile-superficie $superficie
    ?immobile-terreno $terreno
    ?immobile-casa $casa
    ?immobile-villa $villa
    ?immobile-capannone $capannone
    ?immobile-ace $ace
    ?ArrayOfAttributo $attributi
    ?tipo-cucina $cucina
    ?string $giardino
    ?string $parcheggio
    ?string $riscaldamento
    ?string $box-auto
    ?string $posto-auto
    ?string $impianto-tv
    ?immobile-turistico $immobile-turistico
    ?ArrayOfDistanzaPoi $distanze
    ?codice-identificativo $codice-identificativo
    ?ArrayOfInformazioniExtra $informazioni-extra
    @?string $id
    @?int $id-agenzia
  }
h4kuna commented 8 months ago

The generated classes looks like better. Could you set up nullable property by default null value, because property is defined like

private ?\App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileVilla $villa;

but API return nothing and i get exception with

Typed property App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\Immobile::$villa must not be accessed before initialization.

I haven't a choice, how check property before access.

I image like

private ?\App\ChannelManager\ImmobiNet\Soap\Agenzia\Type\ImmobileVilla $villa = null;
veewee commented 8 months ago

Yes, that's possible:

        $assembler = new PropertyAssembler(
            PropertyAssemblerOptions::create()->withOptionalValue()
        );

Example: https://github.com/phpro/soap-client/blob/60a663170f2b748a471e7bfd763ae30b20973ad3/test/PhproTest/SoapClient/Unit/CodeGenerator/Assembler/PropertyAssemblerTest.php#L64-L88

Note: You probably want to combine this with the optional getter as well for type-safety:

$assembler = new GetterAssembler(GetterAssemblerOptions::create()->withOptionalValue());

Example: https://github.com/phpro/soap-client/blob/60a663170f2b748a471e7bfd763ae30b20973ad3/test/PhproTest/SoapClient/Unit/CodeGenerator/Assembler/GetterAssemblerTest.php#L75-L94

h4kuna commented 8 months ago

Thank you, very much, it works.

I think, this behavior could be enabled by default.

veewee commented 8 months ago

No prob :)

I think, this behavior could be enabled by default.

I've considered it when implementing and don't think this is a good idea for several reasons:

This library follows the provided schema in the WSDL, so if it says it's a string we shouldn't annotate it as string|null cause that's confusing and not correct in every generated code scenario.

For example: it could make sense for wither-based requests but not for constructor based requests. Also for models that are being used as response DTOs it would be incorrect.

Instead, I've given to option for you to generate the code in the way you want. Just not by default.

RiseAndCry commented 4 months ago

@veewee Hi, a related question: when i make a request and try to parse the response (let's say BaseReturnType) - it contains all the necessary property values set (dump shows them as expected). But when trying to access any of them via getters i get ... must not be accessed before initialization. How can i fix this ? i'm guessing it's because there's no constructor and thus it's hydrated using reflection (or the cloning breaks it?). Setting the properties as nullable or giving default values makes no sense - as you said, if the schema says it's a string then it should be a string.

veewee commented 4 months ago

The case you mention should just work. Maybe best to open up a new issue and provide it with some more details so that I can take a look at it.