1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace WsdlToPhp\PackageBase; |
||
6 | |||
7 | use DOMDocument; |
||
8 | use SoapClient; |
||
9 | use SoapFault; |
||
10 | use SoapHeader; |
||
11 | |||
12 | abstract class AbstractSoapClientBase implements SoapClientInterface |
||
13 | { |
||
14 | /** |
||
15 | * SoapClient called to communicate with the actual SOAP Service |
||
16 | * @var SoapClient|null |
||
17 | */ |
||
18 | private ?SoapClient $soapClient = null; |
||
19 | |||
20 | /** |
||
21 | * Contains Soap call result |
||
22 | * @var mixed |
||
23 | */ |
||
24 | private $result; |
||
25 | |||
26 | /** |
||
27 | * Contains last errors |
||
28 | * @var array |
||
29 | */ |
||
30 | private array $lastError = []; |
||
31 | |||
32 | /** |
||
33 | * Contains output headers |
||
34 | * @var array |
||
35 | */ |
||
36 | protected array $outputHeaders = []; |
||
37 | |||
38 | 64 | public function __construct(array $wsdlOptions = []) |
|
39 | { |
||
40 | 64 | $this->initSoapClient($wsdlOptions); |
|
41 | } |
||
42 | |||
43 | 56 | public function getSoapClient(): ?SoapClient |
|
44 | { |
||
45 | 56 | return $this->soapClient; |
|
46 | } |
||
47 | |||
48 | 52 | public function setSoapClient(SoapClient $soapClient): SoapClient |
|
49 | { |
||
50 | 52 | return ($this->soapClient = $soapClient); |
|
51 | } |
||
52 | |||
53 | 64 | public function initSoapClient(array $options): void |
|
54 | { |
||
55 | 64 | $wsdlOptions = []; |
|
56 | 64 | $defaultWsdlOptions = static::getDefaultWsdlOptions(); |
|
57 | 64 | foreach ($defaultWsdlOptions as $optionName => $optionValue) { |
|
58 | 64 | if (array_key_exists($optionName, $options) && !is_null($options[$optionName])) { |
|
59 | 56 | $wsdlOptions[str_replace(self::OPTION_PREFIX, '', $optionName)] = $options[$optionName]; |
|
60 | 64 | } elseif (!is_null($optionValue)) { |
|
61 | 64 | $wsdlOptions[str_replace(self::OPTION_PREFIX, '', $optionName)] = $optionValue; |
|
62 | } |
||
63 | } |
||
64 | 64 | if (self::canInstantiateSoapClientWithOptions($wsdlOptions)) { |
|
65 | 52 | $wsdlUrl = null; |
|
66 | 52 | if (array_key_exists(str_replace(self::OPTION_PREFIX, '', self::WSDL_URL), $wsdlOptions)) { |
|
67 | 48 | $wsdlUrl = $wsdlOptions[str_replace(self::OPTION_PREFIX, '', self::WSDL_URL)]; |
|
68 | 48 | unset($wsdlOptions[str_replace(self::OPTION_PREFIX, '', self::WSDL_URL)]); |
|
69 | } |
||
70 | 52 | $soapClientClassName = $this->getSoapClientClassName(); |
|
71 | 52 | $this->setSoapClient(new $soapClientClassName($wsdlUrl, $wsdlOptions)); |
|
72 | } |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Checks if the provided options are sufficient to instantiate a SoapClient: |
||
77 | * - WSDL-mode : only the WSDL is required |
||
78 | * - non-WSDL-mode : URI and LOCATION are required, WSDL url can be empty then |
||
79 | * @param array $wsdlOptions |
||
80 | * @return bool |
||
81 | */ |
||
82 | 64 | protected static function canInstantiateSoapClientWithOptions(array $wsdlOptions): bool |
|
83 | { |
||
84 | 64 | return ( |
|
85 | 64 | array_key_exists(str_replace(self::OPTION_PREFIX, '', self::WSDL_URL), $wsdlOptions) || |
|
86 | 64 | ( |
|
87 | 64 | array_key_exists(str_replace(self::OPTION_PREFIX, '', self::WSDL_URI), $wsdlOptions) && |
|
88 | 64 | array_key_exists(str_replace(self::OPTION_PREFIX, '', self::WSDL_LOCATION), $wsdlOptions) |
|
89 | 64 | ) |
|
90 | 64 | ); |
|
91 | } |
||
92 | |||
93 | /** |
||
94 | * Returns the SoapClient class name to use to create the instance of the SoapClient. |
||
95 | * Be sure that this class inherits from the native PHP SoapClient class and this class has been loaded or can be loaded. |
||
96 | * The goal is to allow the override of the SoapClient without having to modify this generated class. |
||
97 | * Then the overriding SoapClient class can override for example the SoapClient::__doRequest() method if it is needed. |
||
98 | * @param string|null $soapClientClassName |
||
99 | * @return string |
||
100 | */ |
||
101 | 56 | public function getSoapClientClassName(?string $soapClientClassName = null): string |
|
102 | { |
||
103 | 56 | $className = static::DEFAULT_SOAP_CLIENT_CLASS; |
|
104 | 56 | if (!empty($soapClientClassName) && is_subclass_of($soapClientClassName, SoapClient::class)) { |
|
105 | 56 | $className = $soapClientClassName; |
|
106 | } |
||
107 | |||
108 | 56 | return $className; |
|
109 | } |
||
110 | |||
111 | /** |
||
112 | * Method returning all default SoapClient options values |
||
113 | * @return array |
||
114 | */ |
||
115 | 64 | public static function getDefaultWsdlOptions(): array |
|
116 | { |
||
117 | 64 | return [ |
|
118 | 64 | self::WSDL_AUTHENTICATION => null, |
|
119 | 64 | self::WSDL_CACHE_WSDL => WSDL_CACHE_NONE, |
|
120 | 64 | self::WSDL_CLASSMAP => null, |
|
121 | 64 | self::WSDL_COMPRESSION => null, |
|
122 | 64 | self::WSDL_CONNECTION_TIMEOUT => null, |
|
123 | 64 | self::WSDL_ENCODING => null, |
|
124 | 64 | self::WSDL_EXCEPTIONS => true, |
|
125 | 64 | self::WSDL_FEATURES => SOAP_SINGLE_ELEMENT_ARRAYS | SOAP_USE_XSI_ARRAY_TYPE, |
|
126 | 64 | self::WSDL_LOCAL_CERT => null, |
|
127 | 64 | self::WSDL_LOCATION => null, |
|
128 | 64 | self::WSDL_LOGIN => null, |
|
129 | 64 | self::WSDL_PASSPHRASE => null, |
|
130 | 64 | self::WSDL_PASSWORD => null, |
|
131 | 64 | self::WSDL_PROXY_HOST => null, |
|
132 | 64 | self::WSDL_PROXY_LOGIN => null, |
|
133 | 64 | self::WSDL_PROXY_PASSWORD => null, |
|
134 | 64 | self::WSDL_PROXY_PORT => null, |
|
135 | 64 | self::WSDL_SOAP_VERSION => null, |
|
136 | 64 | self::WSDL_SSL_METHOD => null, |
|
137 | 64 | self::WSDL_STREAM_CONTEXT => null, |
|
138 | 64 | self::WSDL_STYLE => null, |
|
139 | 64 | self::WSDL_TRACE => true, |
|
140 | 64 | self::WSDL_TYPEMAP => null, |
|
141 | 64 | self::WSDL_URL => null, |
|
142 | 64 | self::WSDL_URI => null, |
|
143 | 64 | self::WSDL_USE => null, |
|
144 | 64 | self::WSDL_USER_AGENT => null, |
|
145 | 64 | ]; |
|
146 | } |
||
147 | |||
148 | /** |
||
149 | * Allows to set the SoapClient location to call |
||
150 | * @param string $location |
||
151 | * @return AbstractSoapClientBase |
||
152 | */ |
||
153 | 2 | public function setLocation(string $location): self |
|
154 | { |
||
155 | 2 | if ($this->getSoapClient() instanceof SoapClient) { |
|
156 | 2 | $this->getSoapClient()->__setLocation($location); |
|
157 | } |
||
158 | |||
159 | 2 | return $this; |
|
160 | } |
||
161 | |||
162 | /** |
||
163 | * Returns the last request content as a DOMDocument or as a formatted XML String |
||
164 | * @param bool $asDomDocument |
||
165 | * @return DOMDocument|string|null |
||
166 | */ |
||
167 | 4 | public function getLastRequest(bool $asDomDocument = false) |
|
168 | { |
||
169 | 4 | return $this->getLastXml('__getLastRequest', $asDomDocument); |
|
170 | } |
||
171 | |||
172 | /** |
||
173 | * Returns the last response content as a DOMDocument or as a formatted XML String |
||
174 | * @param bool $asDomDocument |
||
175 | * @return DOMDocument|string|null |
||
176 | */ |
||
177 | 4 | public function getLastResponse(bool $asDomDocument = false) |
|
178 | { |
||
179 | 4 | return $this->getLastXml('__getLastResponse', $asDomDocument); |
|
180 | } |
||
181 | |||
182 | /** |
||
183 | * @param string $method |
||
184 | * @param bool $asDomDocument |
||
185 | * @return DOMDocument|string|null |
||
186 | */ |
||
187 | 8 | protected function getLastXml(string $method, bool $asDomDocument = false) |
|
188 | { |
||
189 | 8 | $xml = null; |
|
190 | 8 | if ($this->getSoapClient() instanceof SoapClient) { |
|
191 | 8 | $xml = static::getFormattedXml($this->getSoapClient()->$method(), $asDomDocument); |
|
192 | } |
||
193 | |||
194 | 8 | return $xml; |
|
195 | } |
||
196 | |||
197 | /** |
||
198 | * Returns the last request headers used by the SoapClient object as the original value or an array |
||
199 | * @param bool $asArray allows to get the headers in an associative array |
||
200 | * @return null|string|string[] |
||
201 | */ |
||
202 | 4 | public function getLastRequestHeaders(bool $asArray = false) |
|
203 | { |
||
204 | 4 | return $this->getLastHeaders('__getLastRequestHeaders', $asArray); |
|
205 | } |
||
206 | |||
207 | /** |
||
208 | * Returns the last response headers used by the SoapClient object as the original value or an array |
||
209 | * @param bool $asArray allows to get the headers in an associative array |
||
210 | * @return null|string|string[] |
||
211 | */ |
||
212 | 4 | public function getLastResponseHeaders(bool $asArray = false) |
|
213 | { |
||
214 | 4 | return $this->getLastHeaders('__getLastResponseHeaders', $asArray); |
|
215 | } |
||
216 | |||
217 | /** |
||
218 | * @param string $method |
||
219 | * @param bool $asArray allows to get the headers in an associative array |
||
220 | * @return null|string|string[] |
||
221 | */ |
||
222 | 8 | protected function getLastHeaders(string $method, bool $asArray) |
|
223 | { |
||
224 | 8 | $headers = $this->getSoapClient() instanceof SoapClient ? $this->getSoapClient()->$method() : null; |
|
225 | 8 | if (is_string($headers) && $asArray) { |
|
226 | 4 | return static::convertStringHeadersToArray($headers); |
|
227 | } |
||
228 | |||
229 | 4 | return $headers; |
|
230 | } |
||
231 | |||
232 | /** |
||
233 | * Returns a XML string content as a DOMDocument or as a formatted XML string |
||
234 | * @param string|null $string |
||
235 | * @param bool $asDomDocument |
||
236 | * @return DOMDocument|string|null |
||
237 | */ |
||
238 | 8 | public static function getFormattedXml(?string $string, bool $asDomDocument = false) |
|
239 | { |
||
240 | 8 | return Utils::getFormattedXml($string, $asDomDocument); |
|
241 | } |
||
242 | |||
243 | /** |
||
244 | * Returns an associative array between the headers name and their respective values |
||
245 | * @param string $headers |
||
246 | * @return string[] |
||
247 | */ |
||
248 | 4 | public static function convertStringHeadersToArray(string $headers): array |
|
249 | { |
||
250 | 4 | $lines = explode("\r\n", $headers); |
|
251 | 4 | $headers = []; |
|
252 | 4 | foreach ($lines as $line) { |
|
253 | 4 | if (strpos($line, ':')) { |
|
254 | 4 | $headerParts = explode(':', $line); |
|
255 | 4 | $headers[$headerParts[0]] = trim(implode(':', array_slice($headerParts, 1))); |
|
256 | } |
||
257 | } |
||
258 | |||
259 | 4 | return $headers; |
|
260 | } |
||
261 | |||
262 | /** |
||
263 | * Sets a SoapHeader to send |
||
264 | * For more information, please read the online documentation on {@link http://www.php.net/manual/en/class.soapheader.php} |
||
265 | * @param string $namespace SoapHeader namespace |
||
266 | * @param string $name SoapHeader name |
||
267 | * @param mixed $data SoapHeader data |
||
268 | * @param bool $mustUnderstand |
||
269 | * @param string|null $actor |
||
270 | * @return AbstractSoapClientBase |
||
271 | */ |
||
272 | 6 | public function setSoapHeader(string $namespace, string $name, $data, bool $mustUnderstand = false, ?string $actor = null): self |
|
273 | { |
||
274 | 6 | if ($this->getSoapClient()) { |
|
275 | 6 | $defaultHeaders = (isset($this->getSoapClient()->__default_headers) && is_array($this->getSoapClient()->__default_headers)) ? $this->getSoapClient()->__default_headers : []; |
|
276 | 6 | foreach ($defaultHeaders as $index => $soapHeader) { |
|
277 | 2 | if ($soapHeader->name === $name) { |
|
278 | 2 | unset($defaultHeaders[$index]); |
|
279 | 2 | break; |
|
280 | } |
||
281 | } |
||
282 | 6 | $this->getSoapClient()->__setSoapheaders(null); |
|
283 | 6 | if (!empty($actor)) { |
|
284 | 2 | array_push($defaultHeaders, new SoapHeader($namespace, $name, $data, $mustUnderstand, $actor)); |
|
285 | } else { |
||
286 | 4 | array_push($defaultHeaders, new SoapHeader($namespace, $name, $data, $mustUnderstand)); |
|
287 | } |
||
288 | 6 | $this->getSoapClient()->__setSoapheaders($defaultHeaders); |
|
289 | } |
||
290 | |||
291 | 6 | return $this; |
|
292 | } |
||
293 | |||
294 | /** |
||
295 | * Sets the SoapClient Stream context HTTP Header name according to its value |
||
296 | * If a context already exists, it tries to modify it |
||
297 | * It the context does not exist, it then creates it with the header name and its value |
||
298 | * @param string $headerName |
||
299 | * @param mixed $headerValue |
||
300 | * @return bool |
||
301 | */ |
||
302 | 12 | public function setHttpHeader(string $headerName, $headerValue): bool |
|
303 | { |
||
304 | 12 | $state = false; |
|
305 | 12 | if ($this->getSoapClient() && !empty($headerName)) { |
|
306 | 12 | $streamContext = $this->getStreamContext(); |
|
307 | 12 | if ($streamContext === null) { |
|
308 | $options = []; |
||
309 | $options['http'] = []; |
||
310 | $options['http']['header'] = ''; |
||
311 | } else { |
||
312 | 12 | $options = stream_context_get_options($streamContext); |
|
313 | 12 | if (!array_key_exists('http', $options) || !is_array($options['http'])) { |
|
314 | $options['http'] = []; |
||
315 | $options['http']['header'] = ''; |
||
316 | 12 | } elseif (!array_key_exists('header', $options['http'])) { |
|
317 | $options['http']['header'] = ''; |
||
318 | } |
||
319 | } |
||
320 | 12 | if (count($options) && array_key_exists('http', $options) && is_array($options['http']) && array_key_exists('header', $options['http']) && is_string($options['http']['header'])) { |
|
321 | 12 | $lines = explode("\r\n", $options['http']['header']); |
|
322 | /** |
||
323 | * Ensure there is only one header entry for this header name |
||
324 | */ |
||
325 | 12 | $newLines = []; |
|
326 | 12 | foreach ($lines as $line) { |
|
327 | 12 | if (!empty($line) && strpos($line, $headerName) === false) { |
|
328 | 12 | array_push($newLines, $line); |
|
329 | } |
||
330 | } |
||
331 | /** |
||
332 | * Add new header entry |
||
333 | */ |
||
334 | 12 | array_push($newLines, "$headerName: $headerValue"); |
|
335 | /** |
||
336 | * Set the context http header option |
||
337 | */ |
||
338 | 12 | $options['http']['header'] = implode("\r\n", $newLines); |
|
339 | /** |
||
340 | * Create context if it does not exist |
||
341 | */ |
||
342 | 12 | if ($streamContext === null) { |
|
343 | $state = is_resource($this->getSoapClient()->_stream_context = stream_context_create($options)); |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
344 | } else { |
||
345 | /** |
||
346 | * Set the new context http header option |
||
347 | */ |
||
348 | 12 | $state = stream_context_set_option($this->getSoapClient()->_stream_context, 'http', 'header', $options['http']['header']); |
|
349 | } |
||
350 | } |
||
351 | } |
||
352 | |||
353 | 12 | return $state; |
|
354 | } |
||
355 | |||
356 | /** |
||
357 | * Returns current SoapClient::_stream_context resource or null |
||
358 | * @return resource|null |
||
359 | */ |
||
360 | 14 | public function getStreamContext() |
|
361 | { |
||
362 | 14 | return ($this->getSoapClient() && isset($this->getSoapClient()->_stream_context) && is_resource($this->getSoapClient()->_stream_context)) ? $this->getSoapClient()->_stream_context : null; |
|
363 | } |
||
364 | |||
365 | /** |
||
366 | * Returns current SoapClient::_stream_context resource options or empty array |
||
367 | * @return array |
||
368 | */ |
||
369 | 2 | public function getStreamContextOptions(): array |
|
370 | { |
||
371 | 2 | $options = []; |
|
372 | 2 | $context = $this->getStreamContext(); |
|
373 | 2 | if ($context !== null) { |
|
374 | 2 | $options = stream_context_get_options($context); |
|
375 | 2 | if (isset($options['http']['header']) && is_string($options['http']['header'])) { |
|
376 | 2 | $options['http']['header'] = array_filter(array_map('trim', explode(PHP_EOL, $options['http']['header']))); |
|
377 | } |
||
378 | } |
||
379 | |||
380 | 2 | return $options; |
|
381 | } |
||
382 | |||
383 | /** |
||
384 | * Method returning last errors occurred during the calls |
||
385 | * @return array |
||
386 | */ |
||
387 | 2 | public function getLastError(): array |
|
388 | { |
||
389 | 2 | return $this->lastError; |
|
390 | } |
||
391 | |||
392 | /** |
||
393 | * Method saving the last error returned by the SoapClient |
||
394 | * @param string $methodName the method called when the error occurred |
||
395 | * @param SoapFault $soapFault the fault |
||
396 | * @return AbstractSoapClientBase |
||
397 | */ |
||
398 | 6 | public function saveLastError(string $methodName, SoapFault $soapFault): SoapClientInterface |
|
399 | { |
||
400 | 6 | $this->lastError[$methodName] = $soapFault; |
|
401 | |||
402 | 6 | return $this; |
|
403 | } |
||
404 | |||
405 | /** |
||
406 | * Method getting the last error for a certain method |
||
407 | * @param string $methodName method name to get error from |
||
408 | * @return SoapFault|null |
||
409 | */ |
||
410 | 2 | public function getLastErrorForMethod(string $methodName): ?SoapFault |
|
411 | { |
||
412 | 2 | return array_key_exists($methodName, $this->lastError) ? $this->lastError[$methodName] : null; |
|
413 | } |
||
414 | |||
415 | /** |
||
416 | * Method returning current result from Soap call |
||
417 | * @return mixed |
||
418 | */ |
||
419 | 2 | public function getResult() |
|
420 | { |
||
421 | 2 | return $this->result; |
|
422 | } |
||
423 | |||
424 | /** |
||
425 | * Method setting current result from Soap call |
||
426 | * @param mixed $result |
||
427 | * @return AbstractSoapClientBase |
||
428 | */ |
||
429 | 6 | public function setResult($result): SoapClientInterface |
|
430 | { |
||
431 | 6 | $this->result = $result; |
|
432 | |||
433 | 6 | return $this; |
|
434 | } |
||
435 | |||
436 | /** |
||
437 | * @return array |
||
438 | */ |
||
439 | 2 | public function getOutputHeaders(): array |
|
440 | { |
||
441 | 2 | return $this->outputHeaders; |
|
442 | } |
||
443 | |||
444 | /** |
||
445 | * Default string representation of current object. Don't want to expose any sensible data |
||
446 | * @return string |
||
447 | */ |
||
448 | 2 | public function __toString(): string |
|
449 | { |
||
450 | 2 | return get_called_class(); |
|
451 | } |
||
452 | } |
||
453 |