Passed
Pull Request — develop (#46)
by
unknown
45:48
created

canInstantiateSoapClientWithOptions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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