Das Zend Framework und die eBay trading API – Teil 2

Im ersten Teil dieses Guides hatten wir uns einen Überblick über den Consent Flow und eBay Authentifizierung unserer Nutzer geschaffen und so die Voraussetzung zur praktischen Umsetzung mit dem Zend Framework geschaffen.

Was werde ich hier in Teil 2 beschreiben?

Dieser Teil widmet sich dem Aufbau und Implementierung der Ebay_Api_Adapter Klasse, die wir verwenden werden um Anfragen an die eBay trading API zu senden. Wir benötigen Methoden zur Generierung und Auslieferung der Anfragen und eine geeignete Fehlerbehandlung. Exemplarisch und anhand von Code-Beispielen soll das hier in Teil 2 gezeigt werden.

Unsere Webapplikation muss nicht nur Anfragen an die eBay API senden, sondern auch den Überblick über SessionIDs und Tokens seiner Benutzer behalten. Wir sind also gezwungen diese – in unserem Fall in einer Datenbank – zwischen zu speichern:

Das bedeutet sogleich, dass unsere Applikation auch ein User-System haben muss, so dass die zurückgegebenen SessionIDs und Tokens mit den korrekten Benutzern verknüpft werden können. Ich möchte dies hier nicht noch ausführlich herleiten, sondern setze dieses Feature in der Basis-Applikation voraus. Ich nutze dafür Zend_Acl und ein klassisches User-System mit Register-, Login- und Logout Funktionen. Um voraus zu planen und es eventuell später mehren Nutzern gleichzeitig zu ermöglichen mit einem eBay Account zu arbeiten wird es in meiner Users Tabelle eine account_id und eine n:1 Verknüpfung mit der id des eBay Accounts in der Tabelle Accounts geben. Der Controller User und die dazugehörigen Funktionen existieren also schon.

Die benötigten Controller und Klassen

Für unsere eBay Applikation brauchen wir einen neuen Controller (Ebayauth) und eine neue Library (Ebay) in der wir unsere eBay spezifischen Klassen unterbringen. Wir benötigen:

  • Einen Controller Ebayauth
  • Fünf Actions (index, step1, step2, success und denied) und fünf Views für den Controller Ebayauth: index.phtml, step1.ajax.phtml, step2.ajax.phtml, success.phtml und denied.phtml
  • Eine neue Library mit Namespace Ebay_ (nicht vergessen einen Autoloader im bootstrap anzupassen)
  • Zwei Ebay_ Klassen: Ebay_Api_Adapter und Ebay_Auth_ConsentFlow
  • Eine keys.xml in der Library Ebay

Die Klasse Ebay_Api_Adapter

Die keys.xml erlaubt es uns unsere API-Keys in einer schönen, ausgelagerten XML zu verwalten anstatt sie hardcoded in der Klasse zu lagern. Werfen wir zunächst einen Blick auf den Constructor, die setOptions Methode und die keys.xml:

class Ebay_Api_Adapter
{
    public function __construct($configfile = 'keys.xml')
    {
        $configfile = '../library/Ebay/' . $configfile;
        $configXml = new Zend_Config_Xml($configfile);
        $this->setOptions($configXml->keys->toArray());
        $this->setOptions($configXml->options->toArray());
    }

