PHP und BiPRO: Sich wiederholende Elemente im SOAP Request

Letzte Woche hat Simon zum sechsten Teil der PHP und BiPRO Reihe ein etwas kniffiligeres Problem aufgedeckt. Dieses Problem betraf sich wiederholende Elemente im SOAP XML Request und wie man diese über die objektorientierte Struktur des Downloads, den ich im genannten Beitrag zur BiPRO Norm 410 bereit gestellt habe, realisieren kann.

Im Netz gibt es hierzu einige Ansätze über die ArrayObject Klasse zum Ziel zu kommen. Simon hat hierzu einen ähnlichen Ansatz in den Kommentaren unter dem sechsten Teil zur BiPRO Reihe genannt. Da ich als alter Entwickler ein bisschen faul bin, schien mir dieser Lösungsansatz aber ein wenig zu komplex. Auch mein erster Lösungsansatz über die __doRequest Methode des Soap Clients den XML String nochmal nachträglich zu bearbeiten, war irgendwie Murks.

Simon hat mich mit dem ArrayObject Ansatz aber auf eine einfache Idee gebracht. Als Beispiel zur Veranschaulichung nehmen wir einfach mal das Complex Type Verkaufsprodukt Objekt, welches eine Produkt Eigenschaft besitzt. Diese Eigenschaft kann im SOAP Request mehrfach genannt werden. Das XML Schema sieht dann ungefähr so aus.

Complex Type Verkaufsprodukt als XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<ns1:verkaufsprodukt>
  <ns1:zahlungsweise>2</ns1:zahlungsweise>
    <ns1:produkt xsi:type="ns1:CT_ProduktTHV">
      <ns1:beginn>2017-10-16</ns1:beginn>
      <ns1:sparte>040</ns1:sparte>
      <ns1:elementarprodukt xsi:type="ns1:CT_DeckungTHV">
        <ns1:artid>049</ns1:artid>
        <ns1:selbstbeteiligung>
          <ns1:wert>0</ns1:wert>
        </ns1:selbstbeteiligung>
        <ns1:versicherungssumme>20MIO</ns1:versicherungssumme>
      </ns1:elementarprodukt>
      <ns1:wagnisart>3001</ns1:wagnisart>
      <ns1:wagnismenge>1</ns1:wagnismenge>
      <ns1:modellform>MFTHVBALANC2016</ns1:modellform>
    </ns1:produkt>
    <ns1:produkt xsi:type="ns1:CT_ProduktTHV">
      <ns1:beginn>2017-10-16</ns1:beginn>
      <ns1:sparte>040</ns1:sparte>
      <ns1:elementarprodukt xsi:type="ns1:CT_DeckungTHV">
        <ns1:artid>049</ns1:artid>
        <ns1:selbstbeteiligung>
          <ns1:wert>0</ns1:wert>
        </ns1:selbstbeteiligung>
        <ns1:versicherungssumme>20MIO</ns1:versicherungssumme>
      </ns1:elementarprodukt>
      <ns1:wagnisart>3010</ns1:wagnisart>
      <ns1:wagnismenge>1</ns1:wagnismenge>
      <ns1:modellform>MFTHVBESTSL2016</ns1:modellform>
    </ns1:produkt>
  <ns2:erstelldatum>2017-10-16</ns2:erstelldatum>
</ns1:verkaufsprodukt>

Das gezeigte XML ist ein konkreter Testfall der Janitos Versicherung für die getQuote Methode des Janitos TAA Webservices nach BiPRO Norm 420 für die Tierhaftpflichtversicherung. In diesem beispielhaften XML String sind zwei Produkt-Elemente als Kind-Elemente des Verkaufsprodukt Elements enthalten. Wie bekommt man sowas also sauber mit einer objektorientierten Struktur hin?

Sich wiederholende XML Elemente mit PHP

Wer meine objektorientierte Umsetzung der BiPRO Norm 410 noch nicht kennt, sollte sich diese vielleicht einfach mal herunter laden. Im oben genannten Artikel befindet sich am Ende ein kostenloser Download. Solltet ihr den Code irgendwo nutzen, wäre ein Credit ganz nett. Auf Basis der bestehenden Umsetzung wurde die Klasse Verkaufsprodukt entsprechend abgeändert. Die Klasse sieht nun wie folgt aus.

