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.
2
2017-10-16
040
049
0
20MIO
3001
1
MFTHVBALANC2016
2017-10-16
040
049
0
20MIO
3010
1
MFTHVBESTSL2016
2017-10-16
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.
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.
$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.
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.
Hey Marcel,
ich würde mich gerne an einem Tarifabfragealgorithmus über Bipro versuchen.
Leider finde ich online keine brauchbare Dokumentation.
Muss ich erst Mitglied bei Bipro werden?
Danke im Voraus! 🙂
Guten Morgen Bratan,
man kann bei der BiPRO, sofern man dort angemeldet ist, PDF Dokumentationen von älteren Normen bekommen. Die Registrierung als User auf der Seite https://www.bipro.net/ ist kostenlos. Der BiPRO e.V. unterscheidet hier grundsätzlich zwischen „Release 2 – für Mitglieder“ und „Release 2 – veröffentlicht“. Da Du kein offizielles Mitglied des BiPRO e.V. bist, hast Du lediglich Zugang zu „Release 2 – veröffentlicht. Rein theoretisch könntest Du auf den veröffentlichten Normen basierend etwas programmieren. Ich habe z.B. auf der veröffentlichten Norm 260 und 410 den Security Token Service programmiert: https://wp.me/peP9B-jE (ein PHP Beispiel als Download ist enthalten).
Grundsätzlich wird es Dir aber mehr bringen, wenn Du an einem realistischen Beispiel arbeiten würdest. Da Du an einem „Tarifabfragealgorithmus“ arbeiten möchtest, benötigst Du die TAA Norm (Norm 420 etc.). Du kannst die entsprechenden Normen als angemeldeter User auf der BiPRO Website herunter laden. Allerdings musst Du hier sowohl Client als auch Server selbst erstellen. Ich glaube für den Lernprozess wäre es sinnvoller, wenn Du Dich direkt an ein Versicherungsunternehmen wendest. Meine Erfahrung ist aber auch hier, dass die Versicherer sehr spärlich mit ihren Informationen umgehen, wenn Du nicht genau sagst, wer Du bist und was Du mit deren Schnittstelle vor hast. An Deiner Stelle würde ich es mal über Twitter bei der Generali Informatik (https://twitter.com/GeneraliGDIS) probieren. Die waren bisher immer sehr nett.
Lange Rede kurzer Sinn: Versuch Dein Glück! 😉
Hey Marcel,
vielen Dank für deine schnelle und detaillierte Antwort! 🙂
Es handelt sich bei mir um ein langfristiges Projekt und ja, an ein Tarifierungssystem hatte ich gedacht.
Kannst du denn eine Technologie zur Abfrage dieser Daten (ausgeschlossen PHP) empfehlen?
LG
Hi Bratan,
sofern Du mehrere Service Provider (Versicherer) an Dein Projekt anbinden möchtest, rate ich Dir entsprechend gut zu planen. Es ist definitiv mit PHP machbar. Ich persönlich ziehe die objektorientierte Programmierung mit PHP vor. Andere widerum nutzen hier XML Templates, um den entsprechenden Service umzusetzen. Aus meiner Sicht brauchst Du jede Menge Erfahrung im Umgang mit der Programmiersprache Deiner Wahl, SOAP Webservices und entsprechenden Webstandards, wie z.B. WSSE (Webservice Security).
Aus meiner Erfahrung in der Zusammenarbeit mit anderen Teams kann ich vorab auch schon sagen, dass es in jeder Programmiersprache Probleme mit der Umsetzung von Webservices gibt, die auf dem BiPRO Standard basieren. Die Ursache hierfür hat meist einen historischen Kontext. Der Webservice wurde irgendwann mal für einen Consumer augesetzt und angepasst. Dem entsprechend verkorkst sind die ein oder anderen Webservices auch. Was für den einen Consumer allerfeinst funktioniert, entspricht oft nicht gültigen Webstandards und stellt für andere ein riesiges Problem dar. Meist ist hier immer der Security Token Service die große Einstiegshürde, die die Motivation am meisten nach unten drückt. Ist der STS erstmal umgesetzt, geht der Rest eigentlich ziemlich problemlos. Nach und nach lernt man, dass die Problemchen sich wiederholen. Man hat dann seine Lösungen zur Hand und kommt schneller voran. Erfahrung ist hier einfach Gold.
Nimm einfach die Programmiersprache, mit der Du Dich am wohlsten fühlst. Plane Dein Projekt und programmiere nicht einfach drauf los. Plane Zeit ein, um verschiedene Lösungen zu erarbeiten. Plane noch mehr Zeit für das Testen ein. Dann wird das schon.