AbstractSoapClientBase   F
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 439
Duplicated Lines 0 %

Test Coverage

Coverage 95.65%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 138
c 6
b 0
f 0
dl 0
loc 439
ccs 154
cts 161
cp 0.9565
rs 2.64
wmc 72

27 Methods

Rating   Name   Duplication   Size   Complexity  
B initSoapClient() 0 19 7
A getLastResponseHeaders() 0 3 1
A getSoapClientClassName() 0 8 3
A getLastRequestHeaders() 0 3 1
A __construct() 0 3 1
A getLastResponse() 0 3 1
A getLastHeaders() 0 8 4
A getFormattedXml() 0 3 1
A setSoapClient() 0 3 1
A setLocation() 0 7 2
A convertStringHeadersToArray() 0 12 3
A getSoapClient() 0 3 1
A getLastXml() 0 8 2
A getLastRequest() 0 3 1
A getOutputHeaders() 0 3 1
A getResult() 0 3 1
A getStreamContextOptions() 0 12 4
A setResult() 0 5 1
A getLastError() 0 3 1
A getLastErrorForMethod() 0 3 2
A __toString() 0 3 1
A getStreamContext() 0 3 4
B setSoapHeader() 0 20 7
A saveLastError() 0 5 1
C setHttpHeader() 0 52 16
A getDefaultWsdlOptions() 0 30 1
A canInstantiateSoapClientWithOptions() 0 7 3

How to fix   Complexity   

Complex Class

Complex classes like AbstractSoapClientBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractSoapClientBase, and based on these observations, apply Extract Interface, too.

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
The property _stream_context does not seem to exist on SoapClient.
Loading history...
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