2009-11-13 159 views
8

有誰知道如何記錄所有的請求和PHP內置的SoapClient響應?事實上,我可以用SoapClient::__getLastRequest()SoapClient::__getLastResponse()手動記錄所有內容。但我們在系統中有很多肥皂請求,我正在尋找其他可能性。在PHP中記錄所有Soap請求和響應

注:我使用WSDL模式,以便使用該隧道全線貫通到SoapClient::__soapCall()是不是一種選擇

回答

14

我第二次Aleksanders和Stefans的建議,但不會子類SoapClient。相反,我會將常規SoapClient包裝在裝飾器中,因爲日誌不是SoapClient的直接關注點。另外,鬆耦合使您可以輕鬆地將SoapClient替換爲UnitTest中的模擬對象,以便您可以專注於測試日誌記錄功能。如果您只想記錄特定的調用,則可以添加一些邏輯,通過$ action或您認爲合適的任何內容過濾請求和響應。

編輯因爲斯特凡建議添加一些代碼,裝飾可能會是這個樣子,雖然我不知道的__call()方法(參見Stefans評論)

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

使用裝飾器是一個非常好的主意。實際上,如果我必須解決同樣的問題,我會自己選擇裝飾解決方案。但我認爲,子類化解決方案更容易理解。 – 2009-11-13 14:49:10

+1

如果你添加一個代碼示例,它可能會幫助OP。 – 2009-11-13 14:50:00

+0

然後有一個PHP問題,如果你在一個裝飾器或要裝飾的類中使用方法重載特性__call(),則不允許裝飾器堆疊*(在此處的'SoapClient'案件)。 – 2009-11-13 14:59:36

1

會是這樣的工作方法?

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; 
    } 
} 

和SoapClient以來已經通過__soapCall發送的所有請求,你可以通過繼承和SoapClient的覆蓋它攔截下來。當然,爲了使其工作,您還需要將代碼中的每個new SoapClient(...)替換爲new MySoapClient(...),但這似乎是一個非常簡單的搜索和替換任務。

+0

這不起作用......你有什麼洞察力,爲什麼__soapCall不能被覆蓋了嗎? – 2015-03-16 16:02:04

6

我認爲更好的方法是覆蓋SoapClient::__doRequest()(而不是SoapClient::__soapCall()),因爲您可以直接訪問請求以及響應XML。但子類SoapClient的一般方法應該是要走的路。

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; 
    } 
} 

編輯

OOP設計/設計模式來看一個Decorator顯然是更好的方式來處理這類問題 - 請Gordon's answer。但是這實施起來有點困難。

+0

我想這是一個優於裝飾者的解決方案。由於'SoapClient'不是真正的OOP-aish,並且它沒有實現任何接口,所以從語言的角度來看,它實際上沒有辦法讓裝飾器與它兼容,如果soap客戶端依賴被type-hinted'\ SoapClient '。如果你自己編寫一個使用這個客戶端的庫,當然最好包裝它並實現一些接口。但是如果你想在一個已經存在的庫中替換對象,那麼期望一個類型爲「SoapClient」的對象,裝飾器不會這樣做(但是如果沒有類型提示,你可以使用它)。 – sevavietl 2018-02-15 16:20:32

+0

@sevavietl是的。實現一個與'SoapClient'類型提示兼容的裝飾器可能會有問題(由於缺少接口和'SoapClient' API的動態特性)。從來沒有嘗試過,所以我不能判斷一般情況下是否可能。 – 2018-02-16 07:40:44

5

對不起重溫這樣一箇舊帖子,但我遇到了一些挑戰,接受答案的裝飾器的實現負責記錄Soap請求,並希望共享以防其他人遇到此問題。

想象一下,使用接受的答案中列出的SoapClientLogger類設置您的實例,如下所示。

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

想必您對SoapClientLogger實例調用任何方法會通過__call()方法傳遞並在SoapClient的執行。但是,通常您使用一個SoapClient類的調用由WSDL生成的方法,就像這樣:

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

這種用法永遠不會撞到SoapClientLogger的_doRequest()方法,因此請求將不會被記錄。相反,AddMember()通過$ mySoapClient :: _ call()路由,然後直到SoapClient實例的_doRequest方法。

我仍在尋找一個優雅的解決方案。

3

Adressing在https://stackoverflow.com/a/3939077/861788提出我想出了以下解決方案(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; 
    } 
} 

用法:然後

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客戶端將使用任何記錄每個請求和響應PSR-3記錄儀。