PHP und BiPRO Teil 6: Norm410 Response und die Classmap

Seit etwas längerer Zeit beschäftige ich mich mal wieder intensiv mit dem Thema BiPRO, weil ich für die vs vergleichen-und-sparen GmbH ein BiPRO Modul für das Zend Framework 2 programmiere. Wer das Zend Framework 2 kennt, weiß, dass dort wirklich alles ein Objekt ist. Das ist auch gut so. Im Zusammenhang mit BiPRO stehe ich da allerdings oft vor Problemen. Die Anbieter selbst geben in ihren Beispielen String- oder Template basierte Lösungen an. Kann man mit dem Zend Framework 2 natürlich auch machen, geht mir, als jemand der objektorientierte Programmierung ziemlich geil findet, aber ziemlich gegen den Strich. Zumal der Client und Server die XML Strukturen vollkommen allein zusammen setzen können. Man muss eben nur wissen wie.

Heute erweitern wir das Security Token Service Beispiel (Norm 410), welches in diesem Blog als Download verfügbar ist. Ich zeige Euch, wie man die ein wenig ekelige Response direkt mit einem Objekt auffangen kann. Dieses Objekt wird über entsprechende Getter Methoden verfügen, mit denen ihr die Inhalte der Response sehr einfach bekommen könnt. Wieso ist das cool? Weil die SoapClient und SoapServer Objekte diesen Job eigentlich schon von Haus aus übernehmen. Also bleiben wir in einem objektorientierten Kontext und nutzen diese Eigenschaft einfach.

Wie sieht die Response des STS in Rohform aus

Folgend ist die Response aus meinem Download Beispiel so dargestellt, wie sie einfach vom Client interpretiert wird.

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
object(stdClass)#14 (1) {
  ["any"]=>
  array(4) {
    [0]=>
    string(75) "http://schemas.xmlsoap.org/ws/2005/02/sc/sct"
    ["Lifetime"]=>
    &object(stdClass)#15 (2) {
      ["Created"]=>
      object(stdClass)#16 (1) {
        ["_"]=>
        string(19) "2016-10-21 11:35:18"
      }
      ["Expires"]=>
      object(stdClass)#17 (1) {
        ["_"]=>
        string(19) "2016-10-21 12:35:18"
      }
    }
    ["RequestedSecurityToken"]=>
    object(stdClass)#18 (1) {
      ["any"]=>
      string(124) "bipro:3fb8a718b859b940af53208ecd1e4bc7"
    }
    [1]=>
    string(46) "2.1.0.1.0"
  }
}

Die im Security Token Request verwendeten BiPRO und Web Defintionen (XSD) geben diese Struktur des zurück gelieferten Objektes vor. Wir sehen hier also ein strikt nach Definitionen übersetztes Objekt mit den üblichen Eigenheiten von PHP. Soap Responses mit PHP geben, sofern in den Webservice Definitionen nicht anders notiert, Werte mit einem Unterstrich zurück.

Zum Verständnis auch noch mal die XML Struktur, aus der dieses Objekt resultiert.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
< ?xml version="1.0" encoding="UTF-8"?>
<env:envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:ns3="http://schemas.xmlsoap.org/ws/2005/02/trust" xmlns:ns4="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:ns5="http://www.bipro.net/namespace/allgemein">
  <env:body>
    <ns3:requestsecuritytokenresponse>
      <ns1:tokentype>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</ns1:tokentype>
      <ns3:lifetime>
        <ns2:created>2016-10-21 11:35:18</ns2:created>
        <ns2:expires>2016-10-21 12:35:18</ns2:expires>
      </ns3:lifetime>
      <ns3:requestedsecuritytoken>
        <ns4:securitycontexttoken>
          <ns4:identifier>bipro:3fb8a718b859b940af53208ecd1e4bc7</ns4:identifier>
        </ns4:securitycontexttoken>
      </ns3:requestedsecuritytoken>
      <ns5:biproversion>2.1.0.1.0</ns5:biproversion>
    </ns3:requestsecuritytokenresponse>
  </env:body>
</env:envelope>

Dieses XML Schema wird von dem im Beispiel enthaltenen Soap Server zurück geliefert und entsprecht der BiPRO Norm 410. Ja, ich weiß, dass der Identifier in der echten Welt mindestens ein 1024 Bit String ist. Als Beispiel soll dieser verkürzte Hash einfach mal reichen.