    // statische Instanz-Rückgabe
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    private function setOptions($options = array())
    {
        if(is_array($options)) {
            foreach($options as $key => $value) {
                $key = '_' . $key;
                $this->$key = $value;
            }
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?><ebay>
<keys>
    <devId>hier-eintragen</devId>
    <appId>hier-eintragen</appId>
    <certId>hier-eintragen</certId>
    <ruName>hier-eintragen</ruName>
</keys>
<options>
    <siteId>77</siteId>
    <warningLevel>High</warningLevel>
    <compatLevel>647</compatLevel>
    <errorLanguage>77</errorLanguage>
    <serverUrl><![CDATA[https://api.sandbox.ebay.com/ws/api.dll]]></serverUrl>
    <signinUrl><![CDATA[https://signin.sandbox.ebay.com/ws/eBayISAPI.dll?SignIn]]></signinUrl>
</options></ebay>

Zu den Optionen in der keys.xml möchte ich noch ein paar Worte loswerden:

  • siteID 77 entspricht eBay.de und errorLanguage 77 entspricht Deutsch
  • warningLevel ist während wir mit der Sandbox arbeiten auf High gesetzt.
  • compatLevel 647 ist die aktuelle eBay trading API Version mit der wir arbeiten möchten.
  • serverURL ist unser Ansprechpartner für sämtliche API-Requests.
  • signinURL verwenden wir für die Weiterleitung des Benutzers zum Consent Flow.

Für den HTTP-Request (POST) an die eBay trading API nutzen wir Zend_Http_Client. Die API möchte von uns eine Reihe HTTP-Header gesetzt sehen, bevor sie antworten wird. Dazu folgende Funktion:

private function setHeaders()
{
    $this->_headers = array (
        // Die eBay trading API Version, die wir unterstützen
        'X-EBAY-API-COMPATIBILITY-LEVEL: ' . $this->_compatLevel,

        // Unsere Keys und Zertifikate
        'X-EBAY-API-DEV-NAME: '    . $this->_devId,
        'X-EBAY-API-APP-NAME: '    . $this->_appId,
        'X-EBAY-API-CERT-NAME: '   . $this->_certId,

        // Der Name jeder eBay API Anfrage muss im Header noch einmal benannt werden
        'X-EBAY-API-CALL-NAME: '   . $this->_callVerb,
        'X-EBAY-API-SITEID: '      . $this->_siteId,

        // Wichtig: Den Content Type der Daten, die wir senden
        'Content-Type: text/xml; charset=utf-8'
    );
    $this->_request = new Zend_Http_Client();
    $this->_request->setUri($this->_serverUrl);
    $this->_request->setHeaders($this->_headers);
}

Sind die Header gesetzt, so können wir mit dem Zend_Http_Client Object ($this->_request) eine Anfrage senden. Der eigentliche Inhalt der Anfrage wird mittels POST mitgesendet. Das eigentliche Herzstück unseres API Adapters ist die Methode callApi, die wie folgt Abläuft:

  1. Die Header setzen
    Wir übergeben der callApi Methode die Bezeichnung des Requests als Parameter und rufen damit die setHeaders() Methode auf. Diese baut die Header zusammen und erstellt ein neues Zend_Http_Client Objekt (siehe oben).
  2. Den Request Body zusammensetzen
    Abhängig vom Typ der API Anfrage müssen wir den String des Request-Bodys (xml) zusammensetzen. Eine getSessionID verlangt andere (und weniger) Optionen als eine reguläre Anfrage.
  3. Den Http_Client mit dem Request Body versehen und absenden
    Den zuvor in der setHeaders() Methode erstellten Http_Client übergeben wir den Request Body und senden den Request mittels POST an den API-Server. Die Rückgabe speichern wir in einer Variable.
  4. Die Rückgabe auswerten und Fehler abfangen
    Die Rückgabe (xml) hat je nach API Anfrage eine bestimmte Form, die wir auswerten müssen. Hier muss auch unsere Fehlerbehandlung implementiert werden.

Hier die Implementierung der callApi Methode der Klasse Ebay_Api_Adapter:

public function callApi($verb, $request, $aditionalXml = true, $requestCredentials = true, $serverUrl = null)
{

    if($serverUrl == null) {
        $serverUrl = $this->_serverUrl;
    }

    $this->_callVerb = $verb;

    $this->setHeaders();

    $requestBody =
        '<?xml version="1.0" encoding="utf-8"?>' .
        '<' . $verb . ' xmlns="urn:ebay:apis:eBLBaseComponents">';

    // request Credentials werden nur bei regulären, nicht bei
    // getSessionID oder fetchToken Anfragen benötigt
    if($requestCredentials) {
        $requestBody .=
            '<RequesterCredentials>' .
            '<eBayAuthToken><![CDATA[' . $this->getToken() . ']]></eBayAuthToken>' .
            '</RequesterCredentials>';
    }

    // Auch diese Optionen benötigen wir nur für reguläre Anfragen
    if($aditionalXml) {
        $requestBody .=
            '<ErrorLanguage>' . $this->_errorLanguage . '</ErrorLanguage>' .
            '<WarningLevel>'  . $this->_warningLevel  . '</WarningLevel>';
    }

    $requestBody .=
        $request . '</' . $verb . '>';

    $this->_request->setUri($serverUrl);
    $this->_request->setRawData($requestBody);
    $response = $this->_request->request('POST');

    $this->responseHeader   = $response->getHeaders();
    $this->responseBody     = $response->getBody();

    // die Rückgabe wandeln wir in ein (hoffentlich gültiges)
    // XML Element um
    $xml = @simplexml_load_string($this->responseBody);

    // Existiert in der Rückgabe kein ACK-Feld (zB. weil der ebay
    // Server nicht geantwortet hat) oder ist es nicht 'Success',
    // so ist etwas schief gelaufen
    if(!isset($xml->Ack) || $xml->Ack != 'Success') {
        $errors = 'Keine Antwort von eBay';
        if(isset($xml->Errors))
            $errors = $xml->Errors;
        return array('Success' => false, 'Response' => $errors);
    }

    return array('Success' => true, 'Response' => $xml);
}

Die in Zeile 21 verwendete gettoken() Methode (siehe unten) gibt mir den mit dem User Account verknüpften Token zurück (Siehe Schaubild oben), ist aber optional und setzt ein User-Model voraus. Alternativ oder zu Testzwecken lässt sich der Token auch in der keys.xml hinterlegen und der Funktionsaufruf in Zeile 21 durch ein $this->_token() ersetzen.

public function getToken()
{
    $accountId        = Zend_Auth::getInstance()->getIdentity()->account_id;
    $modelAccount    = new Model_Account();
    return $modelAccount->getEbayToken($accountId);
}

Mit dieser Implementierung können wir nun beliebige eBay trading API Anfragen versenden. Im dritten Teil dieses Guides beschreibe ich die Implementierung der Klasse Ebay_Auth_ConsentFlow.

Share
  1. Hi,

    was ist denn mit der Model_Account Klasse gemeint??

  2. Model_Account ist eine Datenstruktur für die Benutzer der Webapplikation. Über Model_Account kann ich einem Benutzer einen eBay-Token zuweisen. So reicht eine einmalige Authentifizierung mit eBay: der eBay-Token wird in der Datenstruktur hinterlegt.

    Eine genaue Implementierung findet sich hier nicht und ist abhängig von der Webapplikation.

    Grüße

  3. ok klar – danke für die antwort – hatte gar keine ahnung bisher vom framework – inzwischen (nach ein paar tagen) ist mir aber vieles sehr klar. bin dabei eine sehr individuelle listing-app zu entwickeln. beschäftige mich gerade damit, die attribute-based item specifics forms zu rendern…

    nochmal vielen dank für diesen beitrag.

Leave a Comment