./bipro/janitos/entity/tarifierung/Verkaufsprodukt.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
declare(strict_types=1);
namespace MMNewmedia\BiPRO\Janitos\Entity\Tarifierung;
 
class Verkaufsprodukt extends Produktbaustein 
{    
    /**
     * Verkaufsprodukt
     * @var \SoapVar
     * @internal CT_Verkaufsprodukt
     */
    protected $Verkaufsprodukt;
 
    /**
     * Produkt
     * @var \ArrayObject
     * @internal CT_Produkt
     */ 
    protected $Produkt
 
    /**
     * Liefert das Verkaufsprodukt
     * @return \SoapVar
     */
    public function getVerkaufsprodukt() : \SoapVar
    {
        if ($this->Verkaufsprodukt === null) {
            $this->Verkaufsprodukt = (new Verkaufsprodukt())->encode();
        }
 
        return $this->Verkaufsprodukt;
    }
 
    /**
     * Setzt das Verkaufsprodukt
     * @param \SoapVar $oVerkaufsprodukt
     * @return \MMNewmedia\BiPRO\Entity\Tarifierung\Verkaufsprodukt
     */
    public function setVerkaufsprodukt(\SoapVar $oVerkaufsprodukt) : Verkaufsprodukt 
    {
        $this->Verkaufsprodukt = $oVerkaufsprodukt;
        return $this;
    }
 
    /**
     * Liefert das Produkt
     * @return \ArrayObject
     */
    public function getProdukt() : \ArrayObject
    {
        if ($this->Produkt === null) {
            $this->Produkt = new \ArrayObject();
        }
 
        return $this->Produkt;
    }
 
    /**
     * Setzt das Produkt
     * @param \SoapVar $oProdukt
     * @return \MMNewmedia\BiPRO\Entity\Tarifierung\Verkaufsprodukt
     */
    public function setProdukt(\SoapVar $oProdukt) : Verkaufsprodukt
    {
        if ($this->Produkt === null) {
            $this->Produkt = new \ArrayObject();
        }
 
        $this->Produkt->append($oProdukt);
        return $this;
    }
 
    /**
     * @see \MMNewmedia\BiPRO\Entity\BiPROEntity::encode()
     */
    public function encode() : \SoapVar
    {
        $oContainer = new \ArrayObject();
        foreach (get_object_vars($this) as $key => $value) {
            if ($value !== null) {
                if ($value instanceof \ArrayObject) {
                    foreach ($value as $oProdukt) {
                        $oContainer->append($oProdukt);
                    }
                } else {
                    $oContainer->append($value);
                }
            }
        }
 
        $oEncodedObject = new \SoapVar(
            $oContainer,
            SOAP_ENC_OBJECT,
            null,
            null,
            'Verkaufsprodukt',
            'http://www.bipro.net/namespace/tarifierung',
        );
 
        return $oEncodedObject;
    }
}

Was wurde nun geändert? Die Produkteigenschaft ist nun kein SoapVar Objekt mehr, sondern ein ArrayObject. Einerseits habe ich diesen Weg gewählt, um im objektorientierten Kontext zu bleiben. Andererseits kann man hier auch ein einfaches Array oder sonstige Collections als Container verwenden, der die einzelnen Produkt Objekte entgegen nimmt. In diesem Fall wird ein übergebenes Produkt also einfach dem ArrayObject Objekt angehängt. Die encode() Methode wurde ebenfalls abgeändert. Es wird nicht mehr die Klasse an sich als SoapVar erzeugt, sondern die Eigenschaften der Klasse werden einem temporären ArrayObject hinzugefügt, welches dann als SoapVar Objekt umgewandelt wird. Klingt komisch, ergibt aber durchaus Sinn. Somit können mehrere Produkte als Kindknoten realisiert werden.
 

Der Aufruf

Das Bestücken der Klasse mit Daten ist natürlich so einfach wie bisher geblieben. Alle Methoden können weiterhin mittels Chaining direkt nacheinander aufgerufen werden. Wegen dem ArrayObject Objekt kann die setProdukt Methode so oft wie möglich aufgerufen werden. Für die Produkt Eigenschaft des Verkaufsprodukt Objektes werden die Produkte dann gesammelt.