Wie geht das denn vernünftig?

Natürlich bietet PHP eine Lösung, um mit dieser Response vernünftig umgehen zu können. Vernünftig heißt in diesem Sinne einfach, dass mit einer Getter Methode einfach das SecurityContextToken Element zur weiteren Verwendung zurück gegeben wird. Oder einfach eine Getter Methode, um den Identifier als String zurück zu liefern. Oder eine Getter Methode, die die Lifetime zurück gibt.

Die Lösung lautet einfach Classmap. Eigentlich eine Classmap, wie man sie eventuell auch schon aus dem Zend Framework 2 Autoloading kennt. Ein einfaches Array mit Zuweisungen von Namensräumen zu entsprechenden Klassen. Einsolches Array können wir auch dem Soap Client mitgeben. Das sieht dann im Download Beispiel wie folgt aus.

1
2
3
4
5
6
7
8
9
10
11
12
13
$oClient = new \SoapClient(
    $sWsdl,
    [
        'trace'        => true,
        'exception'    => true,
        'cache_wsdl'   => WSDL_CACHE_NONE,
        'compression'  => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
        'soap_version' => SOAP_1_2,
        'classmap'     => [
            'RequestSecurityTokenResponseType' => 'MMNewmedia\BiPRO\Norm410\Model\RequestSecurityTokenResponseType',
        ],
    ]
);

Was habe ich jetzt hier gemacht? Das PHP SoapClient Objekt bietet die Option classmap, welche das eben schon erwähnte Array mit Zuweisungen von Typ Bezeichnungen zu Klassen beinhaltet. Diese Klasse wird dann vom Client voll automatisch aufgerufen, sobald eine Response mit dem angegebenen Namen verfügbar ist. Jetzt werdet ihr Euch wahrscheinlich fragen, woher der Nerd eigentlich weiß, wie der zurück gelieferte Typ eigentlich heißt? Nun, lasse ich mir vom Client die Funktionen des Werbservices ausgeben, erhalte ich folgende Ausgabe.

1
2
3
4
array(1) {
  [0]=>
  string(91) "RequestSecurityTokenResponseType RequestSecurityToken(RequestSecurityTokenType $parameters)"
}

Hieraus geht hervor, dass der zurückgegebene Typ den Namen RequestSecurityTokenResponseType trägt. Dies geht auch aus der WSDL Datei und den damit verbundenen XSD Dateien hervor. Der Soap Client bedient sich anhand dieser Informationen. Ich kann mir nun den entsprechen Typen aus der Lise der Typen heraussuchen, die mir der Soap Client zurück gibt.

1
2
3
4
string(75) "struct RequestSecurityTokenResponseType {
  any;
  anyURI Context;
}"

Was sagt uns diese Definition? Unfassbar wenig! Eigentlich sagt es uns nur, dass wir hier eine Eigenschaft mit dem Namen any haben. Darunter kann dann alles mögliche stehen. Um das alles in einem Objekt verwertet zu bekommen, benötigen wir das eingangs erwähnte, ekelige Response Objekt, um einfach in Erfahrung zu bringen, wie die Datenstruktur aussieht. Daraus können wir dann unser Objekt aufbauen.

Das Datenobjekt für die Response

Fassen wir mal zusammen, was wir jetzt wissen. Wir haben den Namen des Typs, der zurück geliefert wird. Wir wissen, dass wir diesem Typen mittels Classmap eine Klasse zuweisen können, die automatisch vom Client aufgerufen wird, wenn die Response eintrifft. Wir wissen, wie die Datenstruktur aussieht, die vom Client empfangen wird. Also setzen wir uns einfach mal unser Objekt zusammen. Im folgenden Beispiel lasse ich mir einfach das SecurityContextToken Element als SoapVar Objekt zurückgeben. Dieses kann dann in weiteres Requestes verwendet werden.

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
namespace MMNewmedia\BiPRO\Norm410\Model;
 
use MMNewmedia\BiPRO\Entity\SecurityContextToken;
 
/**
 * RequestSecurityTokenResponse Entity nach BiPRO Norm (utf-8)
 * Wertet die RequestSecurtiyToken Response eines BiPRO Security Token Webservices aus
 *
 * @author Marcel Maaß 
 * @copyright 2016 MM Newmedia 
 * @package de.mmnewmedia.bipro
 * @subpackage entity
 * @since 2016-10-20
 * @version
 */
