Passed
Pull Request — develop (#50)
by
unknown
59:22 queued 14:05
created

AbstractSoapClientBase::getStreamContextOptions()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

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