├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── soap-client ├── composer.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── src ├── Builder │ └── SoapContainerBuilder.php ├── Client.php ├── ClientFactory.php ├── Command │ └── Generate.php ├── DependencyInjection │ ├── Configuration.php │ └── SoapClientExtension.php ├── Exception │ ├── ClientException.php │ ├── Fault11Exception.php │ ├── Fault12Exception.php │ ├── FaultException.php │ ├── ServerException.php │ ├── SoapException.php │ └── UnexpectedFormatException.php ├── Resources │ └── config │ │ └── services.xml ├── Result │ ├── ResultCreator.php │ └── ResultCreatorInterface.php └── StubGeneration │ ├── ClientStubGenerator.php │ └── Tag │ ├── MethodTag.php │ └── ParamTag.php └── tests ├── Client ├── BuildClientTest.php ├── Client11RequestResponsesTest.php ├── Client12RequestResponsesTest.php └── RequestResponsesTest.php ├── DependencyInjection └── ConfigurationTest.php ├── Fixtures ├── config.yml └── test.wsdl ├── Stub └── StubGeneratorTest.php ├── bootstrap.php └── example.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | # These are supported funding model platforms 3 | 4 | github: goetas 5 | patreon: goetas 6 | custom: https://www.goetas.com/ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor 3 | composer.lock 4 | soap 5 | /.phpunit.result.cache 6 | tmp 7 | .phpcs-cache 8 | config.yml 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | sudo: false 3 | cache: 4 | directories: 5 | - vendor 6 | - $HOME/.composer/cache 7 | git: 8 | depth: 1 9 | 10 | php: 11 | - 7.2 12 | - 7.3 13 | - 7.4 14 | - 8.0 15 | - 8.1 16 | 17 | matrix: 18 | include: 19 | - php: 7.2 20 | env: 21 | - COMPOSER_FLAGS='--prefer-lowest --prefer-stable' 22 | 23 | before_script: 24 | - phpenv config-rm xdebug.ini 25 | - composer self-update 26 | - composer update $COMPOSER_FLAGS 27 | 28 | script: 29 | - vendor/phpunit/phpunit/phpunit $PHPUNIT_FLAGS 30 | - bin/soap-client generate tests/Fixtures/config.yml soap/src/Container -vvv --dest-class=TestNs/Container/SoapClientContainer 31 | - php tests/example.php 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 Asmir Mustafic 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goetas-webservices / soap-client 2 | 3 | [![Build Status](https://travis-ci.org/goetas-webservices/soap-client.svg?branch=master)](https://travis-ci.org/goetas-webservices/soap-client) 4 | 5 | PHP implementation of SOAP 1.1 and 1.2 client specifications. 6 | 7 | Strengths: 8 | 9 | - Pure PHP, no dependencies on `ext-soap` 10 | - Extensible (JMS event listeners support) 11 | - PSR-7 HTTP messaging 12 | - PSR-17 HTTP messaging factories 13 | - PSR-18 HTTP Client 14 | - No WSDL/XSD parsing on production 15 | - IDE type hinting support 16 | 17 | Only document/literal style is supported and the webservice should follow 18 | the [WS-I](https://en.wikipedia.org/wiki/WS-I_Basic_Profile) guidelines. 19 | 20 | There are no plans to support the deprecated rpc and encoded styles. 21 | Webservices not following the WS-I specifications might work, but they are officially not supported. 22 | 23 | ## Demo 24 | 25 | [goetas-webservices/soap-client-demo](https://github.com/goetas-webservices/soap-client-demo) is a demo project 26 | that shows how to consume a SOAP api in a generic PHP web application. 27 | 28 | 29 | Installation 30 | ----------- 31 | 32 | The recommended way to install goetas-webservices / soap-client is using [Composer](https://getcomposer.org/): 33 | 34 | Add this packages to your `composer.json` file. 35 | 36 | ``` 37 | { 38 | "require": { 39 | "goetas-webservices/soap-client": "^0.3", 40 | }, 41 | "require-dev": { 42 | "goetas-webservices/wsdl2php": "^0.5.1", 43 | }, 44 | } 45 | ``` 46 | 47 | # How to 48 | 49 | To improve performance, this library is based on the concept that all the SOAP/WSDL 50 | metadata has to be compiled into PHP compatible metadata (in reality is a big plain PHP array, 51 | so is really fast). 52 | 53 | To do this we have to define a configuration file (in this case called `config.yml`) that 54 | holds some important information. 55 | 56 | Here is an example: 57 | 58 | ```yml 59 | # config.yml 60 | 61 | soap_client: 62 | alternative_endpoints: 63 | MyServiceName: 64 | MySoapPortName: http://localhost:8080/service 65 | 66 | namespaces: 67 | 'http://www.example.org/test/': 'TestNs/MyApp' 68 | destinations_php: 69 | 'TestNs/MyApp': soap/src 70 | destinations_jms: 71 | 'TestNs/MyApp': soap/metadata 72 | aliases: 73 | 'http://www.example.org/test/': 74 | MyCustomXSDType: 'MyCustomMappedPHPType' 75 | 76 | metadata: 77 | 'test.wsdl': ~ 78 | 'http://www.webservicex.net/weather.asmx?WSDL': ~ 79 | ``` 80 | 81 | This file has some important sections: 82 | 83 | ### SOAP Specific 84 | * `alternative_endpoints` (optional) allows you to specify alternative URLs that can be used 85 | when developing your integration. 86 | If this parameter is not present, will be used the URL defined by the WSDL file, 87 | but if is set, will be used the specified URL for the service called 88 | *MyServiceName* and on *MySoapPortName* port. 89 | 90 | 91 | * `unwrap_returns` (optional, default: *false*) allows to define the "wrapped" SOAP services mode. 92 | Instructs the client to "unwrap" all the returns. 93 | 94 | ### WSDL Specific 95 | 96 | * `metadata` specifies where are placed WSDL files that will be used to generate al the required PHP metadata. 97 | 98 | 99 | ### XML/XSD Specific 100 | 101 | * `namespaces` (required) defines the mapping between XML namespaces and PHP namespaces. 102 | (in the example we have the `http://www.example.org/test/` XML namespace mapped to `TestNs\MyApp`) 103 | 104 | 105 | * `destinations_php` (required) specifies the directory where to save the PHP classes that belongs to 106 | `TestNs\MyApp` PHP namespace. (in this example `TestNs\MyApp` classes will ne saved into `soap/src` directory. 107 | 108 | 109 | * `destinations_jms` (required) specifies the directory where to save JMS Serializer metadata files 110 | that belongs to `TestNs\MyApp` PHP namespace. 111 | (in this example `TestNs\MyApp` metadata will ne saved into `soap/metadata` directory. 112 | 113 | 114 | * `aliases` (optional) specifies some mappings that are handled by custom JMS serializer handlers. 115 | Allows to specify to do not generate metadata for some XML types, and assign them directly a PHP class. 116 | For that PHP class is necessary to create a custom JMS serialize/deserialize handler. 117 | 118 | 119 | 120 | ## Metadata generation 121 | 122 | In order to be able to use the SOAP client we have to generate some metadata and PHP classes. 123 | 124 | To do it we can run: 125 | 126 | ```sh 127 | bin/soap-client generate \ 128 | tests/config.yml \ 129 | --dest-class=GlobalWeather/Container/SoapClientContainer \ 130 | soap/src-gw/Container 131 | ``` 132 | 133 | 134 | * `bin/soap-client generate` is the command we are running 135 | * `tests/config.yml` is a path to our configuration file 136 | * `--dest-class=GlobalWeather/Container/SoapClientContainer` allows to specify the fully qualified class name of the 137 | container class that will hold all the webservice metadata. 138 | * `soap/src/Container` is the path where to save the container class that holds all the webservice metadata 139 | (you will have to configure the auto loader to load it) 140 | 141 | 142 | 143 | ## Using the client 144 | 145 | Once all the metadata are generated we can use our SOAP client. 146 | 147 | Let's see a minimal example: 148 | 149 | ```php 150 | // composer auto loader 151 | require __DIR__ . '/vendor/autoload.php'; 152 | 153 | // instantiate the main container class 154 | // the name was defined by --dest-class=GlobalWeather/Container/SoapClientContainer 155 | // parameter during the generation process 156 | $container = new SoapClientContainer(); 157 | 158 | // create a JMS serializer instance 159 | $serializer = SoapContainerBuilder::createSerializerBuilderFromContainer($container)->build(); 160 | // get the metadata from the container 161 | $metadata = $container->get('goetas_webservices.soap.metadata_reader'); 162 | 163 | $factory = new ClientFactory($metadata, $serializer); 164 | 165 | /** 166 | * @var $client \GlobalWeather\SoapStubs\WeatherSoap 167 | */ 168 | // get the soap client 169 | $client = $factory->getClient('http://www.webservicex.net/weather.asmx?WSDL'); 170 | 171 | // call the webservice 172 | $result = $client->getWeather(2010, "May", "USA"); 173 | 174 | // call the webservice with custom headers 175 | $result = $client->getWeather(2010, "May", "USA", Header::asMustUnderstand(new SomeAuth('me', 'pwd'))); 176 | 177 | 178 | ``` 179 | 180 | 181 | Please note the `@var $client \GlobalWeather\SoapStubs\WeatherSoap`. The generated metadata have also a "stub" class 182 | that allows modern IDE to give you type hinting for parameters and return data. 183 | 184 | This allows you to develop faster your client. 185 | 186 | ### Using the client with dynamic endpoints 187 | 188 | Suppose that you have same Webservice with different endpoints (ex. for each customer), 189 | so you want to change endpoints dynamically and you don't want to write each new endpoint in your config 190 | and run the generator for each customer. 191 | 192 | With the help of Symfony's `EnvVarProcessorInterface`, 193 | you can use `alternative_endpoints` to set dynamically the webservice endpoints. 194 | 195 | Here is an example: 196 | 197 | ```yml 198 | # config.yml 199 | soap_client: 200 | alternative_endpoints: 201 | MyServiceName: 202 | MySoapPortName: 'env(custom_vars:ENDPOINT_SERVICE1_PORT1)' 203 | ``` 204 | 205 | So, `SoapClientContainer` will resolve at runtime the endpoint for the specific service and port and the value will be 206 | taken from the `ENDPOINT_SERVICE1_PORT1` variable. 207 | 208 | Example of simple class that implements `EnvVarProcessorInterface`, responsible for providing a values for 209 | our custom endpoint locations (as `custom_vars:ENDPOINT_SERVICE1_PORT1`). 210 | 211 | ```php 212 | // SimpleEnvVarProcessor.php used for the `env(custom_vars:*)` variables resolution 213 | 214 | use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; 215 | 216 | class SimpleEnvVarProcessor implements EnvVarProcessorInterface 217 | { 218 | private $map = []; 219 | 220 | public function __construct(array $map) 221 | { 222 | $this->map = $map; 223 | } 224 | 225 | public function getEnv($prefix, $name, \Closure $getEnv) 226 | { 227 | return $this->map[$name]; 228 | } 229 | 230 | public static function getProvidedTypes() 231 | { 232 | return []; 233 | } 234 | } 235 | ``` 236 | 237 | At the end, to use the `SoapClientContainer`: 238 | 239 | 240 | ```php 241 | // instantiate our variable processor and set the values for our custom variables 242 | $varProcessor = new SimpleEnvVarProcessor([ 243 | 'ENDPOINT_SERVICE1_PORT1' => 'http://localhost:8080/service' 244 | ]); 245 | 246 | // create an empty symfony container and set into it the $varProcessor namined as 'custom_vars' 247 | $varContainer = new \Symfony\Component\DependencyInjection\Container(); 248 | $varContainer->set('custom_vars', $varProcessor); 249 | 250 | // create the soap container and use $varContainer "env()" style variables resolution 251 | $container = new SoapClientContainer(); 252 | $container->set('container.env_var_processors_locator', $varContainer); 253 | 254 | // now $container can be used as explained in the section "Using the client" 255 | ``` 256 | 257 | In this way the endpoint for the `MyServiceName`.`MySoapPortName` will be dynamically resolved to `http://localhost:8080/service` 258 | even if the WSDL stats something else. 259 | 260 | ## Note 261 | 262 | The code in this project is provided under the 263 | [MIT](https://opensource.org/licenses/MIT) license. 264 | For professional support 265 | contact [goetas@gmail.com](mailto:goetas@gmail.com) 266 | or visit [https://www.goetas.com](https://www.goetas.com) 267 | -------------------------------------------------------------------------------- /bin/soap-client: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setCatchExceptions(true); 17 | $cli->add(new \GoetasWebservices\SoapServices\SoapClient\Command\Generate()); 18 | $cli->run(); 19 | 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goetas-webservices/soap-client", 3 | "authors": [ 4 | { 5 | "name": "Asmir Mustafic", 6 | "email": "goetas@gmail.com" 7 | } 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^7.1|^8.0", 12 | 13 | "psr/http-message": "^1.0", 14 | "psr/http-factory": "^1.0", 15 | "psr/http-client": "^1.0", 16 | 17 | "psr/http-factory-implementation": "^1.0", 18 | "psr/http-message-implementation": "^1.0", 19 | "psr/http-client-implementation": "^1.0", 20 | 21 | "php-http/discovery": "^1.13", 22 | 23 | "symfony/dependency-injection": "^3.3|^4.0|^5.0", 24 | "doctrine/instantiator": "^1.0.3", 25 | "jms/serializer": "^1.6|^2.0|^3.0", 26 | 27 | "goetas-webservices/xsd2php-runtime": "^0.2.11", 28 | "goetas-webservices/soap-common": "^0.2.2", 29 | "goetas-webservices/soap-reader": "^0.3.3", 30 | "goetas-webservices/xsd2php": "^0.4.7", 31 | "laminas/laminas-code": "^4.8", 32 | "doctrine/inflector": "^2.0" 33 | }, 34 | "require-dev": { 35 | "phpunit/phpunit": "^7.0|^8.0", 36 | "goetas-webservices/wsdl2php": "^0.5.3", 37 | "laminas/laminas-diactoros": "^2.4", 38 | "guzzlehttp/guzzle": "^7.2", 39 | "doctrine/coding-standard": "^8.1" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "GoetasWebservices\\SoapServices\\SoapClient\\": "src" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "GoetasWebservices\\SoapServices\\SoapClient\\Tests\\": "tests", 49 | "GoetasWebservices\\Xsd\\XsdToPhp\\Tests\\": "vendor/goetas-webservices/xsd2php/tests", 50 | "GoetasWebservices\\WsdlToPhp\\Tests\\": "vendor/goetas-webservices/wsdl2php/tests" 51 | } 52 | }, 53 | "extra": { 54 | "branch-alias": { 55 | "dev-master": "0.3-dev" 56 | } 57 | }, 58 | "bin": [ 59 | "bin/soap-client" 60 | ], 61 | "config": { 62 | "allow-plugins": { 63 | "dealerdirect/phpcodesniffer-composer-installer": false 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | src/ 14 | tests/ 15 | /home/goetas/projects/soap-server/soap-metadata 16 | */tmp/* 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | tests/* 78 | 79 | 80 | 81 | tests/* 82 | 83 | 84 | 85 | 86 | 87 | 91 | 95 | 96 | 101 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | /home/goetas/projects/soap-server/soap-metadata/src/Envelope/* 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | /home/goetas/projects/soap-server/soap-metadata/src/Envelope/* 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | /home/goetas/projects/soap-server/soap-metadata/src/Envelope/* 139 | 140 | 141 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | ./tests 22 | 23 | 24 | 25 | 26 | 27 | src 28 | soap-metadata/src 29 | vendor/goetas-webservices/soap-common 30 | ../soap-metadata/src 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Builder/SoapContainerBuilder.php: -------------------------------------------------------------------------------- 1 | addExtension(new SoapClientExtension()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | serviceDefinition = $serviceDefinition; 90 | $this->serializer = $serializer; 91 | 92 | $this->client = $client; 93 | $this->messageFactory = $messageFactory; 94 | $this->streamFactory = $streamFactory; 95 | $this->argumentsReader = new ArgumentsReader($this->serializer); 96 | $this->resultCreator = new ResultCreator($this->serializer, !empty($serviceDefinition['unwrap'])); 97 | } 98 | 99 | public function setDebug(bool $debug): void 100 | { 101 | $this->debug = $debug; 102 | } 103 | 104 | private function extractHeaders(array $args): array 105 | { 106 | $headers = []; 107 | foreach ($args as $k => $arg) { 108 | if ($arg instanceof Header) { 109 | $headers[] = $arg; 110 | unset($args[$k]); 111 | } 112 | } 113 | 114 | return [$args, $headers]; 115 | } 116 | 117 | /** 118 | * @param array $args 119 | * 120 | * @return mixed 121 | * 122 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint 123 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint 124 | * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation 125 | */ 126 | public function __call(string $functionName, array $args) 127 | { 128 | [$args, $headers] = $this->extractHeaders($args); 129 | $soapOperation = $this->findOperation($functionName, $this->serviceDefinition); 130 | $message = $this->argumentsReader->readArguments($args, $soapOperation['input']); 131 | 132 | $bag = new HeadersOutgoing($headers); 133 | $context = SerializationContext::create()->setAttribute('headers_outgoing', $bag); 134 | $context->setAttribute('soapOperation', $soapOperation); 135 | $context->setAttribute('soapEndpoint', $this->serviceDefinition['endpoint']); 136 | 137 | $xmlMessage = $this->serializer->serialize($message, 'xml', $context); 138 | 139 | $requestMessage = $this->createRequestMessage($xmlMessage, $soapOperation); 140 | 141 | $responseMessage = $this->client->sendRequest($requestMessage); 142 | 143 | if ($this->debug) { 144 | $this->responseMessage = $responseMessage; 145 | } 146 | 147 | $this->assertValidResponse($responseMessage, $requestMessage); 148 | 149 | // fast return if no return is expected 150 | // @todo but we should still check for headers... 151 | if (!count($soapOperation['output']['parts'])) { 152 | return null; 153 | } 154 | 155 | if (200 !== $responseMessage->getStatusCode()) { 156 | throw $this->createFaultException($responseMessage, $requestMessage); 157 | } 158 | 159 | $body = (string) $responseMessage->getBody(); 160 | if (false !== strpos($body, ':Fault>')) { // some server returns a fault with 200 OK HTTP 161 | throw $this->createFaultException($responseMessage, $requestMessage); 162 | } 163 | 164 | $bag = new HeadersIncoming(); 165 | $context = DeserializationContext::create()->setAttribute('headers_incoming', $bag); 166 | $response = $this->serializer->deserialize($body, $soapOperation['output']['message_fqcn'], 'xml', $context); 167 | 168 | return $this->resultCreator->prepareResult($response, $soapOperation['output']); 169 | } 170 | 171 | public function assertValidResponse(ResponseInterface $responseMessage, RequestInterface $requestMessage, ?\Throwable $previous = null): void 172 | { 173 | if (false === strpos($responseMessage->getHeaderLine('Content-Type'), 'xml')) { 174 | throw new UnexpectedFormatException( 175 | "Unexpected content type '" . $responseMessage->getHeaderLine('Content-Type') . "'", 176 | $responseMessage, 177 | $requestMessage, 178 | $previous 179 | ); 180 | } 181 | 182 | $body = (string) $responseMessage->getBody(); 183 | 184 | if (false === strpos($body, 'http://schemas.xmlsoap.org/soap/envelope/') && false === strpos($body, 'http://www.w3.org/2003/05/soap-envelope')) { 185 | throw new UnexpectedFormatException( 186 | 'Unexpected content, invalid SOAP message', 187 | $responseMessage, 188 | $requestMessage, 189 | $previous 190 | ); 191 | } 192 | } 193 | 194 | private function createFaultException(ResponseInterface $response, RequestInterface $request): FaultException 195 | { 196 | [$faultException, $faultClass] = $this->findFaultClass($response); 197 | 198 | $fault = null; 199 | if (null !== $faultClass) { 200 | $bag = new HeadersIncoming(); 201 | $context = DeserializationContext::create()->setAttribute('headers_incoming', $bag); 202 | $fault = $this->serializer->deserialize((string) $response->getBody(), $faultClass, 'xml', $context); 203 | } 204 | 205 | return $faultException::createFromResponse($response, $request, $fault); 206 | } 207 | 208 | /** 209 | * @return string[] 210 | */ 211 | protected function findFaultClass(ResponseInterface $response): array 212 | { 213 | if (false === strpos((string) $response->getBody(), ':Fault>')) { 214 | return [FaultException::class, null]; 215 | } 216 | 217 | if (false !== strpos($response->getHeaderLine('Content-Type'), 'application/soap+xml')) { 218 | return [Fault12Exception::class, Fault12::class]; 219 | } 220 | 221 | if (false !== strpos($response->getHeaderLine('Content-Type'), 'xml')) { 222 | return [Fault11Exception::class, Fault11::class]; 223 | } 224 | } 225 | 226 | public function __getLastRequestMessage(): ?RequestInterface 227 | { 228 | return $this->requestMessage; 229 | } 230 | 231 | public function __getLastResponseMessage(): ?ResponseInterface 232 | { 233 | return $this->responseMessage; 234 | } 235 | 236 | /** 237 | * @return string[] 238 | */ 239 | protected function buildHeaders(array $operation): array 240 | { 241 | return [ 242 | 'Content-Type' => isset($operation['version']) && '1.2' === $operation['version'] 243 | ? 'application/soap+xml; charset=utf-8' 244 | : 'text/xml; charset=utf-8', 245 | 'SoapAction' => '"' . $operation['action'] . '"', 246 | ]; 247 | } 248 | 249 | protected function findOperation(string $functionName, array $serviceDefinition): array 250 | { 251 | if (isset($serviceDefinition['operations'][$functionName])) { 252 | return $serviceDefinition['operations'][$functionName]; 253 | } 254 | 255 | foreach ($serviceDefinition['operations'] as $operation) { 256 | if (strtolower($functionName) === strtolower($operation['method'])) { 257 | return $operation; 258 | } 259 | } 260 | 261 | throw new ClientException(sprintf('Can not find an operation to run %s service call', $functionName)); 262 | } 263 | 264 | private function createRequestMessage(string $xmlMessage, array $soapOperation): RequestInterface 265 | { 266 | $headers = $this->buildHeaders($soapOperation); 267 | $requestMessage = $this->messageFactory 268 | ->createRequest('POST', $this->serviceDefinition['endpoint']) 269 | ->withBody($this->streamFactory->createStream($xmlMessage)); 270 | foreach ($headers as $name => $val) { 271 | $requestMessage = $requestMessage->withHeader($name, $val); 272 | } 273 | 274 | if ($this->debug) { 275 | $this->requestMessage = $requestMessage; 276 | } 277 | 278 | return $requestMessage; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/ClientFactory.php: -------------------------------------------------------------------------------- 1 | setMetadataReader($reader); 45 | $this->setSerializer($serializer); 46 | } 47 | 48 | public function setHttpClient(ClientInterface $client): void 49 | { 50 | $this->httpClient = $client; 51 | } 52 | 53 | public function setMessageFactory(RequestFactoryInterface $messageFactory): void 54 | { 55 | $this->messageFactory = $messageFactory; 56 | } 57 | 58 | public function setSerializer(SerializerInterface $serializer): void 59 | { 60 | $this->serializer = $serializer; 61 | } 62 | 63 | public function setMetadataReader(MetadataLoaderInterface $reader): void 64 | { 65 | $this->reader = $reader; 66 | } 67 | 68 | private function getSoapService(string $wsdl, ?string $portName = null, ?string $serviceName = null): array 69 | { 70 | $servicesMetadata = $this->reader->load($wsdl); 71 | $service = MetadataUtils::getService($serviceName, $servicesMetadata); 72 | 73 | return MetadataUtils::getPort($portName, $service); 74 | } 75 | 76 | public function getClient(string $wsdl, ?string $portName = null, ?string $serviceName = null): Client 77 | { 78 | $service = $this->getSoapService($wsdl, $portName, $serviceName); 79 | 80 | $this->httpClient = $this->httpClient ?: Psr18ClientDiscovery::find(); 81 | $this->messageFactory = $this->messageFactory ?: Psr17FactoryDiscovery::findRequestFactory(); 82 | $this->streamFactory = $this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(); 83 | 84 | return new Client($service, $this->serializer, $this->messageFactory, $this->streamFactory, $this->httpClient); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Command/Generate.php: -------------------------------------------------------------------------------- 1 | setName('generate'); 21 | $this->setDescription('Create all the necessary PHP classes for a SOAP client'); 22 | $this->setDefinition([ 23 | new InputArgument('config', InputArgument::REQUIRED, 'Config file location'), 24 | new InputArgument('dest-dir', InputArgument::REQUIRED, 'Container files destination directory'), 25 | new InputOption('dest-class', null, InputOption::VALUE_REQUIRED, 'Container class file destination directory'), 26 | ]); 27 | } 28 | 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $logger = new ConsoleLogger($output); 32 | $containerBuilder = new SoapContainerBuilder($input->getArgument('config'), $logger); 33 | 34 | if ($input->getOption('dest-class')) { 35 | $containerBuilder->setContainerClassName($input->getOption('dest-class')); 36 | } 37 | 38 | $debugContainer = $containerBuilder->getDebugContainer(); 39 | //$debugContainer->set('logger', $logger); 40 | 41 | $wsdlMetadata = $debugContainer->getParameter('goetas_webservices.soap.config')['metadata']; 42 | 43 | $schemas = []; 44 | $portTypes = []; 45 | $wsdlReader = $debugContainer->get('goetas_webservices.wsdl2php.wsdl_reader'); 46 | 47 | foreach (array_keys($wsdlMetadata) as $src) { 48 | $definitions = $wsdlReader->readFile($src); 49 | $schemas[] = $definitions->getSchema(); 50 | $portTypes = array_merge($portTypes, $definitions->getAllPortTypes()); 51 | } 52 | 53 | $soapReader = $debugContainer->get('goetas_webservices.wsdl2php.soap_reader'); 54 | $soapServices = $soapReader->getServices(); 55 | 56 | foreach (['php', 'jms'] as $type) { 57 | $converter = $debugContainer->get('goetas_webservices.xsd2php.converter.' . $type); 58 | $wsdlConverter = $debugContainer->get('goetas_webservices.wsdl2php.converter.' . $type); 59 | $items = $wsdlConverter->visitServices($soapServices); 60 | $items = array_merge($items, $converter->convert($schemas)); 61 | 62 | $writer = $debugContainer->get('goetas_webservices.xsd2php.writer.' . $type); 63 | $writer->write($items); 64 | } 65 | 66 | $containerBuilder->dumpContainerForProd($input->getArgument('dest-dir'), $debugContainer); 67 | 68 | $stubGenerator = $debugContainer->get('goetas_webservices.soap.stub.client_generator'); 69 | 70 | $classDefinitions = $stubGenerator->generate($portTypes); 71 | $classWriter = $debugContainer->get('goetas_webservices.xsd2php.class_writer.php'); 72 | $classWriter->write($classDefinitions); 73 | 74 | return 0; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 23 | } else { 24 | $rootNode = $treeBuilder->root('soap_client'); 25 | } 26 | 27 | $rootNode 28 | ->children() 29 | ->booleanNode('strict_types')->defaultFalse()->end() 30 | ->arrayNode('alternative_endpoints')->fixXmlConfig('alternative_endpoint') 31 | ->prototype('array') 32 | ->prototype('scalar') 33 | ->end() 34 | ->end() 35 | ->end() 36 | ->scalarNode('unwrap_returns') 37 | ->defaultValue(false) 38 | ->end() 39 | ->scalarNode('naming_strategy') 40 | ->defaultValue('short') 41 | ->cannotBeEmpty() 42 | ->end() 43 | ->scalarNode('path_generator') 44 | ->defaultValue('psr4') 45 | ->cannotBeEmpty() 46 | ->end() 47 | ->arrayNode('namespaces')->fixXmlConfig('namespace') 48 | ->cannotBeEmpty()->isRequired() 49 | ->requiresAtLeastOneElement() 50 | ->prototype('scalar') 51 | ->end() 52 | ->end() 53 | ->arrayNode('known_locations')->fixXmlConfig('known_location') 54 | ->prototype('scalar') 55 | ->end() 56 | ->end() 57 | ->arrayNode('destinations_php')->fixXmlConfig('destination_php') 58 | ->cannotBeEmpty()->isRequired() 59 | ->requiresAtLeastOneElement() 60 | ->prototype('scalar') 61 | ->end() 62 | ->end() 63 | ->arrayNode('destinations_jms')->fixXmlConfig('destination_jms') 64 | ->cannotBeEmpty()->isRequired() 65 | ->requiresAtLeastOneElement() 66 | ->prototype('scalar') 67 | ->end() 68 | ->end() 69 | ->arrayNode('aliases')->fixXmlConfig('alias') 70 | ->prototype('array') 71 | ->prototype('scalar') 72 | ->end() 73 | ->end() 74 | ->end() 75 | ->arrayNode('metadata')->fixXmlConfig('metadata') 76 | ->cannotBeEmpty()->isRequired() 77 | ->requiresAtLeastOneElement() 78 | ->prototype('scalar')->end() 79 | ->end() 80 | ->end(); 81 | 82 | return $treeBuilder; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/DependencyInjection/SoapClientExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration(new Configuration(), $configs); 20 | foreach ($configs as $subConfig) { 21 | $config = array_merge($config, $subConfig); 22 | } 23 | 24 | $container->setParameter('goetas_webservices.soap.config', $config); 25 | $container->setParameter('goetas_webservices.soap.unwrap_returns', $config['unwrap_returns']); 26 | $container->setParameter('goetas_webservices.soap.strict_types', $config['strict_types']); 27 | 28 | $xml = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 29 | $xml->load('services.xml'); 30 | 31 | $container->setDefinition('logger', new Definition(NullLogger::class)); 32 | 33 | $definition = $container->getDefinition('goetas_webservices.xsd2php.path_generator.jms.' . $config['path_generator']); 34 | $container->setDefinition('goetas_webservices.xsd2php.path_generator.jms', clone $definition); 35 | 36 | $definition = $container->getDefinition('goetas_webservices.xsd2php.path_generator.php.' . $config['path_generator']); 37 | $container->setDefinition('goetas_webservices.xsd2php.path_generator.php', clone $definition); 38 | 39 | $pathGenerator = $container->getDefinition('goetas_webservices.xsd2php.path_generator.jms'); 40 | $pathGenerator->addMethodCall('setTargets', [$config['destinations_jms']]); 41 | 42 | $pathGenerator = $container->getDefinition('goetas_webservices.xsd2php.path_generator.php'); 43 | $pathGenerator->addMethodCall('setTargets', [$config['destinations_php']]); 44 | 45 | foreach (['php', 'jms'] as $type) { 46 | $converter = $container->getDefinition('goetas_webservices.xsd2php.converter.' . $type); 47 | foreach ($config['namespaces'] as $xml => $php) { 48 | $converter->addMethodCall('addNamespace', [$xml, self::sanitizePhp($php)]); 49 | } 50 | 51 | foreach ($config['aliases'] as $xml => $data) { 52 | foreach ($data as $type => $php) { 53 | $converter->addMethodCall('addAliasMapType', [$xml, $type, self::sanitizePhp($php)]); 54 | } 55 | } 56 | } 57 | 58 | $definition = $container->getDefinition('goetas_webservices.xsd2php.naming_convention.' . $config['naming_strategy']); 59 | $container->setDefinition('goetas_webservices.xsd2php.naming_convention', $definition); 60 | 61 | ////////// 62 | 63 | $metadataGenerator = $container->getDefinition('goetas_webservices.soap.metadata.generator'); 64 | foreach ($config['alternative_endpoints'] as $service => $data) { 65 | foreach ($data as $port => $endPoint) { 66 | $metadataGenerator->addMethodCall('addAlternativeEndpoint', [$service, $port, $endPoint]); 67 | } 68 | } 69 | 70 | //$metadataGenerator->addMethodCall('setBaseNs', [array_intersect_key($config, array_combine($keys, $keys))]); 71 | $metadataGenerator->addMethodCall('setUnwrap', [$config['unwrap_returns']]); 72 | $metadataGenerator->replaceArgument(1, $config['namespaces']); 73 | 74 | $writer = $container->getDefinition('goetas_webservices.soap.stub.client_generator'); 75 | $writer->addMethodCall('setUnwrap', [$config['unwrap_returns']]); 76 | 77 | $forProduction = !!$container->getParameter('goetas_webservices.soap.metadata'); 78 | 79 | $readerName = 'goetas_webservices.soap.metadata_loader.' . ($forProduction ? 'array' : 'dev'); 80 | $alias = $container->setAlias('goetas_webservices.soap.metadata_reader', $readerName); 81 | if ($alias instanceof Alias) { 82 | $alias->setPublic(true); 83 | } 84 | } 85 | 86 | protected static function sanitizePhp(string $ns): string 87 | { 88 | return strtr($ns, '/', '\\'); 89 | } 90 | 91 | public function getAlias(): string 92 | { 93 | return 'soap_client'; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Exception/ClientException.php: -------------------------------------------------------------------------------- 1 | getBody()->getFault()->getString(); 21 | 22 | parent::__construct($message, $response, $request, $previous); 23 | $this->fault = $fault; 24 | } 25 | 26 | public function getFault(): Fault 27 | { 28 | return $this->fault; 29 | } 30 | 31 | public static function createFromResponse(ResponseInterface $response, RequestInterface $request, Fault $fault): FaultException 32 | { 33 | return new self($fault, $response, $request); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Exception/Fault12Exception.php: -------------------------------------------------------------------------------- 1 | getBody()->getFault()->getReason()); 21 | 22 | parent::__construct($message, $response, $request, $previous); 23 | $this->fault = $fault; 24 | } 25 | 26 | public function getFault(): Fault 27 | { 28 | return $this->fault; 29 | } 30 | 31 | public static function createFromResponse(ResponseInterface $response, RequestInterface $request, Fault $fault): FaultException 32 | { 33 | return new self($fault, $response, $request); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Exception/FaultException.php: -------------------------------------------------------------------------------- 1 | response = $response; 26 | $this->request = $request; 27 | } 28 | 29 | public function getRequest(): RequestInterface 30 | { 31 | return $this->request; 32 | } 33 | 34 | public function getResponse(): ResponseInterface 35 | { 36 | return $this->response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Exception/SoapException.php: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | FALSE 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | %goetas_webservices.soap.metadata% 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | %goetas_webservices.soap.unwrap_returns% 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 66 | 67 | 70 | 71 | 72 | %goetas_webservices.soap.unwrap_returns% 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 94 | %goetas_webservices.soap.strict_types% 95 | 96 | 97 | 100 | 101 | 102 | 103 | 104 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 119 | 120 | 121 | 122 | 123 | 126 | 127 | 128 | 129 | 132 | 133 | 134 | 135 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/Result/ResultCreator.php: -------------------------------------------------------------------------------- 1 | serializer = $serializer; 25 | $this->unwrap = $unwrap; 26 | } 27 | 28 | /** 29 | * @return mixed 30 | */ 31 | private function getPropertyValue(PropertyMetadata $propertyMetadata, object $object) 32 | { 33 | $reflectionProperty = new \ReflectionProperty($propertyMetadata->class, $propertyMetadata->name); 34 | $reflectionProperty->setAccessible(true); 35 | 36 | return $reflectionProperty->getValue($object); 37 | } 38 | 39 | /** 40 | * @return mixed 41 | */ 42 | public function prepareResult(object $object, array $output) 43 | { 44 | if (!count($output['parts'])) { 45 | return null; 46 | } 47 | 48 | $factory = SerializerUtils::getMetadataFactory($this->serializer); 49 | 50 | $classMetadata = $factory->getMetadataForClass($output['message_fqcn']); 51 | $bodyMetadata = $classMetadata->propertyMetadata['body']; 52 | $bodyClassMetadata = $factory->getMetadataForClass($bodyMetadata->type['name']); 53 | $body = $this->getPropertyValue($bodyMetadata, $object); 54 | $parts = []; 55 | foreach ($bodyClassMetadata->propertyMetadata as $propertyMetadata) { 56 | $parts[$propertyMetadata->name] = $this->getPropertyValue($propertyMetadata, $body); 57 | } 58 | 59 | if (count($output['parts']) > 1) { 60 | return $parts; 61 | } else { 62 | if ($this->unwrap) { 63 | foreach ($bodyClassMetadata->propertyMetadata as $propertyMetadata) { 64 | $propClassMetadata = $factory->getMetadataForClass($propertyMetadata->type['name']); 65 | 66 | if (count($propClassMetadata->propertyMetadata) > 1) { 67 | throw new \Exception('When using wrapped mode, the wrapped object can not have multiple properties'); 68 | } 69 | 70 | if (!count($propClassMetadata->propertyMetadata)) { 71 | return null; 72 | } 73 | 74 | $propertyMetadata = reset($propClassMetadata->propertyMetadata); 75 | 76 | return $this->getPropertyValue($propertyMetadata, reset($parts)); 77 | } 78 | } 79 | 80 | return reset($parts); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Result/ResultCreatorInterface.php: -------------------------------------------------------------------------------- 1 | namingStrategy = $namingStrategy; 42 | $this->phpConverter = $phpConverter; 43 | $this->unwrapReturn = $unwrapReturn; 44 | $this->inflector = InflectorFactory::create()->build(); 45 | } 46 | 47 | public function setUnwrap(bool $mode = true): void 48 | { 49 | $this->unwrapReturn = $mode; 50 | } 51 | 52 | /** 53 | * @param PortType[] $ports 54 | */ 55 | public function generate(array $ports): array 56 | { 57 | $classes = []; 58 | foreach ($ports as $port) { 59 | $class = new ClassGenerator(); 60 | if (false !== $this->visitPortType($class, $port)) { 61 | $classes[] = $class; 62 | } 63 | } 64 | 65 | return $classes; 66 | } 67 | 68 | private function visitPortType(ClassGenerator $class, PortType $portType): ?bool 69 | { 70 | if (!count($portType->getOperations())) { 71 | return false; 72 | } 73 | 74 | $docBlock = new DocBlockGenerator('Class representing ' . $portType->getName()); 75 | $docBlock->setWordWrap(false); 76 | if ($portType->getDocumentation()) { 77 | $docBlock->setLongDescription($portType->getDocumentation()); 78 | } 79 | 80 | $namespaces = $this->phpConverter->getNamespaces(); 81 | $class->setNamespaceName($namespaces[$portType->getDefinition()->getTargetNamespace()] . '\\SoapStubs'); 82 | $class->setName($this->inflector->classify($portType->getName())); 83 | $class->setDocblock($docBlock); 84 | 85 | foreach ($portType->getOperations() as $operation) { 86 | $operationTag = $this->visitOperation($operation); 87 | $docBlock->setTag($operationTag); 88 | } 89 | 90 | return null; 91 | } 92 | 93 | private function visitOperation(PortType\Operation $operation): MethodTag 94 | { 95 | $types = $this->getOperationReturnTypes($operation); 96 | $operationTag = new MethodTag( 97 | $this->inflector->camelize($operation->getName()), 98 | $types, 99 | preg_replace("/[\n\r]+/", ' ', $operation->getDocumentation()) 100 | ); 101 | $params = $this->getOperationParams($operation); 102 | $params[] = $param = new ParamTag('header', ['\\' . Header::class]); 103 | $param->setVaridic(); 104 | 105 | $operationTag->setParams($params); 106 | 107 | return $operationTag; 108 | } 109 | 110 | private function getOperationParams(PortType\Operation $operation): array 111 | { 112 | if (!$operation->getInput()) { 113 | return []; 114 | } 115 | 116 | $parts = $operation->getInput()->getMessage()->getParts(); 117 | if (!$parts) { 118 | return []; 119 | } 120 | 121 | if (count($parts) > 1) { 122 | $params = []; 123 | foreach ($parts as $part) { 124 | $partName = $this->namingStrategy->getPropertyName($part); 125 | $class = $this->getClassFromPart($part); 126 | 127 | $typeName = $class->getPhpType(); 128 | if ($t = $class->isSimpleType()) { 129 | $typeName = $t->getType()->getPhpType(); 130 | } 131 | 132 | $params[] = $param = new ParamTag($partName, [$typeName]); 133 | } 134 | } else { 135 | $params = $this->extractSinglePartParameters(reset($parts)); 136 | } 137 | 138 | return $params; 139 | } 140 | 141 | private function getOperationReturnTypes(PortType\Operation $operation): array 142 | { 143 | if (!$operation->getOutput() || !$operation->getOutput()->getMessage()->getParts()) { 144 | return ['void']; 145 | } 146 | 147 | $parts = $operation->getOutput()->getMessage()->getParts(); 148 | if (count($parts) > 1) { 149 | return ['array']; 150 | } 151 | 152 | /** 153 | * @var $part \GoetasWebservices\XML\WSDLReader\Wsdl\Message\Part 154 | */ 155 | $part = reset($parts); 156 | 157 | $class = $this->getClassFromPart($part); 158 | if ($this->unwrapReturn) { 159 | foreach ($class->getProperties() as $property) { 160 | $propertyClass = $property->getType(); 161 | if ($t = $propertyClass->isSimpleType()) { 162 | return [$t->getType()->getPhpType()]; 163 | } 164 | 165 | return [$propertyClass->getPhpType()]; 166 | } 167 | } 168 | 169 | if ($t = $class->isSimpleType()) { 170 | return [$t->getType()->getPhpType()]; 171 | } 172 | 173 | return [$class->getPhpType()]; 174 | } 175 | 176 | private function getClassFromPart(Part $part): PHPClass 177 | { 178 | if ($part->getType()) { 179 | return $this->phpConverter->visitType($part->getType()); 180 | } else { 181 | return $this->phpConverter->visitElementDef($part->getElement()); 182 | } 183 | } 184 | 185 | /** 186 | * @return array 187 | */ 188 | private function extractSinglePartParameters(Part $part): array 189 | { 190 | $params = []; 191 | $class = $this->getClassFromPart($part); 192 | 193 | foreach ($class->getPropertiesInHierarchy() as $property) { 194 | $t = $property->getType()->getPhpType(); 195 | $params[] = $param = new ParamTag($property->getName(), [$t]); 196 | } 197 | 198 | return $params; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/StubGeneration/Tag/MethodTag.php: -------------------------------------------------------------------------------- 1 | params = $params; 27 | } 28 | 29 | public function generateParams(): string 30 | { 31 | $params = array_map(static function (ParamTag $paramTag) { 32 | if ($paramTag instanceof SoapParamTag) { 33 | return $paramTag->generateForMethod(); 34 | } 35 | 36 | return (!empty($paramTag->getTypes()) ? $paramTag->getTypesAsString() : '') 37 | . (!empty($paramTag->getVariableName()) ? ' $' . $paramTag->getVariableName() : ''); 38 | }, $this->params); 39 | 40 | return implode(', ', $params); 41 | } 42 | 43 | public function generate(): string 44 | { 45 | $params = $this->generateParams(); 46 | 47 | return '@method' 48 | . ($this->isStatic ? ' static' : '') 49 | . (!empty($this->types) ? ' ' . $this->getTypesAsString() : '') 50 | . (!empty($this->methodName) ? ' ' . $this->methodName . '(' . $params . ')' : '') 51 | . (!empty($this->description) ? ' ' . $this->description : ''); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/StubGeneration/Tag/ParamTag.php: -------------------------------------------------------------------------------- 1 | default = $default; 30 | } 31 | 32 | public function setVaridic(): void 33 | { 34 | $this->varidic = true; 35 | } 36 | 37 | public function generate(): string 38 | { 39 | return '@param' 40 | . (!empty($this->types) ? ' ' . $this->getTypesAsString() : '') 41 | . (!empty($this->variableName) ? ' $' . $this->variableName : '') 42 | . (!empty($this->default) ? ' = ' . $this->default : '') 43 | . (!empty($this->description) ? ' ' . $this->description : ''); 44 | } 45 | 46 | public function generateForMethod(): string 47 | { 48 | return (!empty($this->types) ? $this->getTypesAsString() : '') 49 | . (!empty($this->variableName) ? ' ' . ($this->varidic ? '...' : '') . '$' . $this->variableName : '') 50 | . (!empty($this->default) ? ' = ' . $this->default : ''); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Client/BuildClientTest.php: -------------------------------------------------------------------------------- 1 | 'Ex']; 28 | $generator = new Generator($namespaces); 29 | $serializer = $generator->buildSerializer(); 30 | 31 | $naming = new ShortNamingStrategy(); 32 | $metadataGenerator = new MetadataGenerator($naming, $namespaces); 33 | 34 | $dispatcher = new EventDispatcher(); 35 | $wsdlReader = new DefinitionsReader(null, $dispatcher); 36 | $soapReader = new SoapReader(); 37 | $dispatcher->addSubscriber($soapReader); 38 | 39 | $metadataLoader = new DevMetadataLoader($metadataGenerator, $soapReader, $wsdlReader); 40 | $this->factory = new ClientFactory($metadataLoader, $serializer); 41 | } 42 | 43 | public function testBuildServer(): void 44 | { 45 | $client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl'); 46 | $this->assertNotNull($client); 47 | } 48 | 49 | public function testGetService(): void 50 | { 51 | $client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP'); 52 | $this->assertNotNull($client); 53 | } 54 | 55 | public function testGetWrongPort(): void 56 | { 57 | $this->expectException(PortNotFoundException::class); 58 | $this->expectExceptionMessage('The port named XXX can not be found'); 59 | $client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'XXX'); 60 | $this->assertNotNull($client); 61 | } 62 | 63 | public function testGetWrongService(): void 64 | { 65 | $this->expectException(PortNotFoundException::class); 66 | $this->expectExceptionMessage('The port named testSOAP can not be found'); 67 | $client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP', 'alternativeTest'); 68 | $this->assertNotNull($client); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Client/Client11RequestResponsesTest.php: -------------------------------------------------------------------------------- 1 | factory->getClient(__DIR__ . '/../Fixtures/test.wsdl'); 28 | } 29 | 30 | public function testGetLastRequestMessage(): void 31 | { 32 | $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' 33 | 34 | 35 | 36 | 37 | 38 | 39 | '); 40 | 41 | $this->responseMock->append($httpResponse); 42 | 43 | $this->client->setDebug(true); 44 | $this->assertNull($this->client->__getLastRequestMessage()); 45 | 46 | /** 47 | * @var $response \Ex\GetSimpleResponse 48 | */ 49 | $response = $this->client->getSimple('foo'); 50 | $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $this->client->__getLastRequestMessage()); 51 | $this->assertXmlStringEqualsXmlString( 52 | ' 53 | 54 | 55 | 56 | 57 | 58 | ', 59 | (string) $this->client->__getLastRequestMessage()->getBody() 60 | ); 61 | } 62 | 63 | public function testGetLastResponseMessage(): void 64 | { 65 | $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' 66 | 67 | 68 | 69 | 70 | 71 | 72 | '); 73 | 74 | $this->responseMock->append($httpResponse); 75 | 76 | $this->client->setDebug(true); 77 | $this->assertNull($this->client->__getLastResponseMessage()); 78 | 79 | /** 80 | * @var $response \Ex\GetSimpleResponse 81 | */ 82 | $response = $this->client->getSimple('foo'); 83 | $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $this->client->__getLastResponseMessage()); 84 | $this->assertSame($httpResponse, $this->client->__getLastResponseMessage()); 85 | } 86 | 87 | public function testGetSimple(): void 88 | { 89 | $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' 90 | 91 | 92 | 93 | 94 | 95 | 96 | '); 97 | 98 | $this->responseMock->append($httpResponse); 99 | 100 | /** 101 | * @var $response \Ex\GetSimpleResponse 102 | */ 103 | $response = $this->client->getSimple('foo'); 104 | $this->assertInstanceOf('Ex\GetSimpleResponse', $response); 105 | $this->assertEquals('A', $response->getOut()); 106 | } 107 | 108 | public function testGetSimpleUnwrapped(): void 109 | { 110 | $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' 111 | 112 | 113 | 114 | 115 | 116 | 117 | '); 118 | $this->responseMock->append($httpResponse); 119 | 120 | $naming = new ShortNamingStrategy(); 121 | $dispatcher = new EventDispatcher(); 122 | $wsdlReader = new DefinitionsReader(null, $dispatcher); 123 | $soapReader = new SoapReader(); 124 | $dispatcher->addSubscriber($soapReader); 125 | 126 | $metadataGenerator = new MetadataGenerator($naming, self::$namespaces); 127 | $metadataGenerator->setUnwrap(true); 128 | $metadataReader = new DevMetadataLoader($metadataGenerator, $soapReader, $wsdlReader); 129 | 130 | $this->factory->setMetadataReader($metadataReader); 131 | 132 | /** 133 | * @var $response \Ex\GetSimpleResponse 134 | */ 135 | $response = $this->getClient()->getSimple('foo'); 136 | $this->assertSame('A', $response); 137 | } 138 | 139 | public function testHeaders(): void 140 | { 141 | $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' 142 | 143 | 144 | 145 | 146 | 147 | 148 | '); 149 | 150 | $this->responseMock->append($httpResponse); 151 | $this->responseMock->append($httpResponse); 152 | 153 | $mp = new GetReturnMultiParam(); 154 | $mp->setIn('foo'); 155 | 156 | $this->client->getSimple('foo', new Header($mp)); 157 | $this->client->getSimple('foo', (new Header($mp))->mustUnderstand()); 158 | $this->assertXmlStringEqualsXmlString( 159 | ' 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | ', 171 | (string) $this->requestResponseStack[0]['request']->getBody() 172 | ); 173 | 174 | $this->assertXmlStringEqualsXmlString( 175 | ' 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | ', 188 | (string) $this->requestResponseStack[1]['request']->getBody() 189 | ); 190 | } 191 | 192 | public function testResponseHeaders(): void 193 | { 194 | $httpResponse = new Response( 195 | 200, 196 | ['Content-Type' => 'text/xml'], 197 | ' 200 | 201 | 202 | username 203 | pass 204 | 205 | 206 | 207 | 208 | str 209 | 210 | 211 | ' 212 | ); 213 | 214 | $this->responseMock->append($httpResponse); 215 | 216 | $res = $this->client->responseHeaderMessages('foo'); 217 | $this->assertEquals('str', $res->getOut()); 218 | } 219 | 220 | public function testNoOutput(): void 221 | { 222 | $this->responseMock->append( 223 | new Response(200, ['Content-Type' => 'text/xml'], ' 224 | 225 | 226 | ') 227 | ); 228 | 229 | $response = $this->client->noOutput('foo'); 230 | $this->assertNull($response); 231 | } 232 | 233 | public function testNoInput(): void 234 | { 235 | $this->responseMock->append( 236 | new Response(200, ['Content-Type' => 'text/xml'], ' 237 | 238 | 239 | 240 | 241 | 242 | 243 | ') 244 | ); 245 | 246 | $this->client->noInput('foo'); 247 | $this->assertXmlStringEqualsXmlString( 248 | '', 249 | (string) $this->requestResponseStack[0]['request']->getBody() 250 | ); 251 | } 252 | 253 | public function testNoBoth(): void 254 | { 255 | $this->responseMock->append( 256 | new Response( 257 | 200, 258 | ['Content-Type' => 'text/xml'], 259 | '' 260 | ) 261 | ); 262 | 263 | $response = $this->client->noBoth('foo'); 264 | $this->assertNull($response); 265 | $this->assertXmlStringEqualsXmlString( 266 | '', 267 | (string) $this->requestResponseStack[0]['request']->getBody() 268 | ); 269 | } 270 | 271 | public function testReturnMultiParam(): void 272 | { 273 | $this->responseMock->append( 274 | new Response( 275 | 200, 276 | ['Content-Type' => 'text/xml'], 277 | ' 278 | 279 | 280 | 281 | 282 | 283 | 284 | ' 285 | ) 286 | ); 287 | 288 | $mp = new GetReturnMultiParam(); 289 | $mp->setIn('foo'); 290 | $return = $this->client->getReturnMultiParam($mp); 291 | 292 | $this->assertCount(2, $return); 293 | $this->assertEquals($return['otherParam'], 'str'); 294 | 295 | $this->assertInstanceOf(GetReturnMultiParamResponse::class, $return['getReturnMultiParamResponse']); 296 | $this->assertEquals($return['getReturnMultiParamResponse']->getOut(), 'foo'); 297 | 298 | $this->assertXmlStringEqualsXmlString( 299 | ' 300 | 301 | 302 | 303 | 304 | 305 | ', 306 | (string) $this->requestResponseStack[0]['request']->getBody() 307 | ); 308 | } 309 | 310 | public function testMultiParamRequest(): void 311 | { 312 | $this->responseMock->append( 313 | new Response( 314 | 200, 315 | ['Content-Type' => 'text/xml'], 316 | ' 317 | 318 | 319 | 320 | 321 | 322 | ' 323 | ) 324 | ); 325 | 326 | $mp = new GetMultiParam(); 327 | $mp->setIn('foo'); 328 | $this->client->getMultiParam($mp, 'str'); 329 | 330 | $this->assertXmlStringEqualsXmlString( 331 | ' 332 | 333 | 334 | 335 | 336 | 337 | 338 | ', 339 | (string) $this->requestResponseStack[0]['request']->getBody() 340 | ); 341 | } 342 | 343 | /** 344 | * @dataProvider getHttpFaultCodes 345 | */ 346 | public function testApplicationError(int $code): void 347 | { 348 | $this->responseMock->append( 349 | new Response($code, ['Content-Type' => 'text/xml'], ' 350 | 351 | 352 | 353 | SOAP-ENV:MustUnderstand 354 | SOAP Must Understand Error 355 | 356 | 357 | Business Exception 358 | 100 359 | some message 360 | fault state 361 | 362 | 363 | 364 | 365 | ') 366 | ); 367 | 368 | try { 369 | $this->client->getSimple('foo'); 370 | $this->assertTrue(false, 'Exception is not thrown'); 371 | } catch (Fault11Exception $e) { 372 | $this->assertInstanceOf(Fault::class, $e->getFault()); 373 | $this->assertSame('SOAP Must Understand Error', $e->getMessage()); 374 | $this->assertInstanceOf(FaultBase::class, $e->getFault()->getBody()->getFault()); 375 | 376 | $detail = $e->getFault()->getBody()->getFault()->getRawDetail(); 377 | $this->assertInstanceOf(\SimpleXMLElement::class, $detail); 378 | $this->assertXmlStringEqualsXmlString(trim(' 379 | 380 | Business Exception 381 | 100 382 | some message 383 | fault state 384 | 385 | '), $detail->asXML()); 386 | } 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /tests/Client/Client12RequestResponsesTest.php: -------------------------------------------------------------------------------- 1 | factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP12'); 30 | } 31 | 32 | public function testGetLastRequestMessage(): void 33 | { 34 | $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' 35 | 36 | 37 | 38 | 39 | 40 | 41 | '); 42 | 43 | $this->responseMock->append($httpResponse); 44 | 45 | $this->client->setDebug(true); 46 | $this->assertNull($this->client->__getLastRequestMessage()); 47 | 48 | /** 49 | * @var $response \Ex\GetSimpleResponse 50 | */ 51 | $response = $this->client->getSimple('foo'); 52 | $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $this->client->__getLastRequestMessage()); 53 | $this->assertXmlStringEqualsXmlString( 54 | ' 55 | 56 | 57 | 58 | 59 | 60 | ', 61 | (string) $this->client->__getLastRequestMessage()->getBody() 62 | ); 63 | } 64 | 65 | public function testGetLastResponseMessage(): void 66 | { 67 | $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' 68 | 69 | 70 | 71 | 72 | 73 | 74 | '); 75 | 76 | $this->responseMock->append($httpResponse); 77 | 78 | $this->client->setDebug(true); 79 | $this->assertNull($this->client->__getLastResponseMessage()); 80 | 81 | /** 82 | * @var $response \Ex\GetSimpleResponse 83 | */ 84 | $response = $this->client->getSimple('foo'); 85 | $this->assertInstanceOf('Psr\Http\Message\ResponseInterface', $this->client->__getLastResponseMessage()); 86 | $this->assertSame($httpResponse, $this->client->__getLastResponseMessage()); 87 | } 88 | 89 | public function testGetSimple(): void 90 | { 91 | $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' 92 | 93 | 94 | 95 | 96 | 97 | 98 | '); 99 | 100 | $this->responseMock->append($httpResponse); 101 | 102 | /** 103 | * @var $response \Ex\GetSimpleResponse 104 | */ 105 | $response = $this->client->getsimple('foo'); 106 | $this->assertInstanceOf('Ex\GetSimpleResponse', $response); 107 | $this->assertEquals('A', $response->getOut()); 108 | } 109 | 110 | public function testGetSimpleUnwrapped(): void 111 | { 112 | $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' 113 | 114 | 115 | 116 | 117 | 118 | 119 | '); 120 | $this->responseMock->append($httpResponse); 121 | 122 | $naming = new ShortNamingStrategy(); 123 | $dispatcher = new EventDispatcher(); 124 | $wsdlReader = new DefinitionsReader(null, $dispatcher); 125 | $soapReader = new SoapReader(); 126 | $dispatcher->addSubscriber($soapReader); 127 | 128 | $metadataGenerator = new MetadataGenerator($naming, self::$namespaces); 129 | $metadataGenerator->setUnwrap(true); 130 | $metadataReader = new DevMetadataLoader($metadataGenerator, $soapReader, $wsdlReader); 131 | 132 | $this->factory->setMetadataReader($metadataReader); 133 | 134 | /** 135 | * @var $response \Ex\GetSimpleResponse 136 | */ 137 | $response = $this->getClient()->getSimple('foo'); 138 | $this->assertSame('A', $response); 139 | } 140 | 141 | public function testHeaders(): void 142 | { 143 | $httpResponse = new Response(200, ['Content-Type' => 'application/soap+xml'], ' 144 | 145 | 146 | 147 | 148 | 149 | 150 | '); 151 | 152 | $this->responseMock->append($httpResponse); 153 | $this->responseMock->append($httpResponse); 154 | 155 | $this->client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP12', null, true); 156 | 157 | $mp = new GetReturnMultiParam(); 158 | $mp->setIn('foo'); 159 | 160 | $this->client->getSimple('foo', new Header($mp)); 161 | $this->client->getSimple('foo', (new Header($mp))->mustUnderstand()); 162 | $this->assertXmlStringEqualsXmlString( 163 | trim( 164 | ' 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | ' 176 | ), 177 | (string) $this->requestResponseStack[0]['request']->getBody() 178 | ); 179 | 180 | $this->assertXmlStringEqualsXmlString( 181 | ' 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | ', 194 | (string) $this->requestResponseStack[1]['request']->getBody() 195 | ); 196 | } 197 | 198 | public function testResponseHeaders(): void 199 | { 200 | $httpResponse = new Response( 201 | 200, 202 | ['Content-Type' => 'application/soap+xml'], 203 | ' 206 | 207 | 208 | username 209 | pass 210 | 211 | 212 | 213 | 214 | str 215 | 216 | 217 | ' 218 | ); 219 | 220 | $this->responseMock->append($httpResponse); 221 | 222 | $res = $this->client->responseHeaderMessages('foo'); 223 | $this->assertEquals('str', $res->getOut()); 224 | } 225 | 226 | public function testNoOutput(): void 227 | { 228 | $this->responseMock->append( 229 | new Response(200, ['Content-Type' => 'application/soap+xml'], ' 230 | 231 | 232 | ') 233 | ); 234 | $this->client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP12', null, true); 235 | 236 | $response = $this->client->noOutput('foo'); 237 | $this->assertNull($response); 238 | } 239 | 240 | public function testNoInput(): void 241 | { 242 | $this->responseMock->append( 243 | new Response(200, ['Content-Type' => 'application/soap+xml'], ' 244 | 245 | 246 | 247 | 248 | 249 | 250 | ') 251 | ); 252 | $this->client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP12', null, true); 253 | 254 | $this->client->noInput('foo'); 255 | $this->assertXmlStringEqualsXmlString( 256 | '', 257 | (string) $this->requestResponseStack[0]['request']->getBody() 258 | ); 259 | } 260 | 261 | public function testNoBoth(): void 262 | { 263 | $this->responseMock->append( 264 | new Response( 265 | 200, 266 | ['Content-Type' => 'application/soap+xml'], 267 | '' 268 | ) 269 | ); 270 | $this->client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP12', null, true); 271 | 272 | $response = $this->client->noBoth('foo'); 273 | $this->assertNull($response); 274 | $this->assertXmlStringEqualsXmlString( 275 | '', 276 | (string) $this->requestResponseStack[0]['request']->getBody() 277 | ); 278 | } 279 | 280 | public function testReturnMultiParam(): void 281 | { 282 | $this->responseMock->append( 283 | new Response( 284 | 200, 285 | ['Content-Type' => 'application/soap+xml'], 286 | ' 287 | 288 | 289 | 290 | 291 | 292 | 293 | ' 294 | ) 295 | ); 296 | $this->client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP12', null, true); 297 | 298 | $mp = new GetReturnMultiParam(); 299 | $mp->setIn('foo'); 300 | $return = $this->client->getReturnMultiParam($mp); 301 | 302 | $this->assertCount(2, $return); 303 | $this->assertEquals($return['otherParam'], 'str'); 304 | 305 | $this->assertInstanceOf(GetReturnMultiParamResponse::class, $return['getReturnMultiParamResponse']); 306 | $this->assertEquals($return['getReturnMultiParamResponse']->getOut(), 'foo'); 307 | 308 | $this->assertXmlStringEqualsXmlString( 309 | ' 310 | 311 | 312 | 313 | 314 | 315 | ', 316 | (string) $this->requestResponseStack[0]['request']->getBody() 317 | ); 318 | } 319 | 320 | public function testMultiParamRequest(): void 321 | { 322 | $this->responseMock->append( 323 | new Response( 324 | 200, 325 | ['Content-Type' => 'application/soap+xml'], 326 | ' 327 | 328 | 329 | 330 | 331 | 332 | ' 333 | ) 334 | ); 335 | $this->client = $this->factory->getClient(__DIR__ . '/../Fixtures/test.wsdl', 'testSOAP12', null, true); 336 | 337 | $mp = new GetMultiParam(); 338 | $mp->setIn('foo'); 339 | $this->client->getMultiParam($mp, 'str'); 340 | 341 | $this->assertXmlStringEqualsXmlString( 342 | ' 343 | 344 | 345 | 346 | 347 | 348 | 349 | ', 350 | (string) $this->requestResponseStack[0]['request']->getBody() 351 | ); 352 | } 353 | 354 | /** 355 | * @dataProvider getHttpFaultCodes 356 | */ 357 | public function testApplicationError(int $code): void 358 | { 359 | $this->responseMock->append( 360 | new Response($code, ['Content-Type' => 'application/soap+xml'], ' 361 | 362 | 363 | 364 | 365 | server 366 | 367 | 368 | String index out of range: 3 369 | 370 | 371 | 372 | Business Exception 373 | 100 374 | some message 375 | fault state 376 | 377 | 378 | 379 | 380 | ') 381 | ); 382 | 383 | try { 384 | $this->client->getSimple('foo'); 385 | $this->assertTrue(false, 'Exception is not thrown'); 386 | } catch (Fault12Exception $e) { 387 | $this->assertInstanceOf(Fault::class, $e->getFault()); 388 | $this->assertSame('String index out of range: 3', $e->getMessage()); 389 | $this->assertInstanceOf(FaultBase::class, $e->getFault()->getBody()->getFault()); 390 | 391 | $detail = $e->getFault()->getBody()->getFault()->getRawDetail(); 392 | $this->assertInstanceOf(\SimpleXMLElement::class, $detail); 393 | $this->assertXmlStringEqualsXmlString(trim(' 394 | 395 | Business Exception 396 | 100 397 | some message 398 | fault state 399 | 400 | '), $detail->asXML()); 401 | } 402 | } 403 | 404 | public function testSerializerContextParametersAreAdded() 405 | { 406 | $this->handlerRegistry->registerHandler(GraphNavigator::DIRECTION_SERIALIZATION, \Ex\SoapEnvelope12\Messages\GetSimpleInput::class, 'xml', 407 | function($visitor, \Ex\SoapEnvelope12\Messages\GetSimpleInput $obj, array $type, Context $context) { 408 | 409 | $this->assertTrue($context->hasAttribute('soapEndpoint'), 'The "soapEndpoint" attribute was not found on the context object'); 410 | $this->assertEquals('http://www.example.org/12', $context->getAttribute('soapEndpoint')); 411 | 412 | $this->assertTrue($context->hasAttribute('soapOperation'), 'The "soapOperation" attribute was not found on the context object'); 413 | $this->assertTrue( 414 | is_array($context->getAttribute('soapOperation')), 415 | 'The "soapOperation" attribute is not of type array, but '.gettype($context->getAttribute('soapOperation')) 416 | ); 417 | $this->assertEquals('http://www.example.org/test/getSimple', $context->getAttribute('soapOperation')['action']); 418 | 419 | throw new SerializerHandlerAssertionsWereExecuted('Stop serialization, test has finished'); 420 | } 421 | ); 422 | 423 | $client = $this->getClient(); 424 | 425 | // Assert that subscribing handler with assertions was executed 426 | $this->expectException(SerializerHandlerAssertionsWereExecuted::class); 427 | 428 | $client->getSimple('foo'); 429 | } 430 | } 431 | 432 | class SerializerHandlerAssertionsWereExecuted extends \Exception {}; -------------------------------------------------------------------------------- /tests/Client/RequestResponsesTest.php: -------------------------------------------------------------------------------- 1 | 'Ex']; 36 | /** 37 | * @var Generator 38 | */ 39 | protected static $generator; 40 | 41 | /** 42 | * @var MockHandler 43 | */ 44 | protected $responseMock; 45 | 46 | /** 47 | * @var array 48 | */ 49 | protected $requestResponseStack = []; 50 | 51 | /** 52 | * @var Client 53 | */ 54 | protected $client; 55 | 56 | /** 57 | * @var ClientFactory 58 | */ 59 | protected $factory; 60 | 61 | /** 62 | * @var HandlerRegistryInterface 63 | */ 64 | protected $handlerRegistry; 65 | 66 | public static function setUpBeforeClass(): void 67 | { 68 | self::$generator = new Generator(self::$namespaces, [], __DIR__ . '/tmp'); 69 | self::$generator->generate([__DIR__ . '/../Fixtures/test.wsdl']); 70 | self::$generator->registerAutoloader(); 71 | } 72 | 73 | public static function tearDownAfterClass(): void 74 | { 75 | self::$generator->unRegisterAutoloader(); 76 | //self::$generator->cleanDirectories(); 77 | } 78 | 79 | public function setUp(): void 80 | { 81 | $ref = new \ReflectionClass(Fault::class); 82 | 83 | $headerHandler = new HeaderHandler(); 84 | 85 | $listeners = static function (EventDispatcherInterface $d) use ($headerHandler): void { 86 | $d->addSubscriber($headerHandler); 87 | }; 88 | 89 | $handlers = function (HandlerRegistryInterface $h) use ($headerHandler): void { 90 | $h->registerSubscribingHandler($headerHandler); 91 | $this->handlerRegistry = $h; 92 | }; 93 | 94 | $serializer = self::$generator->buildSerializer($handlers, [ 95 | 'GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope12' => dirname($ref->getFileName()) . '/../../../Resources/metadata/jms12', 96 | 'GoetasWebservices\SoapServices\Metadata\Envelope\SoapEnvelope' => dirname($ref->getFileName()) . '/../../../Resources/metadata/jms', 97 | ], $listeners); 98 | 99 | $this->responseMock = new MockHandler(); 100 | $history = Middleware::history($this->requestResponseStack); 101 | 102 | $handler = HandlerStack::create($this->responseMock); 103 | $handler->push($history); 104 | 105 | $guzzle = new GuzzleHttpClient(['handler' => $handler]); 106 | 107 | $naming = new ShortNamingStrategy(); 108 | $dispatcher = new EventDispatcher(); 109 | $wsdlReader = new DefinitionsReader(null, $dispatcher); 110 | $soapReader = new SoapReader(); 111 | $dispatcher->addSubscriber($soapReader); 112 | 113 | $metadataGenerator = new MetadataGenerator($naming, self::$namespaces); 114 | $metadataLoader = new DevMetadataLoader($metadataGenerator, $soapReader, $wsdlReader); 115 | 116 | $this->factory = new ClientFactory($metadataLoader, $serializer); 117 | $this->factory->setHttpClient($guzzle); 118 | $this->client = $this->getClient(); 119 | } 120 | 121 | abstract protected function getClient(): Client; 122 | 123 | public function getErrorResponses(): array 124 | { 125 | return [ 126 | [new Response(500, ['Content-Type' => 'text/xml'], '')], 127 | [new Response(500, ['Content-Type' => 'text/html'])], 128 | 129 | [new Response(404, ['Content-Type' => 'text/xml'], '')], 130 | [new Response(404, ['Content-Type' => 'text/html'])], 131 | 132 | [new Response(200, ['Content-Type' => 'text/html'])], 133 | 134 | [new Response(500, ['Content-Type' => 'application/soap+xml'], '')], 135 | [new Response(500, ['Content-Type' => 'text/html'])], 136 | 137 | [new Response(404, ['Content-Type' => 'application/soap+xml'], '')], 138 | [new Response(404, ['Content-Type' => 'text/html'])], 139 | 140 | [new Response(200, ['Content-Type' => 'text/html'])], 141 | ]; 142 | } 143 | 144 | public function getHttpFaultCodes(): array 145 | { 146 | return [ 147 | [500], 148 | [200], 149 | ]; 150 | } 151 | 152 | /** 153 | * @dataProvider getErrorResponses 154 | */ 155 | public function testGetSimpleError(ResponseInterface $response): void 156 | { 157 | $this->expectException(UnexpectedFormatException::class); 158 | $this->responseMock->append($response); 159 | 160 | $this->client->getSimple('foo'); 161 | } 162 | 163 | public function testNoMethod(): void 164 | { 165 | $this->expectException(ClientException::class); 166 | $this->expectExceptionMessage('Can not find an operation to run abc service call'); 167 | 168 | $this->client->abc(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /tests/DependencyInjection/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | getDebugContainer(); 16 | 17 | $tempDir = sys_get_temp_dir(); 18 | 19 | $builder->dumpContainerForProd($tempDir, $debugContainer); 20 | $this->assertFileExists($tempDir . '/SoapContainer.php'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Fixtures/config.yml: -------------------------------------------------------------------------------- 1 | soap_client: 2 | metadata: 3 | 'tests/Fixtures/test.wsdl': ~ 4 | alternative_endpoints: 5 | service: 6 | port: xxx 7 | namespaces: 8 | 'http://www.example.org/test/': 'TestNs' 9 | destinations_php: 10 | 'TestNs': soap/src 11 | destinations_jms: 12 | 'TestNs\SoapEnvelope12': soap/metadata/soap-env-12 13 | 'TestNs\SoapEnvelope': soap/metadata/soap-env-11 14 | 'TestNs\SoapParts': soap/metadata/soap-parts 15 | 'TestNs': soap/metadata 16 | aliases: 17 | 'http://www.example.org/test/': 18 | responseHeaderMessagesResponse: 'HeaderResponse' 19 | 20 | -------------------------------------------------------------------------------- /tests/Fixtures/test.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | -------------------------------------------------------------------------------- /tests/Stub/StubGeneratorTest.php: -------------------------------------------------------------------------------- 1 | getDebugContainer(); 16 | 17 | /** 18 | * @var $clientStubGenerator \GoetasWebservices\SoapServices\SoapClient\StubGeneration\ClientStubGenerator 19 | */ 20 | $clientStubGenerator = $debugContainer->get('goetas_webservices.soap.stub.client_generator'); 21 | 22 | $wsdlMetadata = $debugContainer->getParameter('goetas_webservices.soap.config')['metadata']; 23 | $schemas = []; 24 | $portTypes = []; 25 | $wsdlReader = $debugContainer->get('goetas_webservices.wsdl2php.wsdl_reader'); 26 | 27 | foreach (array_keys($wsdlMetadata) as $src) { 28 | $definitions = $wsdlReader->readFile($src); 29 | $schemas[] = $definitions->getSchema(); 30 | $portTypes = array_merge($portTypes, $definitions->getAllPortTypes()); 31 | } 32 | 33 | $classDefinitions = $clientStubGenerator->generate($portTypes); 34 | 35 | $this->assertCount(2, $classDefinitions); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | addPsr4('TestNs\\', __DIR__ . '/../soap/src'); 22 | 23 | $container = new SoapClientContainer(); 24 | 25 | $serializer = SoapContainerBuilder::createSerializerBuilderFromContainer($container)->build(); 26 | $metadata = $container->get('goetas_webservices.soap.metadata_reader'); 27 | 28 | $responseMock = new MockHandler(); 29 | $httpResponse = new Response(200, ['Content-Type' => 'text/xml'], ' 30 | 31 | 32 | 33 | 34 | 35 | 36 | '); 37 | 38 | $responseMock->append($httpResponse); 39 | 40 | $requestResponseStack = []; 41 | $history = Middleware::history($requestResponseStack); 42 | 43 | $handler = HandlerStack::create($responseMock); 44 | $handler->push($history); 45 | 46 | $guzzle = new Client(['handler' => $handler]); 47 | 48 | $factory = new ClientFactory($metadata, $serializer); 49 | $factory->setHttpClient($guzzle); 50 | 51 | /** 52 | * @var $client \TestNs\SoapStubs\Test 53 | */ 54 | $client = $factory->getClient('tests/Fixtures/test.wsdl'); 55 | 56 | $out = $client->getSimple('bar'); 57 | 58 | if (!($out instanceof GetSimpleResponse)) { 59 | echo "\$out is not instanceof GetSimpleResponse\n"; 60 | exit(-128); 61 | } 62 | 63 | $request = (string) $requestResponseStack[0]['request']->getBody(); 64 | if (false === strpos($request, '') || false === strpos($request, ':getSimple ')) { 65 | echo "Wrong request\n"; 66 | exit(-128); 67 | } 68 | 69 | exit(0); 70 | --------------------------------------------------------------------------------