2009-11-13 3 views
8

Kann jemand alle Anfrage und Antworten mit dem eingebauten SoapClient in PHP protokollieren? Ich könnte tatsächlich alles manuell mit SoapClient::__getLastRequest() und SoapClient::__getLastResponse() protokollieren Aber wir haben so viele Seifen Anfragen in unserem System, dass ich andere Möglichkeiten suche.Alle Soap-Anfrage und Antworten in PHP protokollieren

Hinweis: Ich verwende Wsdl Modus so ein Verfahren, das alle durch Tunnel zu SoapClient::__soapCall()

etwas ist keine Option

Antwort

14

Ich zweite Aleksanders und Stefans Vorschlag aber würde nicht SoapClient Unterklasse. Stattdessen würde ich den regulären SoapClient in einen Decorator einbinden, da die Protokollierung kein direktes Anliegen des SoapClients ist. Mit der losen Kopplung können Sie den SoapClient in Ihren UnitTests leicht durch einen Schein ersetzen, sodass Sie sich auf das Testen der Protokollierungsfunktionen konzentrieren können. Wenn Sie nur bestimmte Anrufe protokollieren möchten, können Sie eine Logik hinzufügen, die Anfragen und Antworten nach $ action oder nach allen Kriterien sortiert, die Sie für richtig halten.

bearbeiten seit Stefan vorgeschlagen, einige Code zu schreiben, würde der Dekorateur wahrscheinlich in etwa so aussehen, obwohl ich über die __call() -Methode nicht sicher bin (siehe Stefans Kommentare)

class SoapClientLogger 
{ 
    protected $soapClient; 

    // wrapping the SoapClient instance with the decorator 
    public function __construct(SoapClient $client) 
    { 
     $this->soapClient = $client; 
    } 

    // Overloading __doRequest with your logging code 
    function __doRequest($request, $location, $action, $version, $one_way = 0) 
    { 
     $this->log($request, $location, $action, $version); 

     $response = $this->soapClient->__doRequest($request, $location, 
                $action, $version, 
                $one_way); 

     $this->log($response, $location, $action, $version); 
     return $response; 
    } 

    public function log($request, $location, $action, $version) 
    { 
     // here you could add filterings to log only items, e.g. 
     if($action === 'foo') { 
      // code to log item 
     } 
    } 

    // route all other method calls directly to soapClient 
    public function __call($method, $args) 
    { 
     // you could also add method_exists check here 
     return call_user_func_array(array($this->soapClient, $method), $args); 
    } 
} 
+0

Die Verwendung eines Dekorators ist eine sehr gute Idee. Eigentlich würde ich selbst mit einer Decorator-Lösung gehen, wenn ich an dem gleichen Problem arbeiten müsste. Aber ich denke, die Unterklassen-Lösung ist ein bisschen verständlicher. –

+1

Vielleicht hilft es dem OP, wenn Sie ein Codebeispiel hinzufügen. –

+0

Und dann gibt es ein PHP-Problem, das das * Stacking * von Decorators nicht zulässt, wenn Sie das Methoden-Überladungs-Feature '__call()' in einem Ihrer Decorators oder in der zu dekorierenden Klasse verwenden ('SoapClient' in diesem) Fall). –

1

möchten uns an dieser Arbeit?

class MySoapClient extends SoapClient 
{ 
    function __soapCall($function_name, $arguments, $options = null, $input_headers = null, &$output_headers = null) 
    { 
     $out = parent::__soapCall($function_name, $arguments, $options, $input_headers, $output_headers); 

     // log request here... 
     // log response here... 

     return $out; 
    } 
} 

Da Soapclient bereits alle Anfragen durch __soapCall sendet, können Sie sie abfangen von Soapclient Subklassen und überschreiben sie. Natürlich, damit es funktioniert, müssen Sie auch alle new SoapClient(...) in Ihrem Code durch new MySoapClient(...) ersetzen, aber das scheint wie eine ziemlich einfache Suche und ersetzen Aufgabe.

+0

Das funktioniert nicht ... haben Sie einen Einblick, warum __soapCall nicht mehr überschrieben werden kann? –

6

Ich denke, der bessere Weg ist, SoapClient::__doRequest() (und nicht SoapClient::__soapCall()) zu überschreiben, da Sie direkten Zugriff auf die Anfrage haben-sowie auf die Antwort-XML. Aber der allgemeine Ansatz zur Unterklasse SoapClient sollte der richtige Weg sein.

class My_LoggingSoapClient extends SoapClient 
{ 
    // logging methods 

    function __doRequest($request, $location, $action, $version, $one_way = 0) 
    { 
     $this->_logRequest($location, $action, $version, $request); 
     $response = parent::__doRequest($request, $location, $action, $version, $one_way); 
     $this->_logResponse($location, $action, $version, $response); 
     return $response; 
    } 
} 

EDIT

Aus OOP-Design/Entwurfsmuster Sicht ein Decorator offensichtlich der bessere Weg ist, diese Art von Problem zu umgehen - bitte Gordon's answer sehen. Aber das ist ein bisschen schwieriger zu implementieren.

+0

