├── .gitignore ├── phpunit.xml ├── composer.json ├── LICENSE ├── .github └── workflows │ └── main.yaml ├── tests ├── unit │ ├── FactoryTest.php │ ├── example.wsdl │ └── SoapClientTest.php └── functional │ └── SoapClientTest.php ├── README.md └── src ├── SoapClient.php └── Factory.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .idea/ 3 | .DS_Store 4 | /build/ 5 | composer.lock 6 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests/unit 5 | 6 | 7 | 8 | 9 | src 10 | 11 | 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meng-tian/async-soap-guzzle", 3 | "description": "An asynchronous SOAP client build on top of Guzzle.", 4 | "keywords": ["SOAP", "asynchronous", "Guzzle"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Meng Tian", 9 | "email": "tianmeng94@hotmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.1.0", 14 | "meng-tian/php-async-soap": "~1.0", 15 | "meng-tian/soap-http-binding": "~0.4.0", 16 | "psr/http-factory": "~1.0", 17 | "guzzlehttp/guzzle": "^6.1 || ^7.0" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "~7.0|~9.3", 21 | "laminas/laminas-diactoros": "^2.0" 22 | }, 23 | "autoload": { 24 | "psr-4": {"Meng\\AsyncSoap\\Guzzle\\": "src/"} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Meng Tian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | operating-system: [ubuntu-latest] 10 | php-versions: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] 11 | 12 | runs-on: ${{ matrix.operating-system }} 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup PHP, with composer and extensions 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-versions }} 22 | 23 | - name: Get composer cache directory 24 | id: composer-cache 25 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 26 | 27 | - name: Cache composer dependencies 28 | uses: actions/cache@v2 29 | with: 30 | path: ${{ steps.composer-cache.outputs.dir }} 31 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} 32 | restore-keys: ${{ runner.os }}-composer- 33 | 34 | - name: Install Composer dependencies 35 | run: | 36 | composer install --no-progress --prefer-dist --optimize-autoloader 37 | 38 | - name: Run Tests 39 | run: vendor/bin/phpunit --coverage-text 40 | -------------------------------------------------------------------------------- /tests/unit/FactoryTest.php: -------------------------------------------------------------------------------- 1 | create(new Client(), new StreamFactory(), new RequestFactory(), null, ['uri'=>'', 'location'=>'']); 22 | 23 | $this->assertTrue($client instanceof SoapClient); 24 | } 25 | 26 | /** 27 | * @test 28 | */ 29 | public function wsdlFromHttpUrl() 30 | { 31 | $handlerMock = new MockHandler([ 32 | new Response('200', [], fopen(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'example.wsdl', 'r')) 33 | ]); 34 | $handler = new HandlerStack($handlerMock); 35 | $clientMock = new Client(['handler' => $handler]); 36 | 37 | $factory = new Factory(); 38 | $client = $factory->create($clientMock, new StreamFactory, new RequestFactory, 'http://www.mysite.com/wsdl'); 39 | 40 | $this->assertTrue($client instanceof SoapClient); 41 | } 42 | 43 | /** 44 | * @test 45 | */ 46 | public function wsdlFromLocalFile() 47 | { 48 | $factory = new Factory(); 49 | $client = $factory->create(new Client(), new StreamFactory(), new RequestFactory(), dirname(__FILE__) . DIRECTORY_SEPARATOR . 'example.wsdl'); 50 | 51 | $this->assertTrue($client instanceof SoapClient); 52 | } 53 | 54 | /** 55 | * @test 56 | */ 57 | public function wsdlFromDataUri() 58 | { 59 | $wsdlString = file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'example.wsdl'); 60 | $wsdl = 'data://text/plain;base64,' . base64_encode($wsdlString); 61 | 62 | $factory = new Factory(); 63 | $client = $factory->create(new Client(), new StreamFactory(), new RequestFactory(), $wsdl); 64 | 65 | $this->assertTrue($client instanceof SoapClient); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/unit/example.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 31 | 32 | 34 | 35 | 37 | 38 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | snowboarding-info.com Endorsement Service 51 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asynchronous SOAP client 2 | 3 | [![codecov.io](https://codecov.io/github/meng-tian/async-soap-guzzle/coverage.svg?branch=master)](https://codecov.io/github/meng-tian/async-soap-guzzle?branch=master) ![workflow](https://github.com/meng-tian/async-soap-guzzle/actions/workflows/main.yaml/badge.svg) 4 | 5 | An asynchronous SOAP client build on top of Guzzle. The `SoapClient` implements [meng-tian/php-async-soap](https://github.com/meng-tian/php-async-soap). 6 | 7 | ## Requirement 8 | PHP 7.1 --enablelibxml --enable-soap 9 | 10 | ## Install 11 | ``` 12 | composer require meng-tian/async-soap-guzzle 13 | ``` 14 | 15 | ## Usage 16 | From [v0.4.0](https://github.com/meng-tian/async-soap-guzzle/tree/v0.4.0) or newer, an instance of `Psr\Http\Message\RequestFactoryInterface` and an instance of `Psr\Http\Message\StreamFactoryInterface` need to be injected into `Meng\AsyncSoap\Guzzle\Factory`. These two interfaces are defined in [PSR-17](https://www.php-fig.org/psr/psr-17/) to create [PSR-7](https://www.php-fig.org/psr/psr-7/) compliant HTTP instances. This change will decouple this library from any specific implementation of PSR-7 and PSR-17. Clients can determine which implementation of PSR-17 they want to use. Plenty of different implementations of PSR17 can be found from [Packagist](https://packagist.org/?query=psr-17), e.g., `symfony/psr-http-message-bridge`, or `laminas/laminas-diactoros`. 17 | 18 | 1. Require this library and an implementation of PSR-17 in your `composer.json`: 19 | ```json 20 | ... 21 | "require": { 22 | "php": ">=7.1.0", 23 | "meng-tian/async-soap-guzzle": "~0.4.0", 24 | "laminas/laminas-diactoros": "^2.0" # this can be replaced by any implementation of PSR-17 25 | }, 26 | ... 27 | ``` 28 | 2. Run `composer install` 29 | 30 | 3. Create your async SOAP client and call your SOAP messages: 31 | ```php 32 | use GuzzleHttp\Client; 33 | use Meng\AsyncSoap\Guzzle\Factory; 34 | use Laminas\Diactoros\RequestFactory; 35 | use Laminas\Diactoros\StreamFactory; 36 | 37 | $factory = new Factory(); 38 | $client = $factory->create(new Client(), new StreamFactory(), new RequestFactory(), 'http://www.webservicex.net/Statistics.asmx?WSDL'); 39 | 40 | // async call 41 | $promise = $client->callAsync('GetStatistics', [['X' => [1,2,3]]]); 42 | $result = $promise->wait(); 43 | 44 | // sync call 45 | $result = $client->call('GetStatistics', [['X' => [1,2,3]]]); 46 | 47 | // magic method 48 | $promise = $client->GetStatistics(['X' => [1,2,3]]); 49 | $result = $promise->wait(); 50 | ``` 51 | 52 | ## License 53 | This library is released under [MIT](https://github.com/meng-tian/async-soap-guzzle/blob/master/LICENSE) license. 54 | -------------------------------------------------------------------------------- /src/SoapClient.php: -------------------------------------------------------------------------------- 1 | httpBindingPromise = $httpBindingPromise; 20 | $this->client = $client; 21 | } 22 | 23 | public function __call($name, $arguments) 24 | { 25 | return $this->callAsync($name, $arguments); 26 | } 27 | 28 | public function call($name, array $arguments, array $options = null, $inputHeaders = null, array &$outputHeaders = null) 29 | { 30 | $callPromise = $this->callAsync($name, $arguments, $options, $inputHeaders, $outputHeaders); 31 | return $callPromise->wait(); 32 | } 33 | 34 | public function callAsync($name, array $arguments, array $options = null, $inputHeaders = null, array &$outputHeaders = null) 35 | { 36 | return \GuzzleHttp\Promise\Coroutine::of( 37 | function () use ($name, $arguments, $options, $inputHeaders, &$outputHeaders) { 38 | /** @var HttpBinding $httpBinding */ 39 | $httpBinding = (yield $this->httpBindingPromise); 40 | $request = $httpBinding->request($name, $arguments, $options, $inputHeaders); 41 | $requestOptions = isset($options['request_options']) ? $options['request_options'] : []; 42 | 43 | try { 44 | $response = (yield $this->client->sendAsync($request, $requestOptions)); 45 | yield $this->interpretResponse($httpBinding, $response, $name, $outputHeaders); 46 | } catch (RequestException $exception) { 47 | if ($exception->hasResponse()) { 48 | $response = $exception->getResponse(); 49 | yield $this->interpretResponse($httpBinding, $response, $name, $outputHeaders); 50 | } else { 51 | throw $exception; 52 | } 53 | } finally { 54 | $request->getBody()->close(); 55 | } 56 | } 57 | ); 58 | } 59 | 60 | private function interpretResponse(HttpBinding $httpBinding, ResponseInterface $response, $name, &$outputHeaders) 61 | { 62 | try { 63 | return $httpBinding->response($response, $name, $outputHeaders); 64 | } finally { 65 | $response->getBody()->close(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | isHttpUrl($wsdl)) { 35 | $httpBindingPromise = $client->requestAsync('GET', $wsdl)->then( 36 | function (ResponseInterface $response) use ($streamFactory, $requestFactory, $options) { 37 | $wsdl = $response->getBody()->__toString(); 38 | $interpreter = new Interpreter('data://text/plain;base64,' . base64_encode($wsdl), $options); 39 | return new HttpBinding($interpreter, new RequestBuilder($streamFactory, $requestFactory), $streamFactory); 40 | } 41 | ); 42 | } else { 43 | $httpBindingPromise = new FulfilledPromise( 44 | new HttpBinding(new Interpreter($wsdl, $options), new RequestBuilder($streamFactory, $requestFactory), $streamFactory) 45 | ); 46 | } 47 | 48 | return new SoapClient($client, $httpBindingPromise); 49 | } 50 | 51 | private function isHttpUrl($wsdl) 52 | { 53 | return filter_var($wsdl, FILTER_VALIDATE_URL) !== false 54 | && in_array(parse_url($wsdl, PHP_URL_SCHEME), ['http', 'https']); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/functional/SoapClientTest.php: -------------------------------------------------------------------------------- 1 | factory = new Factory(); 14 | } 15 | 16 | /** 17 | * @test 18 | */ 19 | public function call() 20 | { 21 | $client = $this->factory->create( 22 | new Client(), 23 | 'http://www.webservicex.net/Statistics.asmx?WSDL' 24 | ); 25 | $response = $client->call('GetStatistics', [['X' => [1,2,3]]]); 26 | $this->assertNotEmpty($response); 27 | } 28 | 29 | /** 30 | * @test 31 | * @dataProvider webServicesProvider 32 | */ 33 | public function callAsync($wsdl, $options, $function, $args, $contains) 34 | { 35 | $client = $this->factory->create( 36 | new Client(), 37 | $wsdl, 38 | $options 39 | ); 40 | $response = $client->callAsync($function, $args)->wait(); 41 | $this->assertNotEmpty($response); 42 | foreach ($contains as $contain) { 43 | $this->assertArrayHasKey($contain, (array)$response); 44 | } 45 | } 46 | 47 | public function webServicesProvider() 48 | { 49 | return [ 50 | [ 51 | 'wsdl' => 'http://www.webservicex.net/Statistics.asmx?WSDL', 52 | 'options' => [], 53 | 'function' => 'GetStatistics', 54 | 'args' => [['X' => [1,2,3]]], 55 | 'contains' => [ 56 | 'Sums', 'Average', 'StandardDeviation', 'skewness', 'Kurtosis' 57 | ] 58 | ], 59 | [ 60 | 'wsdl' => 'http://www.webservicex.net/Statistics.asmx?WSDL', 61 | 'options' => ['soap_version' => SOAP_1_2], 62 | 'function' => 'GetStatistics', 63 | 'args' => [['X' => [1,2,3]]], 64 | 'contains' => [ 65 | 'Sums', 'Average', 'StandardDeviation', 'skewness', 'Kurtosis' 66 | ] 67 | ], 68 | [ 69 | 'wsdl' => 'http://www.webservicex.net/CurrencyConvertor.asmx?WSDL', 70 | 'options' => [], 71 | 'function' => 'ConversionRate', 72 | 'args' => [['FromCurrency' => 'GBP', 'ToCurrency' => 'USD']], 73 | 'contains' => [ 74 | 'ConversionRateResult' 75 | ] 76 | ], 77 | [ 78 | 'wsdl' => 'http://www.webservicex.net/CurrencyConvertor.asmx?WSDL', 79 | 'options' => ['soap_version' => SOAP_1_2], 80 | 'function' => 'ConversionRate', 81 | 'args' => [['FromCurrency' => 'GBP', 'ToCurrency' => 'USD']], 82 | 'contains' => [ 83 | 'ConversionRateResult' 84 | ] 85 | ], 86 | [ 87 | 'wsdl' => 'http://www.webservicex.net/bep.asmx?WSDL', 88 | 'options' => ['soap_version' => SOAP_1_1], 89 | 'function' => 'BreakEvenPoint', 90 | 'args' => [['FixedCost' => 1.1, 'VariableCost' => 1.2, 'ReturnsPerUnit' => 1.3]], 91 | 'contains' => [ 92 | 'BreakEvenPointResult' 93 | ] 94 | ], 95 | [ 96 | 'wsdl' => 'http://www.webservicex.net/bep.asmx?WSDL', 97 | 'options' => ['soap_version' => SOAP_1_2], 98 | 'function' => 'BreakEvenPoint', 99 | 'args' => [['FixedCost' => 1.1, 'VariableCost' => 1.2, 'ReturnsPerUnit' => 1.3]], 100 | 'contains' => [ 101 | 'BreakEvenPointResult' 102 | ] 103 | ], 104 | ]; 105 | } 106 | } -------------------------------------------------------------------------------- /tests/unit/SoapClientTest.php: -------------------------------------------------------------------------------- 1 | handlerMock = new MockHandler(); 36 | $handler = new HandlerStack($this->handlerMock); 37 | $this->client = new Client(['handler' => $handler]); 38 | 39 | $this->httpBindingMock = $this->getMockBuilder(HttpBinding::class) 40 | ->disableOriginalConstructor() 41 | ->setMethods(['request', 'response']) 42 | ->getMock(); 43 | } 44 | 45 | /** 46 | * @test 47 | */ 48 | public function magicCallDeferredHttpBindingRejected() 49 | { 50 | $this->httpBindingPromise = new RejectedPromise(new \Exception()); 51 | $this->httpBindingMock->expects($this->never())->method('request'); 52 | 53 | $client = new SoapClient($this->client, $this->httpBindingPromise); 54 | $this->expectException(\Exception::class); 55 | $client->someSoapMethod(['some-key' => 'some-value'])->wait(); 56 | } 57 | 58 | /** 59 | * @test 60 | */ 61 | public function magicCallHttpBindingFailed() 62 | { 63 | $this->httpBindingPromise = new FulfilledPromise($this->httpBindingMock); 64 | 65 | $this->httpBindingMock->method('request') 66 | ->will( 67 | $this->throwException(new RequestException()) 68 | ) 69 | ->with( 70 | 'someSoapMethod', [['some-key' => 'some-value']] 71 | ); 72 | 73 | $this->httpBindingMock->expects($this->never())->method('response'); 74 | 75 | $client = new SoapClient($this->client, $this->httpBindingPromise); 76 | $this->expectException(RequestException::class); 77 | $client->someSoapMethod(['some-key' => 'some-value'])->wait(); 78 | } 79 | 80 | /** 81 | * @test 82 | */ 83 | public function magicCall500Response() 84 | { 85 | $this->httpBindingPromise = new FulfilledPromise($this->httpBindingMock); 86 | 87 | $this->httpBindingMock->method('request') 88 | ->willReturn( 89 | new Request('POST', 'www.endpoint.com') 90 | ) 91 | ->with( 92 | 'someSoapMethod', [['some-key' => 'some-value']] 93 | ); 94 | 95 | $response = new Response('500'); 96 | $this->httpBindingMock->method('response') 97 | ->willReturn( 98 | 'SoapResult' 99 | ) 100 | ->with( 101 | $response, 'someSoapMethod', null 102 | ); 103 | 104 | $this->handlerMock->append(GuzzleRequestException::create(new Request('POST', 'www.endpoint.com'), $response)); 105 | 106 | $client = new SoapClient($this->client, $this->httpBindingPromise); 107 | $this->assertEquals('SoapResult', $client->someSoapMethod(['some-key' => 'some-value'])->wait()); 108 | } 109 | 110 | /** 111 | * @test 112 | */ 113 | public function magicCallResponseNotReceived() 114 | { 115 | $this->httpBindingPromise = new FulfilledPromise($this->httpBindingMock); 116 | 117 | $this->httpBindingMock->method('request') 118 | ->willReturn( 119 | new Request('POST', 'www.endpoint.com') 120 | ) 121 | ->with( 122 | 'someSoapMethod', [['some-key' => 'some-value']] 123 | ); 124 | 125 | $this->httpBindingMock->expects($this->never())->method('response'); 126 | 127 | $this->handlerMock->append(GuzzleRequestException::create(new Request('POST', 'www.endpoint.com'))); 128 | 129 | $client = new SoapClient($this->client, $this->httpBindingPromise); 130 | $this->expectException(GuzzleRequestException::class); 131 | $client->someSoapMethod(['some-key' => 'some-value'])->wait(); 132 | } 133 | 134 | /** 135 | * @test 136 | */ 137 | public function magicCallUndefinedResponse() 138 | { 139 | $this->httpBindingPromise = new FulfilledPromise($this->httpBindingMock); 140 | 141 | $this->httpBindingMock->method('request') 142 | ->willReturn( 143 | new Request('POST', 'www.endpoint.com') 144 | ) 145 | ->with( 146 | 'someSoapMethod', [['some-key' => 'some-value']] 147 | ); 148 | 149 | $this->httpBindingMock->expects($this->never())->method('response'); 150 | 151 | $this->handlerMock->append(new \Exception()); 152 | 153 | $client = new SoapClient($this->client, $this->httpBindingPromise); 154 | $this->expectException(\Exception::class); 155 | $client->someSoapMethod(['some-key' => 'some-value'])->wait(); 156 | 157 | } 158 | 159 | /** 160 | * @test 161 | */ 162 | public function magicCallClientReturnSoapFault() 163 | { 164 | $this->httpBindingPromise = new FulfilledPromise($this->httpBindingMock); 165 | 166 | $this->httpBindingMock->method('request') 167 | ->willReturn( 168 | new Request('POST', 'www.endpoint.com') 169 | ) 170 | ->with( 171 | 'someSoapMethod', [['some-key' => 'some-value']] 172 | ); 173 | 174 | $response = new Response('200', [], 'body'); 175 | $this->httpBindingMock->method('response') 176 | ->will( 177 | $this->throwException(new \SoapFault('soap fault', 'soap fault')) 178 | ) 179 | ->with( 180 | $response, 'someSoapMethod', null 181 | ); 182 | 183 | $this->handlerMock->append($response); 184 | 185 | $client = new SoapClient($this->client, $this->httpBindingPromise); 186 | $this->expectException(\SoapFault::class); 187 | $client->someSoapMethod(['some-key' => 'some-value'])->wait(); 188 | } 189 | 190 | /** 191 | * @test 192 | */ 193 | public function magicCallSuccess() 194 | { 195 | $this->httpBindingPromise = new FulfilledPromise($this->httpBindingMock); 196 | 197 | $this->httpBindingMock->method('request') 198 | ->willReturn( 199 | new Request('POST', 'www.endpoint.com') 200 | ) 201 | ->with( 202 | 'someSoapMethod', [['some-key' => 'some-value']] 203 | ); 204 | 205 | $response = new Response('200', [], 'body'); 206 | $this->httpBindingMock->method('response') 207 | ->willReturn( 208 | 'SoapResult' 209 | ) 210 | ->with( 211 | $response, 'someSoapMethod', null 212 | ); 213 | 214 | $this->handlerMock->append($response); 215 | 216 | $client = new SoapClient($this->client, $this->httpBindingPromise); 217 | $this->assertEquals('SoapResult', $client->someSoapMethod(['some-key' => 'some-value'])->wait()); 218 | } 219 | 220 | /** 221 | * @test 222 | */ 223 | public function resultsAreEquivalent() 224 | { 225 | $this->httpBindingPromise = new FulfilledPromise($this->httpBindingMock); 226 | 227 | $this->httpBindingMock->method('request') 228 | ->willReturn( 229 | new Request('POST', 'www.endpoint.com') 230 | ) 231 | ->with( 232 | 'someSoapMethod', [['some-key' => 'some-value']] 233 | ); 234 | 235 | $response = new Response('200', [], 'body'); 236 | $this->httpBindingMock->method('response')->willReturn( 237 | 'SoapResult' 238 | ); 239 | 240 | $this->handlerMock->append($response); 241 | $this->handlerMock->append($response); 242 | $this->handlerMock->append($response); 243 | 244 | $client = new SoapClient($this->client, $this->httpBindingPromise); 245 | $magicResult = $client->someSoapMethod(['some-key' => 'some-value'])->wait(); 246 | $syncResult = $client->call('someSoapMethod', [['some-key' => 'some-value']]); 247 | $asyncResult = $client->callAsync('someSoapMethod', [['some-key' => 'some-value']])->wait(); 248 | $this->assertEquals($magicResult, $asyncResult); 249 | $this->assertEquals($syncResult, $asyncResult); 250 | } 251 | } 252 | --------------------------------------------------------------------------------