Регистрация всех запросов и ответов на мыло в PHP

Кто-нибудь знает, как регистрировать все запросы и ответы с помощью встроенного SoapClient в PHP? Я мог бы на самом деле вручную войти все SoapClient::__getLastRequest() а также SoapClient::__getLastResponse() Но в нашей системе столько запросов на мыло, что я ищу другие возможности.

Примечание: я использую режим wsdl, поэтому использую метод, который туннелирует до SoapClient::__soapCall() не вариант

7 ответов

Решение

Я поддерживаю предложение Александерс и Стефанс, но не буду подкласс SoapClient. Вместо этого я бы обернул обычный SoapClient в декораторе, потому что ведение журнала не является прямой задачей SoapClient. Кроме того, слабая связь позволяет вам легко заменить SoapClient макетом в ваших UnitTests, чтобы вы могли сосредоточиться на тестировании функциональности регистрации. Если вы хотите регистрировать только определенные вызовы, вы можете добавить некоторую логику, которая фильтрует запросы и ответы по $ action или как угодно.

Редактировать, поскольку Стефан предложил добавить некоторый код, декоратор, вероятно, будет выглядеть примерно так, хотя я не уверен насчет метода __call() (см. Комментарии Стефана)

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

РЕДАКТИРОВАТЬ

С точки зрения модели ООП-дизайна / дизайна Декоратор, очевидно, является лучшим способом решения такого рода проблем - см . Ответ Гордона. Но это немного сложнее реализовать.

Извините, что вернулся к такому старому посту, но я столкнулся с некоторыми трудностями, связанными с реализацией принятого ответа декоратора, который отвечает за регистрацию запросов Soap, и хотел бы поделиться им, если кто-нибудь еще столкнется с этим.

Представьте, что вы настроили свой экземпляр следующим образом, используя класс SoapClientLogger, описанный в принятом ответе.

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

Предположительно, любой метод, который вы вызываете для экземпляра SoapClientLogger, будет передан через метод __call() и выполнен в SoapClient. Однако обычно вы используете SoapClient, вызывая методы, сгенерированные из WSDL, например:

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

Такое использование никогда не повлияет на метод _doRequest() SoapClientLogger и, следовательно, запрос не будет зарегистрирован. Вместо этого AddMember() направляется через $mySoapClient::_call(), а затем прямо в метод _doRequest экземпляра SoapClient.

Я все еще ищу элегантное решение для этого.

Обращаясь к проблеме, поднятой в /questions/25424956/registratsiya-vseh-zaprosov-i-otvetov-na-myilo-v-php/25424974#25424974 я пришел со следующим решением ( полный источник):

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

Будет ли что-то вроде этой работы?

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(...), но это похоже на довольно простую задачу поиска и замены.

Я знаю, что это очень старая проблема, но я вижу кусочки и фрагменты, и сложно собрать все воедино.

Расширяя ответ Стефана Герига , мне пришлось расширить 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(),
            parent::__getLastResponse()
        );

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

Надеюсь, это поможет прояснить возможные решения.

Я проверял запросы и ответы с помощью WireShark.

Другие вопросы по тегам