【问题标题】:Logging all Soap request and responses in PHP在 PHP 中记录所有 Soap 请求和响应
【发布时间】:2010-12-16 07:21:07
【问题描述】:

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

注意:我使用的是 wsdl 模式,因此不能使用隧道通到 SoapClient::__soapCall() 的方法

【问题讨论】:

    标签: php soap


    【解决方案1】:

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

    编辑因为 Stefan 建议添加一些代码,装饰器可能看起来像这样,虽然我不确定 __call() 方法(参见 Stefans cmets)

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

    【讨论】:

    • 使用装饰器是一个非常好的主意。实际上,如果我必须解决同样的问题,我会自己使用装饰器解决方案。但我认为,子类化解决方案更容易理解。
    • 如果您在其中一个装饰器中或在要装饰的类(在这种情况下为SoapClient)。
    • 我不知道 __call() 有问题。你有我可以阅读它的链接吗?很高兴知道这一点。哦,我在上面添加了代码。
    • 我的意思是在使用__call()时无法确定一个方法是否可以调用,所以调用一个不存在的方法总是会引发一个致命错误。如果要装饰SoapClient,您可以查看is_callable(array($this->soapClient, $method))in_array($method, $this->soapClient-> __getFunctions())。但这不允许堆叠装饰器。
    • 忘记我对问题(以及装饰器的堆叠)的评论......可以进行我上面显示的检查并且仍然能够堆叠你的装饰器。对不起所有的混乱g
    【解决方案2】:

    我认为更好的方法是覆盖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。但这实现起来有点困难。

    【讨论】:

    • 我想这是一个比装饰器更好的解决方案。由于SoapClient 不是真正的OOP-aish,并且它没有实现任何接口,所以从语言的角度来看,如果soap 客户端依赖项使用\SoapClient 进行类型提示,那么从语言的角度来看,确实没有办法使装饰器与之兼容.如果您自己编写一个使用该客户端的库,当然最好将其包装并实现一些接口。但是如果你想替换一个已经存在的库中的对象,它需要一个类型为SoapClient的对象,装饰器不会这样做(但同样,如果没有类型提示,你可以使用它)。
    • @sevavietl 没错。实现与SoapClient 类型提示兼容的装饰器可能会出现问题(由于缺少接口和SoapClient API 的动态特性)。我自己从来没有尝试过,所以我无法判断一般情况下是否可行。
    • 这是唯一对我有用的方法,使用 wsdl 的动态方法。
    【解决方案3】:

    很抱歉重温这么旧的帖子,但我在接受的答案实现装饰器时遇到了一些挑战,该装饰器负责记录 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 方法。

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

    【讨论】:

      【解决方案4】:

      解决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 记录器记录每个请求和响应。

      【讨论】:

        【解决方案5】:

        这样的东西有用吗?

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

        【讨论】:

        • 这行不通...您对为什么 __soapCall 不能再被覆盖有任何见解吗?
        【解决方案6】:

        我知道这是一个非常古老的问题,但我看到了零碎的东西,很难将它们拼凑在一起。

        扩展Stefan Gehrig answer,我最终不得不扩展SoapClient 以便能够使用来自WSDL 的动态方法。此外,为方便起见,此类直接在构造中添加了跟踪选项。

        <?php
        
        class SoapClientLogger extends SoapClient
        {
            /**
             * @var string
             */
            protected $provider;
        
            /**
             * Create the SoapClient instance.
             *
             * @param  string|null  $wsdl
             * @param  array|null  $options
             * @throws SoapFault
             */
            public function __construct(string $wsdl = null, ?array $options = null)
            {
                /**
                 * Set trace option to enable logging.
                 */
                $options = array_merge($options, [
                    'trace' => 1,
                ]);
        
                parent::__construct($wsdl, $options);
            }
        
            /**
             * Overloading __doRequest method.
             *
             * @param  string  $request
             * @param  string  $location
             * @param  string  $action
             * @param  int  $version
             * @param  null  $one_way
             * @return string|null
             */
            public function __doRequest($request, $location, $action, $version, $one_way = NULL): ?string
            {
                $sentAt = now();
                $startTime = microtime(true);
                $response = parent::__doRequest($request, $location, $action, $version, $one_way);
        
                $this->log(
                    $location,
                    $action,
                    $version,
                    $sentAt,
                    number_format(microtime(true) - $startTime, 4),
                    parent::__getLastRequestHeaders(),
                    parent::__getLastRequest(),
                    parent::__getLastResponseHeaders(),
                    // Sometimes get last response is null but $response has the body.
                    parent::__getLastResponse() ?? $response
                );
        
                return $response;
            }
        
        
            /**
             * Handle logging.
             *
             * @param  string  $location
             * @param  string  $action
             * @param  string  $version
             * @param  Carbon  $sentAt
             * @param  float  $timing
             * @param  string|null  $requestHeaders
             * @param  string|null  $requestBody
             * @param  string|null  $responseHeaders
             * @param  string|null  $responseBody
             */
            protected function log(
                string $location,
                string $action,
                string $version,
                Carbon $sentAt,
                float $timing,
                string $requestHeaders = null,
                string $requestBody = null,
                string $responseHeaders = null,
                string $responseBody = null
            ) {
                // Do logging tasks here.
            }
        }
        

        希望这有助于明确可能的解决方案。

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-04-02
            • 2011-11-02
            • 1970-01-01
            • 1970-01-01
            • 2023-01-09
            • 2014-03-22
            • 2015-03-11
            • 1970-01-01
            相关资源
            最近更新 更多