diff --git a/README.md b/README.md index 0f8e9fb..492ddfd 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,9 @@ A Laravel SOAP client that provides a clean interface for handling requests and * [Call](#call) * [Parameters](#parameters) * [Nodes](#nodes) +- [Tracing](#Tracing) - [Hooks](#hooks) -- [Faking](#faking) +- [Faking](#faking) - [Configuration](#configuration) * [Include](#include) @@ -147,6 +148,24 @@ Now, just by adding or removing a body to the `soap_node()` the outputted array A node can be made with either the Facade `Soap::node()` or the helper method `soap_node()`. +## Tracing +Soap allows you to easily trace your interactions with the SOAP endpoint being accessed. + +To trace all requests, set the following in the register method of your `ServiceProvider`: + +```php +Soap::trace() +``` +Now, all `Response` objects returned will have a `Trace` object attached, accessible via `$response->getTrace()`. This has two properties `xmlRequest` and `xmlResponse` - storing the raw XML values for each. + +Tracing can also be declared locally: +```php +Soap::to('...')->trace()->call('...') +``` +Now, just this `Response` will have a valid `Trace`. + +Tracing is null safe. If `$response->getTrace()` is called when a `Trace` hasn't been set, a new `Trace` is returned. This `Trace`'s properties will all return `null`. + ## Hooks Hooks allow you to perform actions before and after Soap makes a request. @@ -225,20 +244,20 @@ you can use this method, passing in the desired count as a parameter. #### `Soap::assertSent(callable $callback)` -You can dive a little deeper and test that a particular request was +You can dive a little deeper and test that a particular request was actually sent, and that it returned the expected response. You should pass a closure into this method, which receives the `$request` and `$response` as parameters, and return `true` if they match your expectations. #### `Soap::assertNotSent(callable $callback)` -This is the opposite of `Soap::assertSent`. You can make sure that a +This is the opposite of `Soap::assertSent`. You can make sure that a particular request wasn't made. Again, returning `true` from the closure will cause it to pass. #### `Soap::assertNothingSent()` -If you just want to make sure that absolutely nothing was sent out, you +If you just want to make sure that absolutely nothing was sent out, you can call this. It does what it says on the tin. ## Configuration @@ -257,7 +276,7 @@ You can even use dot syntax on your array keys to permeate deeper into the reque ```php Soap::include(['login.credentials' => soap_node(['user' => '...', 'password' => '...'])])->for('...'); -``` +``` ### Changelog diff --git a/src/Request/Request.php b/src/Request/Request.php index c5a3edc..3ae6569 100644 --- a/src/Request/Request.php +++ b/src/Request/Request.php @@ -33,4 +33,6 @@ public function getMethod(); public function getBody(); public function set($key, $value): self; + + public function trace($shouldTrace = true): self; } diff --git a/src/Request/SoapClientRequest.php b/src/Request/SoapClientRequest.php index 45ff136..8ca6605 100644 --- a/src/Request/SoapClientRequest.php +++ b/src/Request/SoapClientRequest.php @@ -4,17 +4,18 @@ use RicorocksDigitalAgency\Soap\Parameters\Builder; use RicorocksDigitalAgency\Soap\Response\Response; -use SoapClient; +use RicorocksDigitalAgency\Soap\Support\Tracing\Trace; class SoapClientRequest implements Request { protected string $endpoint; protected string $method; protected $body = []; - protected SoapClient $client; + protected $client; protected Builder $builder; protected Response $response; protected $hooks = []; + protected $shouldTrace = false; public function __construct(Builder $builder) { @@ -48,8 +49,23 @@ public function call($method, $parameters = []) protected function getResponse() { - return $this->response ??= Response::new($this->makeRequest()) - ->withXml($this->client()->__getLastRequest(), $this->client()->__getLastResponse()); + return $this->response ??= $this->getRealResponse(); + } + + protected function getRealResponse() + { + return tap( + Response::new($this->makeRequest()), + fn($response) => $this->shouldTrace ? $this->addTrace($response) : $response + ); + } + + protected function addTrace($response) + { + return $response->setTrace( + Trace::thisXmlRequest($this->client()->__getLastRequest()) + ->thisXmlResponse($this->client()->__getLastResponse()) + ); } protected function makeRequest() @@ -59,7 +75,7 @@ protected function makeRequest() protected function client() { - return $this->client ??= new SoapClient($this->endpoint, ['trace' => true]); + return $this->client ??= app(\SoapClient::class, ['wsdl' => $this->endpoint, 'options' => ['trace' => $this->shouldTrace]]); } public function getMethod() @@ -109,4 +125,10 @@ public function set($key, $value): Request data_set($this->body, $key, $value); return $this; } + + public function trace($shouldTrace = true): Request + { + $this->shouldTrace = $shouldTrace; + return $this; + } } diff --git a/src/Response/Response.php b/src/Response/Response.php index b857b2b..cbd6abf 100644 --- a/src/Response/Response.php +++ b/src/Response/Response.php @@ -2,11 +2,12 @@ namespace RicorocksDigitalAgency\Soap\Response; +use RicorocksDigitalAgency\Soap\Support\Tracing\Trace; + class Response { public $response; - public $xmlRequest = null; - public $xmlResponse = null; + protected Trace $trace; public static function new($response = []): self { @@ -18,13 +19,17 @@ public function __get($name) return data_get($this->response, $name); } - public function withXml($xmlRequest, $xmlResponse) + public function setTrace(Trace $trace) { - $this->xmlRequest = $xmlRequest; - $this->xmlResponse = $xmlResponse; + $this->trace = $trace; return $this; } + public function trace() + { + return $this->trace ??= app(Trace::class); + } + public function set($key, $value): self { data_set($this->response, $key, $value); diff --git a/src/Soap.php b/src/Soap.php index c6cf45c..82cc452 100644 --- a/src/Soap.php +++ b/src/Soap.php @@ -69,6 +69,12 @@ public function afterRequesting(callable $hook) return $this; } + public function trace($shouldTrace = true) + { + $this->beforeRequesting(fn($request) => $request->trace($shouldTrace)); + return $this; + } + public function __call($method, $parameters) { if (static::hasMacro($method)) { diff --git a/src/Support/Tracing/Trace.php b/src/Support/Tracing/Trace.php new file mode 100644 index 0000000..11907ba --- /dev/null +++ b/src/Support/Tracing/Trace.php @@ -0,0 +1,19 @@ + $instance->xmlRequest = $xml); + } + + public function thisXmlResponse($xml): self + { + return tap($this, fn($self) => $self->xmlResponse = $xml); + } +} diff --git a/tests/Mocks/MockSoapClient.php b/tests/Mocks/MockSoapClient.php new file mode 100644 index 0000000..6b7c850 --- /dev/null +++ b/tests/Mocks/MockSoapClient.php @@ -0,0 +1,94 @@ +shouldTrace = true; + } + } + + public function __call(string $function_name, array $arguments) + { + } + + public function __doRequest(string $request, string $location, string $action, int $version, $one_way = 0) + { + } + + public function __getCookies() + { + } + + public function __getFunctions() + { + return [ + "The mock client does not actually have functions!" + ]; + } + + public function __getLastRequest() + { + if (!$this->shouldTrace) { + return null; + } + + return 'World'; + } + + public function __getLastRequestHeaders() + { + } + + public function __getLastResponse() + { + if (!$this->shouldTrace) { + return null; + } + + return 'Success!'; + } + + public function __getLastResponseHeaders() + { + } + + public function __getTypes() + { + } + + public function __setCookie(string $name, string $value = null) + { + } + + public function __setLocation(string $new_location = null) + { + } + + public function __setSoapHeaders($soapheaders) + { + } + + public function __soapCall( + string $function_name, + array $arguments, + array $options = [], + $input_headers = [], + &$output_headers = [] + ) { + } + + public function SoapClient(mixed $wsdl, array $options = []) + { + return new static($wsdl, $options); + } + +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index d74c8ba..167d79f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,11 +4,17 @@ use Orchestra\Testbench\TestCase as OrchestraTestCase; use RicorocksDigitalAgency\Soap\Providers\SoapServiceProvider; +use RicorocksDigitalAgency\Soap\Tests\Mocks\MockSoapClient; abstract class TestCase extends OrchestraTestCase { const EXAMPLE_SOAP_ENDPOINT = "http://www.dneonline.com/calculator.asmx?WSDL"; + protected function fakeClient() + { + $this->app->bind(\SoapClient::class, MockSoapClient::class); + } + protected function getPackageProviders($app) { return [SoapServiceProvider::class]; diff --git a/tests/Tracing/SoapTracingTest.php b/tests/Tracing/SoapTracingTest.php new file mode 100644 index 0000000..45400a3 --- /dev/null +++ b/tests/Tracing/SoapTracingTest.php @@ -0,0 +1,49 @@ +fakeClient(); + } + + /** @test */ + public function a_trace_can_be_requested_at_time_of_request() + { + $response = Soap::to(static::EXAMPLE_SOAP_ENDPOINT) + ->trace() + ->call('Add', ['intA' => 10, 'intB' => 25]); + + $this->assertEquals('World', $response->trace()->xmlRequest); + $this->assertEquals('Success!', $response->trace()->xmlResponse); + } + + /** @test */ + public function a_trace_can_be_requested_globally() + { + Soap::trace(); + + $response = Soap::to(static::EXAMPLE_SOAP_ENDPOINT) + ->call('Add', ['intA' => 10, 'intB' => 25]); + + $this->assertEquals('World', $response->trace()->xmlRequest); + $this->assertEquals('Success!', $response->trace()->xmlResponse); + } + + /** @test */ + public function by_default_the_trace_has_no_content_on_the_response() + { + $response = Soap::to(static::EXAMPLE_SOAP_ENDPOINT) + ->call('Add', ['intA' => 10, 'intB' => 25]); + + $this->assertEmpty($response->trace()->xmlRequest); + $this->assertEmpty($response->trace()->xmlResponse); + } +} diff --git a/tests/Tracing/TraceTest.php b/tests/Tracing/TraceTest.php new file mode 100644 index 0000000..636439a --- /dev/null +++ b/tests/Tracing/TraceTest.php @@ -0,0 +1,48 @@ +World'; + + $trace = Trace::thisXmlRequest($xml); + + $this->assertSame($xml, $trace->xmlRequest); + } + + /** @test */ + public function the_trace_object_has_a_thisXmlResponse_method() + { + $request = 'World'; + $response = 'Success!'; + + $trace = Trace::thisXmlRequest($request)->thisXmlResponse($response); + + $this->assertSame($response, $trace->xmlResponse); + } + + /** @test */ + public function a_null_trace_returns_gracefully() + { + $trace = Trace::thisXmlRequest(null)->thisXmlResponse(null); + + $this->assertNull($trace->xmlRequest); + $this->assertNull($trace->xmlResponse); + } + + /** @test */ + public function a_fresh_trace_returns_gracefully() + { + $trace = new Trace; + + $this->assertNull($trace->xmlRequest); + $this->assertNull($trace->xmlResponse); + } +}