在PHP中记录所有Soap请求和响应

时间:2009-11-13 13:57:51

标签: php soap

有没有人知道如何使用PHP内置的SoapClient记录所有请求和响应?实际上,我可以使用SoapClient::__getLastRequest()SoapClient::__getLastResponse()手动记录所有内容但是我们的系统中有很多肥皂请求,我正在寻找其他可能性。

注意:我正在使用wsdl模式,因此使用隧道传输到SoapClient::__soapCall()的方法不是一个选项

6 个答案:

答案 0 :(得分:17)

我是Aleksanders和Stefans的第二个建议,但不会将SoapClient子类化。相反,我将常规SoapClient包装在装饰器中,因为日志记录不是SoapClient的直接关注点。此外,松散耦合使您可以轻松地在UnitTests中使用模拟替换SoapClient,因此您可以专注于测试日志记录功能。如果您只想记录特定的调用,可以添加一些逻辑,通过$ action或您认为合适的任何内容来过滤请求和响应。

编辑,因为Stefan建议添加一些代码,装饰器可能看起来像这样,虽然我不确定__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);
    }
}

答案 1 :(得分:7)

我认为更好的方法是覆盖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-design / 设计模式的角度来看,Decorator显然是处理此类问题的更好方法 - 请参阅{{ 3}}。但实施起来有点困难。

答案 2 :(得分:6)

很抱歉重新访问这么老的帖子,但是我遇到了一些挑战,接受了答案的装饰器的实现,负责记录Soap请求,并希望分享以防其他人遇到此问题。

想象一下,使用接受的答案中概述的SoapClientLogger类,设置如下所示的实例。

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

假设您在SoapClientLogger实例上调用的任何方法都将通过__call()方法传递并在SoapClient上执行。但是,通常通过调用从WSDL生成的方法来使用SoapClient,如下所示:

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

此用法永远不会触及SoapClientLogger的_doRequest()方法,因此不会记录请求。相反,AddMember()通过$ mySoapClient :: _ call()进行路由,然后直接进入SoapClient实例的_doRequest方法。

我仍然在寻找一个优雅的解决方案。

答案 3 :(得分:3)

解决https://stackoverflow.com/a/3939077/861788中提出的问题我提出了以下解决方案(full source):

<?php

namespace Lc5\Toolbox\LoggingSoapClient;

use Psr\Log\LoggerInterface;

/**
 * Class LoggingSoapClient
 *
 * @author Łukasz Krzyszczak <lukasz.krzyszczak@gmail.com>
 */
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记录器记录每个请求和响应。

答案 4 :(得分: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(...),但这似乎是一个非常简单的搜索和替换任务。

答案 5 :(得分:-2)

我用WireShark检查了请求和响应。