Ich denke, das ist eine bevorzugte Lösung gegenüber Dekorateur. Da 'SoapClient' nicht wirklich OOP-aish ist, und es keine Schnittstellen implementiert, ist es wirklich keine Möglichkeit, den Decorator aus sprachlicher Sicht damit kompatibel zu machen, wenn die Soap-Client-Abhängigkeit mit' \ SoapClient typisiert wurde '. Wenn Sie eine Bibliothek schreiben, die diesen Client selbst verwendet, sollten Sie sie besser einbinden und eine Schnittstelle implementieren. Wenn Sie jedoch ein Objekt in einer bereits vorhandenen Bibliothek ersetzen wollen, die ein Objekt vom Typ 'SoapClient' erwartet, tun Dekoratoren das nicht (aber wiederum, wenn es keine Typhinweise gibt, können Sie damit arbeiten). – sevavietl

+0

@sevavietl Das stimmt. Die Implementierung eines Decorators, der mit einem 'SoapClient'-Typhinweis kompatibel ist, könnte problematisch sein (aufgrund der fehlenden Schnittstelle und der dynamischen Natur der' SoapClient'-API). Habe das nie selbst versucht, also kann ich nicht beurteilen, ob das überhaupt möglich ist oder nicht. –

5

Leider nochmals zu besuchen so ein alter Post, aber ich stieß auf einige Herausforderungen mit der akzeptierten Antwort der Umsetzung eines Dekorators, der für die Protokollierung der Soap-Anfragen verantwortlich ist und teilen wollte, falls jemand anderes in diese läuft.

Stellen Sie sich vor, Sie richten Ihre Instanz wie folgt ein und verwenden dabei die SoapClientLogger-Klasse, die in der akzeptierten Antwort angegeben ist.

$mySoapClient = new SoapClientLogger(new SoapClient()); 

Vermutlich jede Methode, die Sie auf der SoapClientLogger Instanz aufrufen wird auf dem Soapclient durch die __call() -Methode und ausgeführt werden sollen geben. Jedoch Sie in der Regel die Verwendung eines Soapclient machen, indem sie die Methoden aus dem WSDL generierten Aufruf wie folgt aus:

$mySoapClient->AddMember($parameters); // AddMember is defined in the WSDL 

Diese Nutzung wird nie die SoapClientLogger des _doRequest() -Methode getroffen und damit die Anforderung wird nicht protokolliert.Stattdessen wird AddMember() über $ mySoapClient :: _ call() und dann direkt an die _doRequest-Methode der SoapClient-Instanz weitergeleitet.

Ich suche immer noch nach einer eleganten Lösung dafür.

3

Adressiervolumen das Problem in https://stackoverflow.com/a/3939077/861788 hebt ich mit der folgenden Lösung kam (full source):

<?php 

namespace Lc5\Toolbox\LoggingSoapClient; 

use Psr\Log\LoggerInterface; 

/** 
* Class LoggingSoapClient 
* 
* @author Łukasz Krzyszczak <[email protected]> 
*/ 
class LoggingSoapClient 
{ 

    const REQUEST = 'Request'; 
    const RESPONSE = 'Response'; 

    /** 
    * @var TraceableSoapClient 
    */ 
    private $soapClient; 

    /** 
    * @var LoggerInterface 
    */ 
    private $logger; 

    /** 
    * @param TraceableSoapClient $soapClient 
    * @param LoggerInterface $logger 
    */ 
    public function __construct(TraceableSoapClient $soapClient, LoggerInterface $logger) 
    { 
     $this->soapClient = $soapClient; 
     $this->logger  = $logger; 
    } 

    /** 
    * @param string $method 
    * @param array $arguments 
    * @return string 
    */ 
    public function __call($method, array $arguments) 
    { 
     $result = call_user_func_array([$this->soapClient, $method], $arguments); 

     if (!method_exists($this->soapClient, $method) || $method === '__soapCall') { 
      $this->logger->info($this->soapClient->__getLastRequest(), ['type' => self::REQUEST]); 
      $this->logger->info($this->soapClient->__getLastResponse(), ['type' => self::RESPONSE]); 
     } 

     return $result; 
    } 

    /** 
    * @param string $request 
    * @param string $location 
    * @param string $action 
    * @param int $version 
    * @param int $oneWay 
    * @return string 
    */ 
    public function __doRequest($request, $location, $action, $version, $oneWay = 0) 
    { 
     $response = $this->soapClient->__doRequest($request, $location, $action, $version, $oneWay); 

     $this->logger->info($request, ['type' => self::REQUEST]); 
     $this->logger->info($response, ['type' => self::RESPONSE]); 

     return $response; 
    } 
} 

Verbrauch:

use Lc5\Toolbox\LoggingSoapClient\LoggingSoapClient; 
use Lc5\Toolbox\LoggingSoapClient\TraceableSoapClient; 
use Lc5\Toolbox\LoggingSoapClient\MessageXmlFormatter; 
use Monolog\Handler\StreamHandler; 
use Monolog\Logger; 

$handler = new StreamHandler('path/to/your.log'); 
$handler->setFormatter(new MessageXmlFormatter()); 

$logger = new Logger('soap'); 
$logger->pushHandler($handler); 

$soapClient = new LoggingSoapClient(new TraceableSoapClient('http://example.com'), $logger); 

SOAP-Client wird dann jede Anfrage protokolliert und Antwort unter Verwendung eines beliebigen PSR-3 Logger.

Verwandte Themen