English 中文(简体)
Logging all Soap request and responses in PHP
原标题:
  • 时间:2009-11-13 13:57:51
  •  标签:
  • php
  • soap

Does anyone know how to log all request and responses with the builtin SoapClient in PHP? I could in fact manually log everything with SoapClient::__getLastRequest() and SoapClient::__getLastResponse() But we have that much soap requests in our system that i m looking other possibilities.

Note: i m using wsdl mode so using a method that tunnels all through to SoapClient::__soapCall() isn t an option

最佳回答

I second Aleksanders and Stefans suggestion but would not subclass SoapClient. Instead I d wrap the regular SoapClient in a decorator, because logging is not a direct concern of the SoapClient. In addition, the loose coupling lets you easily substitute the SoapClient with a mock in your UnitTests, so you can concentrate on testing the logging functionality. If you only want to log specific calls, you can add some logic that filters requests and responses by $action or anything you see fit.

Edit since Stefan suggested to add some code, the decorator would probably look something like this, although I am not sure about the __call() method (see Stefans comments)

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);
    }
}
问题回答

I think the better way is to override SoapClient::__doRequest() (and not SoapClient::__soapCall()) as you ll have direct access to the request- as well as to the response-XML. But the general approach to subclass SoapClient should be the way to go.

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

From an OOP-design / design pattern point of view a Decorator is obviously the better way to handle this kind of problem - please see Gordon s answer. But this is a little bit more difficult to implement.

Sorry to revisit such an old post, but I encountered some challenges with the accepted answer s implementation of a decorator that is responsible for the logging of the Soap requests and wanted to share in case anyone else runs into this.

Imagine you set up your instance like the following, using the SoapClientLogger class outlined in the accepted answer.

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

Presumably any method you call on the SoapClientLogger instance will get passed through the __call() method and executed on the SoapClient. However, typically you make use of a SoapClient by calling the methods generated from the WSDL, like this:

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

This usage will never hit the SoapClientLogger s _doRequest() method and thus the request will not be logged. Instead AddMember() is routed through $mySoapClient::_call() and then right on down to the SoapClient instance s _doRequest method.

I m still searching for an elegant solution to this.

Adressing the issue raised in https://stackoverflow.com/a/3939077/861788 I came with the following solution (full source):

<?php

namespace Lc5ToolboxLoggingSoapClient;

use PsrLogLoggerInterface;

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

Usage:

use Lc5ToolboxLoggingSoapClientLoggingSoapClient;
use Lc5ToolboxLoggingSoapClientTraceableSoapClient;
use Lc5ToolboxLoggingSoapClientMessageXmlFormatter;
use MonologHandlerStreamHandler;
use MonologLogger;

$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 will then log every request and response using any PSR-3 logger.

Would something like this work?

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

Since SoapClient already sends all requests through __soapCall, you can intercept them by subclassing SoapClient and overriding it. Of course, to make it work you need to also replace every new SoapClient(...) in your code with new MySoapClient(...), but that seems like a pretty easy search and replace task.

I know this is a very old issue but I see bits and pieces and it s difficult to piece it all together.

Expanding on Stefan Gehrig answer, I ended up having to extend SoapClient in order to be able to use the dynamic methods from the WSDL. Also, out of convenience, this class adds the tracing option directly in the construct.

<?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.
    }
}

Hope this helps provide clarity on possible solutions.

I checked requests and responses with WireShark.





相关问题
Brute-force/DoS prevention in PHP [closed]

I am trying to write a script to prevent brute-force login attempts in a website I m building. The logic goes something like this: User sends login information. Check if username and password is ...

please can anyone check this while loop and if condition

<?php $con=mysql_connect("localhost","mts","mts"); if(!con) { die( unable to connect . mysql_error()); } mysql_select_db("mts",$con); /* date_default_timezone_set ("Asia/Calcutta"); $date = ...

定值美元

如何确认来自正确来源的数字。

Generating a drop down list of timezones with PHP

Most sites need some way to show the dates on the site in the users preferred timezone. Below are two lists that I found and then one method using the built in PHP DateTime class in PHP 5. I need ...

Text as watermarking in PHP

I want to create text as a watermark for an image. the water mark should have the following properties front: Impact color: white opacity: 31% Font style: regular, bold Bevel and Emboss size: 30 ...

How does php cast boolean variables?

How does php cast boolean variables? I was trying to save a boolean value to an array: $result["Users"]["is_login"] = true; but when I use debug the is_login value is blank. and when I do ...

热门标签