class RequestSecurityTokenResponseType {
 
    /**
     * Liefert den SecurityContextToken Knoten als SoapVar für die weitere Verwendung
     * @return \SoapVar
     */
    public function getSecurityContextToken() {
        $oIdentifier = new \SoapVar(
            $this->any['RequestedSecurityToken']->any,
            XSD_STRING,
            null,
            null,
            'Identifier',
            'http://schemas.xmlsoap.org/ws/2005/02/sc'
        );
 
        $oSecurityContextToken = new SecurityContextToken();
        $oSecurityContextToken->setIdentifier($oIdentifier);
 
        return $oSecurityContextToken->encode();
    }
}
 
// Beispiel
$oResult = $oClient->RequestSecurityToken($oMeineParameter);
$oSecurityContextToken = $oResult->getSecurityContextToken();

Wie man hier sehr schön sehen kann, kann ich in diesem Objekt direkt auf $this->any zugreifen. Ich verfüge also direkt über die Datenstruktur der Response (dem ekeligen Objekt), die eingangs schon erwähnt wurde, und kann die erhaltenen Daten dann entsprechend aufbereiten. So kann ich also ganz bequem alles so aufbereiten, wie ich es brauche. Im Download sind weitere Methoden enthalten, die unter anderem das Lifetime Objekt und den Identifier zurück geben.

Fazit

Zugegeben sieht das am Anfang alles ganz schön unstrukturiert und vielleicht auch überdimensioniert aus. Letztendlich kann ich mir aber so ganz komfortabel die Teile aus der Response so zurückgeben lassen, wie ich sie im weiteren Verlauf benötige. Die oben dargestellte Funktion benötige ich z.B., um den ermittelten Security Token für die Requests der Tarifierung nach BiPRO Norm 421 weiter zu verwenden.
Schade ist nur, dass diese Vorgehensweise in der PHP Dokumentation zwar erwähnt, aber nicht erklärt wird. An einigen wenigen Stellen im Netz wird zwar an einfachen Beispielen erklärt, wie Classmaps im SoapClient funktionieren. Aber BiPRO Prozesse sind um einiges komplexer, als die im Netz erwähnten Beispiele. Also wird der Lernerfolg hier einfach erzielt, indem man es einfach macht.
Nachteil ist mal wieder, dass selbst die BiPRO Norm 410 ziemlich unterschiedlich von den verschiedenen Anbietern umgesetzt wird. Das gezeigte Beispiel funktioniert mit dem Standard Security Token Service der BiPRO, so wie sie sich ihn im Idealfall vorstellt. In der Praxis muss man oftmals für jeden Service ein eigenes Response Model entwickeln, um die Eigenheiten der Versicherer zu berücksichtigen. Beim Security Token Service nicht so oft. Bei den Tarifierungs Services sieht es da schon ganz anders aus.

Mit dem Response Objekt wäre die Norm 410 jetzt also komplett mit PHP umgesetzt. War doch gar nicht so schwer, oder?

Download

Wie auch schon beim letzten Download, müsst ihr beachten, dass in der mitgelieferten WSDL Datei der Endpunkt an Eure Umgebung angepasst werden muss. Bitte beachtet auch, dass das gelieferte Beispiel auch wirklich nur ein Beispiel ist. Ich übernehme keinerlei Haftung, falls ihr das Beispiel trotz dieser kleinen Warnung produktiv einsetzt.

Der Aufruf erfolgt dann über http://www.deinedomain.tld/bipro/norm410/client.php.

Download “BiPRO Norm 410 Version 1.1” mmnewmedia-bipro-norm410_1.1.zip – 150-mal heruntergeladen – 74 KB

Noch ein kleines persönliches Anliegen. Wenn ihr das Beispiel nutzt hinterlasst ruhig mal eine Kritik oder ein Like. Sagt ruhig mal, was ihr denkt. Ich persönlich bin immer am Austausch mit anderen interessiert.

In diesem Sinne. Haben wir wieder was gelernt. 😉

Tags: , , , , , ,
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.

