Passed
Push — feature/issue-32 ( 6f5839...dcb804 )
by Mikaël
01:22
created

AbstractSoapClientBase::getFormattedXml()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 64
    }
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 64
    }
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
        return (
85 64
            array_key_exists(str_replace(self::OPTION_PREFIX, '', self::WSDL_URL), $wsdlOptions) ||
86
            (
87 16
                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
            )
90
        );
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 = self::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
        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
        ];
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 6
        return $this;
291
    }
292
293
    /**
294
     * Sets the SoapClient Stream context HTTP Header name according to its value
295
     * If a context already exists, it tries to modify it
296
     * It the context does not exist, it then creates it with the header name and its value
297
     * @param string $headerName
298
     * @param mixed $headerValue
299
     * @return bool
300
     */
301 12
    public function setHttpHeader(string $headerName, $headerValue): bool
302
    {
303 12
        $state = false;
304 12
        if ($this->getSoapClient() && !empty($headerName)) {
305 12
            $streamContext = $this->getStreamContext();
306 12
            if ($streamContext === null) {
307
                $options = [];
308
                $options['http'] = [];
309
                $options['http']['header'] = '';
310
            } else {
311 12
                $options = stream_context_get_options($streamContext);
312 12
                if (!array_key_exists('http', $options) || !is_array($options['http'])) {
313
                    $options['http'] = [];
314
                    $options['http']['header'] = '';
315 12
                } elseif (!array_key_exists('header', $options['http'])) {
316
                    $options['http']['header'] = '';
317
                }
318
            }
319 12
            if (count($options) && array_key_exists('http', $options) && is_array($options['http']) && array_key_exists('header', $options['http']) && is_string($options['http']['header'])) {
320 12
                $lines = explode("\r\n", $options['http']['header']);
321
                /**
322
                 * Ensure there is only one header entry for this header name
323
                 */
324 12
                $newLines = [];
325 12
                foreach ($lines as $line) {
326 12
                    if (!empty($line) && strpos($line, $headerName) === false) {
327 12
                        array_push($newLines, $line);
328
                    }
329
                }
330
                /**
331
                 * Add new header entry
332
                 */
333 12
                array_push($newLines, "$headerName: $headerValue");
334
                /**
335
                 * Set the context http header option
336
                 */
337 12
                $options['http']['header'] = implode("\r\n", $newLines);
338
                /**
339
                 * Create context if it does not exist
340
                 */
341 12
                if ($streamContext === null) {
342
                    $state = ($this->getSoapClient()->_stream_context = stream_context_create($options)) ? true : false;
0 ignored issues
show
Bug introduced by
The property _stream_context does not seem to exist on SoapClient.
Loading history...
343
                } else {
344
                    /**
345
                     * Set the new context http header option
346
                     */
347 12
                    $state = stream_context_set_option($this->getSoapClient()->_stream_context, 'http', 'header', $options['http']['header']);
348
                }
349
            }
350
        }
351 12
        return $state;
352
    }
353
354
    /**
355
     * Returns current SoapClient::_stream_context resource or null
356
     * @return resource|null
357
     */
358 14
    public function getStreamContext()
359
    {
360 14
        return ($this->getSoapClient() && isset($this->getSoapClient()->_stream_context) && is_resource($this->getSoapClient()->_stream_context)) ? $this->getSoapClient()->_stream_context : null;
361
    }
362
363
    /**
364
     * Returns current SoapClient::_stream_context resource options or empty array
365
     * @return array
366
     */
367 2
    public function getStreamContextOptions(): array
368
    {
369 2
        $options = [];
370 2
        $context = $this->getStreamContext();
371 2
        if ($context !== null) {
372 2
            $options = stream_context_get_options($context);
373 2
            if (isset($options['http']['header']) && is_string($options['http']['header'])) {
374 2
                $options['http']['header'] = array_filter(array_map('trim', explode(PHP_EOL, $options['http']['header'])));
375
            }
376
        }
377 2
        return $options;
378
    }
379
380
    /**
381
     * Method returning last errors occurred during the calls
382
     * @return array
383
     */
384 2
    public function getLastError(): array
385
    {
386 2
        return $this->lastError;
387
    }
388
389
    /**
390
     * Method saving the last error returned by the SoapClient
391
     * @param string $methodName the method called when the error occurred
392
     * @param SoapFault $soapFault the fault
393
     * @return AbstractSoapClientBase
394
     */
395 6
    public function saveLastError(string $methodName, SoapFault $soapFault): SoapClientInterface
396
    {
397 6
        $this->lastError[$methodName] = $soapFault;
398
399 6
        return $this;
400
    }
401
402
    /**
403
     * Method getting the last error for a certain method
404
     * @param string $methodName method name to get error from
405
     * @return SoapFault|null
406
     */
407 2
    public function getLastErrorForMethod(string $methodName): ?SoapFault
408
    {
409 2
        return array_key_exists($methodName, $this->lastError) ? $this->lastError[$methodName] : null;
410
    }
411
412
    /**
413
     * Method returning current result from Soap call
414
     * @return mixed
415
     */
416 2
    public function getResult()
417
    {
418 2
        return $this->result;
419
    }
420
421
    /**
422
     * Method setting current result from Soap call
423
     * @param mixed $result
424
     * @return AbstractSoapClientBase
425
     */
426 6
    public function setResult($result): SoapClientInterface
427
    {
428 6
        $this->result = $result;
429
430 6
        return $this;
431
    }
432
433
    /**
434
     * @return array
435
     */
436 2
    public function getOutputHeaders(): array
437
    {
438 2
        return $this->outputHeaders;
439
    }
440
441
    /**
442
     * Default string representation of current object. Don't want to expose any sensible data
443
     * @return string
444
     */
445 2
    public function __toString(): string
446
    {
447 2
        return get_called_class();
448
    }
449
}
450