Erzeugen des Verkaufsprodukts mit mehreren Produkten
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$oProdukt1 = (new ProduktTHV())
    ->setBeginn(new \SoapVar(...))
    ->setSparte(new \SoapVar(...))
    ->setWagnisart(new \SoapVar(...))
    ->setWagnismenge(new \SoapVar(...))
    ->setModellform(new \SoapVar(...))
    ->setElementarprodukt($oElementarprodukt1->encode());
 
$oProdukt2 = (new ProduktTHV())
    ->setBeginn(new \SoapVar(...))
    ->setSparte(new \SoapVar(...))
    ->setWagnisart(new \SoapVar(...))
    ->setWagnismenge(new \SoapVar(...))
    ->setModellform(new \SoapVar(...))
    ->setElementarprodukt($oElementarprodukt2->encode());
 
$oVerkaufsprodukt = (new Verkaufsprodukt())
    ->setProdukt($oProdukt1->encode())
    ->setProdukt($oProdukt2->encode())
    ->setBeginn(...)
    ->setZahlungsweise(...);

Sobald ich nun die getQuote Methode des Webservices aufrufe, erzeugt der SoapClient das oben dargestellte XML. War doch einfach, oder? 😉
 

Fazit

Simon hat hier wirklich einen kleinen Stein ins Rollen gebracht mit seinem Kommentar. Glücklicherweise war die Lösung des Problems dann doch relativ einfach umzusetzen und die objektorientierte Struktur des MMNewmedia BiPRO Frameworks musste nicht unterbrochen werden. Das Erzeugen des XML Strings für den durchzuführenden Request bleibt einzig und allein dem PHP Soap Client überlassen und wir müssen uns nicht mit XML Templates rumschlagen. PHP geht hier mit seiner SoapClient Klasse mal wieder einen ganz besonderen Weg, der nirgends dokumentiert ist. Allerdings haben der Denkanstoß von Simon und die Hinweise auf das ArrayObject bei StackOverflow dann doch sehr geholfen. Danke Simon. 😉

Haben wir wieder was gelernt.

About Author: Marcel

Ich bin Senior PHP Developer bei MM Newmedia. Seit 2005 bin ich begeisterter Webentwickler und arbeite als Freelancer für namenhafte Firmen und entwickle jede Menge abgefahrenes Zeug und berichte darüber in meinem Blog.

2 thoughts on “PHP und BiPRO: Sich wiederholende Elemente im SOAP Request

  • Vielen Dank! Das ist echt eine sehr elegante Lösung. Doch um einiges einfacher als was ich da versucht habe 😀

  • Ich habe leider mit der aktuellen Lösung immer noch Probleme. Ich habe versucht die Partner Klasse auf die gleiche Weise anzupassen, weil dort theoretisch auch mehrere Kommunikationsverbindungen auftreten könnten.

    Leider ist mit der neuen Lösung das Partner Tag dann komplett leer.

    Ich habe herausgefunden, dass es mit dem type=“CT_Person“ zusammen hängt.

    public function encode() {
    $container = new \ArrayObject();
    foreach (get_object_vars($this) as $key => $value) {
    if ($value !== null) {
    if ($value instanceof \ArrayObject) {
    foreach ($value as $item) {
    $container->append($item);
    }
    } else {
    $container->append($value);
    }
    }
    }
    $encodedObject = new \SoapVar(
    $container,
    SOAP_ENC_OBJECT,
    null, //’CT_Person‘,
    null, //’http://www.bipro.net/namespace/partner‘,
    ‚Partner‘,
    ‚http://www.bipro.net/namespace/tarifierung‘
    );

    return $encodedObject;
    }

    erstellt das richtige Objekt aber ohne type=“CT_Person“

    public function encode() {
    $encodedObject = new \SoapVar(
    $container,
    SOAP_ENC_OBJECT,
    ‚CT_Person‘,
    ‚http://www.bipro.net/namespace/partner‘,
    ‚Partner‘,
    ‚http://www.bipro.net/namespace/tarifierung‘
    );

    return $encodedObject;
    }

    funktioniert einwandfrei solange keinen Tags wiederholt werden müssen.

    Hast du eventuell eine Idee woran es liegen kann, dass Soap in der XML Struktur den Tag komplett leer lässt? Und warum das mit dem type zutuen haben könnte? Das gleiche Problem scheint auch bei anderen Tags auzutreten wo ein type Attribut benötigt wird.

Kommentar verfassen