9 thoughts on “PHP und BiPRO Teil 6: Norm410 Response und die Classmap

  • Vielen Dank für dieses ausführliche Beispiel. Es hat mir um einiges weiter geholfen um mit BiPro zu arbeiten. Leider bin ich auf ein Problem gestoßen, dass ich nicht so einfach bewältigen kann.

    In meiner Anwendung ist es notwendig im Request einige Tags mehrmals zu verwenden. Zum Beispiel das Partner Tag. Leider ist es für mich nicht wirklich klar wie ich das in der Objektorientierten Architektur machen kann.

    Ich habe versucht die Partner Property auf ein ArrayObject umzuändern und dort dann die einzelnen Partner hinzuzufügen. Das erstellt zwar mehrere Partner Tags aber umschließt diese mit einem weiteren Partner Tag also:
    ……..

    Ich habe zwar im Internet nach Lösungen gesucht, aber alles was ich finde benutzt keine Objekt Struktur sondern nur reine ArrayObjects.

    Gibt es eine Möglichkeit die Objekt Struktur zu behalten aber trotzdem Tags mehrmals zu haben?

    • Hi Simon,

      gern. Ich als PHP Entwickler bekomme ja seit Jahren mit, wie schwer BiPRO Umsetzungen mit PHP sein können, weil die Service Provider immer wieder von gültigen Web Standards abweichen.

      Welche BiPRO Norm setzt Du denn gerade um. Soweit mir bekannt ist, gibt es für die Norm 410 gar kein Partner Element.

      Da ich Deinen Code nicht genau kenne, gehe ich jetzt einfach mal davon aus, dass Du ein Entity hast, welches eine Partner Eigenschaft besitzt. Diese Partner Eigenschaft kann n-mal auftreten. Was passiert, wenn Du der Eigenschaft Partner ein SoapVar Objekt übergibst, welches das Array mit den Partner Objekten beinhaltet?

      Darüber hinaus kommt es drauf an, wie das Partner Element in der dazugehörigen XSD Datei definiert ist. Ist es dort als Sequenz, die sich wiederholen kann definiert, sollte es mit dem Array funktionieren. Du erkennst die Wiederholungen an dem maxOccurs Attribut. Wenn dieses nicht notiert ist, greift hier der Standard Wert 1, so dass das Partner Element nur einmal auftreten kann.

      Ich hoffe ich konnte Dir weiter helfen. Ansonsten schaue ich auch mal nach, ob ich ein konkretes Beispiel liefern kann.

      • Entschuldigung falls diese Frage zu diesem Beitrag etwas unpassend ist. Ich versuche Norm 426 umzusetzen und benutze die Objekte aus deinem Beispiel zu Norm 410 als Vorlage um meine Objekt Struktur aufzusetzen.

        In der XSD Datei steht bei Partner maxOccurs=“unbounded“. Die Datei sollte also richtig seien. Leider funktionieren bei mir die verschiedenen Kombinationen alle nicht richtig. Ich habe sowohl array() als auch new ArrayObject() verwendet. Ich habe versucht SOAP_ENC_OBJECT sowie SOAP_ENC_ARRAY zu verwenden. Ich habe versucht die inneren Elemente als SoapVar zu encoden oder Sie nicht zu encoden. Ich habe versucht beim äußeren Element den type hinzuzunehmen oder wegzulassen. Alles erzeugt nicht das gewünschte Ergebniss:

        $partner = array();
        $partner[] = $partner1->encode();
        $partner[] = $partner2->encode();
        $tarifierung->setPartner(new \SoapVar($partner, SOAP_ENC_OBJECT,null, null, ‚Partner‘, ‚http://www.bipro.net/namespace/tarifierung‘));

        Erzeugt:

        ….
        ….

        Also geschachtelte Tags die ich nicht haben will.

        Es wäre eine sehr große Hilfe für mich, wenn du den Fehler in meinem Code sehen würdest.

      • In meinem vorherigem Kommentar kann man leider mein Ergebniss nicht richtig sehen:
        Es ist ein übergeordnetes Partner Tag (durch das Property) und dieses hat zwei Kinder Elemente die wiederum Partner Tags sind.

      • Ich habe am Wochenende mal ein wenig getestet und konnte das Problem zumindest reproduzieren. Eine einfache Lösung habe ich aber noch nicht gefunden. Eine eher etwas unsaubere Lösung könnte sein, dass man das vom Soap Client erstellte XML in der Methode \SoapClient::__doRequest() direkt vor dem Absenden noch mal parst und korrigiert. Mit diesem Lösungsansatz konnte ich das Problem zumindest erstmal lösen. Allerdings kommt mir das auch schon wieder ein wenig spanisch vor. Ich bleibe mal dran und sobald ich Zeit und eine entsprechend saubere Lösung gefunden habe, schreibe ich etwas dazu. 😉

      • Vielen Dank!

        Als ich im Internet zu Soap nachgeforscht habe bin ich auf die Lösung gestoßen:

        $tarifierung = new ArrayObject();
        $tarifierung->append($partner1->encode());
        $tarifierung->append($partner2->encode());
        $tarifierung->append($verkaufsprodukt->encode());

        Mit dieser Lösung wäre es möglich die richtige XML Struktur zu erhalten, aber mit dieser Lösung müsste ich an einigen Stellen auf die Objektstruktur verzichten, was das ganze doch um einiges unübersichtlicher machen würde.

        Ich bin also auch noch auf der Suche nach einer Lösung die mit der Objektstruktur zusammen funktioniert.

      • Ich habe einen Lösungsansatz gefunden der für mich viel versprechend aussieht:

        Da die funktionalität mit ArrayObjects funktioniert gehe ich davon aus, dass SoapVar intern mit Iteratorn funktioniert. Ich habe also probiert einen Iterator in meine Klasse einzubauen der meine Elemente richtig zurückliefert.

        Ich habe es mit der Verkaufsprodukt Klasse ausprobiert, da diese das gleiche Problem mit Produkten hat aber um einiges weniger umfangreich ist als meine Tarifierungs Klasse

        Folgenden Code habe ich dafür verwendet:

        class Verkaufsprodukt implements BiPROEntity, Countable, Iterator {

        private $cursor;
        private $item;
        private $produkt_cursor;
        private $keys;
        public function __construct(){
        $this->keys = array(‚Erweiterung‘, ‚Bezeichnung‘, ‚GewuenschteZahlungsweise‘, ‚Produkt‘);
        $this->cursor = -1;
        $this->produkt_cursor = 0;
        }

        …..

        public function encode() {
        $encodedObject = new \SoapVar(
        $this,
        SOAP_ENC_ARRAY,
        null,
        null,
        ‚Verkaufsprodukt‘,
        ‚http://www.bipro.net/namespace/tarifierung‘
        );

        return $encodedObject;
        }
        public function count(){
        return 3+count($this->Produkt);
        }
        public function rewind(){
        $this->cursor = -1;
        $this->next();
        }
        public function next(){
        $this->cursor++;
        if($this->keys[$this->cursor – $this->produkt_cursor] == ‚Produkt‘){
        $this->item = $this->Produkt[$this->produkt_cursor];
        $this->produkt_cursor++;
        }else{
        $this->item = $this->{$this->keys[$this->cursor – $this->produkt_cursor]};
        }
        }
        public function current(){
        return $this->item;
        }
        public function key(){
        return $this->keys[$this->cursor – $this->produkt_cursor];
        }
        public function valid(){
        return ($this->cursor + $this->produkt_cursor count());
        }

        Mit dieser Herangehensweise bekomme ich schon die Struktur die ich mir wünsche, allerdings habe ich immer noch Probleme:
        In der XML Datei haben jetzt die Kinder von Verkaufsprodukt alle den Tag Namen xsd:anyType. Mir ist leider noch nicht genau klar welchen Wert Soap für den Tag Namen verwendet.

        Der aktuelle Code funktioniert in dieser Fassung auch nur wenn der wiederholte Tag am Schluss vorkommt.

        Wenn ich das Problem mit den Tag Namen lösen kann, werde ich versuchen die Iterator Methoden in einer extra Klasse BiProIterator zu verallgemeinern, so dass alle Klassen die wiederholte Tags haben nur noch diese Klasse erweitern müssen und dann im Konstruktor den keys array initialisieren müssen.

      • Hey Simon,

        ich habe hier eine einfache Lösung des Problems gefunden. Have fun. 😉

  • Pingback: PHP und BiPRO: Sich wiederholende Elemente im SOAP Request | Der Entwickler Blog von MM Newmedia

Kommentar verfassen