2009-11-13 12 views
8

Qualcuno sa come registrare tutte le richieste e le risposte con il built-in SoapClient in PHP? Potrei infatti registrare manualmente tutto con SoapClient::__getLastRequest() e SoapClient::__getLastResponse() Ma abbiamo così tante richieste di sapone nel nostro sistema che sto cercando altre possibilità.Registrazione di tutte le richieste e risposte di sapone in PHP

Nota: sto usando la modalità di WSDL in modo da utilizzare un metodo che incanala tutto fino alla SoapClient::__soapCall() non è un'opzione

risposta

14

I secondo suggerimento di Aleksanders e Stefans ma non sottoclasse SoapClient. Invece avrei avvolto il normale SoapClient in un decoratore, perché la registrazione non è una preoccupazione diretta di SoapClient. Inoltre, l'accoppiamento libero consente di sostituire facilmente SoapClient con una simulazione nei tuoi Unit Test, così puoi concentrarti sul test della funzionalità di registrazione. Se desideri registrare solo chiamate specifiche, puoi aggiungere una logica che filtra richieste e risposte in base a $ azione o qualsiasi cosa tu ritenga opportuno.

Modifica poiché Stefan ha suggerito di aggiungere del codice, il decoratore sarebbe probabilmente guardare qualcosa di simile, anche se non sono sicuro circa il metodo __call() (vedi commenti 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

L'uso di un decoratore è un'ottima idea. In realtà, se dovessi lavorare sullo stesso problema, andrei con una soluzione decoratrice. Ma penso che la soluzione di sottoclassi sia un po 'più comprensibile. –

+1

Forse aiuterà l'OP se aggiungi un esempio di codice. –

+0

E poi c'è un problema PHP che disabilita lo * stacking * dei decoratori se stai usando la funzione di overloading dei metodi '__call()' in uno dei tuoi decoratori o nella classe da decorare ('SoapClient' in questo Astuccio). –

1

Sarebbe qualcosa di simile a questo lavoro?

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

Dal SoapClient manda già tutte le richieste attraverso __soapCall, è possibile intercettare sottoclasse SoapClient e ridefinendo esso. Ovviamente, per farlo funzionare è necessario sostituire anche ogni new SoapClient(...) nel codice con new MySoapClient(...), ma sembra un'attività di ricerca e sostituzione piuttosto semplice.

+0

Questo non funziona ... avete qualche idea sul perché __soapCall non può essere più sovrascritto? –

6

Penso che il modo migliore è quello di ignorare SoapClient::__doRequest() (e non SoapClient::__soapCall()) come avrete accesso diretto alla richiesta- nonché alla risposta-XML. Ma l'approccio generale alla sottoclasse SoapClient dovrebbe essere la strada da percorrere.

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

Dal OOP-design/design pattern punto di vista un Decorator è ovviamente il modo migliore per gestire questo tipo di problema - Guarda Gordon's answer. Ma questo è un po 'più difficile da implementare.

+0

Immagino che questa sia una soluzione preferibile rispetto a un decoratore. Dato che 'SoapClient' non è realmente OOP-aish e non implementa alcuna interfaccia, non è in alcun modo possibile rendere decoratore compatibile con esso dal punto di vista della lingua, se la dipendenza da soap client fosse accennata con' \ SoapClient '. Se scrivi una libreria che utilizza questo client tu stesso, ovviamente, meglio avvolgerlo e implementare un'interfaccia. Ma se vuoi sostituire l'oggetto in una libreria già esistente, che si aspetta un oggetto di tipo 'SoapClient', i decoratori non lo faranno (ma di nuovo, se non ci sono suggerimenti di tipo che puoi usare con esso). – sevavietl

+0

@sevavietl Proprio così. L'implementazione di un decoratore compatibile con un suggerimento di tipo 'SoapClient' potrebbe essere problematico (a causa dell'interfaccia mancante e della natura dinamica dell'API' SoapClient'). Non ci ho mai provato, quindi non posso giudicare se sia possibile in generale o no. –

5

Siamo spiacenti di rivisitare così vecchio post, ma ho incontrato alcune sfide con l'implementazione della risposta accettata di un decoratore che è responsabile per la registrazione delle richieste di sapone e voleva condividere nel caso in cui qualcun altro si imbatte in questo.

Immagina di configurare l'istanza come la seguente, utilizzando la classe SoapClientLogger descritta nella risposta accettata.

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

Presumibilmente qualsiasi metodo si chiama nell'istanza SoapClientLogger otterrà passato attraverso il metodo __call() ed eseguito sul SoapClient. Tuttavia, in genere si fanno uso di un SoapClient chiamando i metodi generati dal WSDL, come questo:

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

Questo utilizzo non sarà mai colpito il metodo del SoapClientLogger _doRequest() e, quindi, la richiesta non sarà registrata.Invece AddMember() viene instradato tramite $ mySoapClient :: _ call() e poi direttamente al metodo _doRequest dell'istanza di SoapClient.

Sto ancora cercando una soluzione elegante a questo.

3

Indirizzamento la questione sollevata nella https://stackoverflow.com/a/3939077/861788 Sono venuto con la seguente soluzione (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; 
    } 
} 

Usage:

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

client SOAP sarà quindi accedere ogni richiesta e risposta utilizzando qualsiasi PSR-3 logger.

Problemi correlati