Completed
Push — master ( 13cb67...c71e7b )
by Mohamed
02:41
created

ParallelSoapClient::__doRequest()   C

Complexity

Conditions 7
Paths 15

Size

Total Lines 72
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 7.0222

Importance

Changes 0
Metric Value
cc 7
eloc 40
nc 15
nop 5
dl 0
loc 72
ccs 36
cts 39
cp 0.9231
crap 7.0222
rs 6.7427
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Soap;
4
5
use Psr\Log\LoggerInterface;
6
use Psr\Log\NullLogger;
7
8
/**
9
 * Single/Parallel Soap Class
10
 *
11
 * Implements soap with multi server-to-server calls using curl module.
12
 *
13
 * @author Mohamed Meabed <[email protected]>
14
 * @link   https://github.com/Meabed/php-parallel-soap
15
 * @note   Check the Example files and read the documentation carefully
16
 */
17
class ParallelSoapClient extends \SoapClient
18
{
19
    /**  array of all responses in the client */
20
    public $soapResponses = [];
21
22
    /**  array of all requests in the client */
23
    public $soapRequests = [];
24
25
    /**  array of all requests xml in the client */
26
    public $requestXmlArr = [];
27
28
    /**  string the xml returned from soap call */
29
    public $xmlResponse;
30
31
    /**  string current method call  */
32
    public $soapMethod;
33
34
    /** @var array of all requests soap methods in the client */
35
    public $soapMethodArr = [];
36
37
    /**  array of all requestIds  */
38
    public $requestIds;
39
40
    /**  string last request id  */
41
    public $lastRequestId;
42
43
    /**  bool soap parallel flag  */
44
    public $multi = false;
45
46
    /** @var bool log soap request */
47
    public $logSoapRequest = false;
48
49
    /** @var bool log pretty xml */
50
    public $logPrettyXml = false;
51
52
    /**  array of all curl_info in the client */
53
    public $curlInfo = [];
54
55
    /** @var array curl options */
56
    public $curlOptions = [];
57
58
    /** @var \Closure */
59
    protected $debugFn;
60
61
    /** @var \Closure */
62
    protected $resFn;
63
64
    /** @var \Closure */
65
    protected $soapActionFn;
66
67
    /** @var LoggerInterface */
68
    public $logger;
69
70
    /**
71
     * @var array
72
     */
73
    public $sharedCurlData = [];
74
75
    /**  getRequestResponse action constant used for parsing the xml with from parent::__doRequest */
76
    const GET_RESPONSE_CONST = 'getRequestResponseMethod';
77
    const ERROR_STR = '*ERROR*';
78
79
80
    /**
81
     * @return mixed
82
     */
83
    public function getMulti()
84
    {
85
        return $this->multi;
86
    }
87
88
    /**
89
     * @param mixed $multi
90
     * @return ParallelSoapClient
91
     */
92 8
    public function setMulti($multi)
93
    {
94 8
        $this->multi = $multi;
95 8
        return $this;
96
    }
97
98
    /**
99
     * @return array
100
     */
101
    public function getCurlOptions()
102
    {
103
        return $this->curlOptions;
104
    }
105
106
    /**
107
     * @param array $curlOptions
108
     * @return ParallelSoapClient
109
     */
110
    public function setCurlOptions(array $curlOptions)
111
    {
112
        $this->curlOptions = $curlOptions;
113
        return $this;
114
    }
115
116
    /**
117
     * @return LoggerInterface
118
     */
119
    public function getLogger()
120
    {
121
        return $this->logger;
122
    }
123
124
    /**
125
     * @param LoggerInterface $logger
126
     * @return ParallelSoapClient
127
     */
128
    public function setLogger(LoggerInterface $logger)
129
    {
130
        $this->logger = $logger;
131
        return $this;
132
    }
133
134
    /**
135
     * @return bool
136
     */
137
    public function isLogSoapRequest()
138
    {
139
        return $this->logSoapRequest;
140
    }
141
142
    /**
143
     * @param bool $logSoapRequest
144
     * @return ParallelSoapClient
145
     */
146
    public function setLogSoapRequest(bool $logSoapRequest)
147
    {
148
        $this->logSoapRequest = $logSoapRequest;
149
        return $this;
150
    }
151
152
    /**
153
     * @return bool
154
     */
155
    public function isLogPrettyXml()
156
    {
157
        return $this->logPrettyXml;
158
    }
159
160
    /**
161
     * @param bool $logPrettyXml
162
     * @return ParallelSoapClient
163
     */
164
    public function setLogPrettyXml(bool $logPrettyXml)
165
    {
166
        $this->logPrettyXml = $logPrettyXml;
167
        return $this;
168
    }
169
170
    /**
171
     * @return \Closure
172
     */
173
    public function getDebugFn()
174
    {
175
        return $this->debugFn;
176
    }
177
178
    /**
179
     * @param \Closure $debugFn
180
     * @return ParallelSoapClient
181
     */
182
    public function setDebugFn(\Closure $debugFn)
183
    {
184
        $this->debugFn = $debugFn;
185
        return $this;
186
    }
187
188
    /**
189
     * @return \Closure
190
     */
191
    public function getResFn()
192
    {
193
        return $this->resFn;
194
    }
195
196
    /**
197
     * @param \Closure $resFn
198
     * @return ParallelSoapClient
199
     */
200
    public function setResFn(\Closure $resFn)
201
    {
202
        $this->resFn = $resFn;
203
        return $this;
204
    }
205
206
    /**
207
     * @return \Closure
208
     */
209
    public function getSoapActionFn()
210
    {
211
        return $this->soapActionFn;
212
    }
213
214
    /**
215
     * @param \Closure $soapActionFn
216
     * @return ParallelSoapClient
217
     */
218
    public function setSoapActionFn(\Closure $soapActionFn)
219
    {
220
        $this->soapActionFn = $soapActionFn;
221
        return $this;
222
    }
223
224
225 8
    public function __construct($wsdl, array $options = null)
226
    {
227
        parent::__construct($wsdl, $options);
228
229
        // logger
230
        $logger = $options['logger'] ?? new NullLogger();
231
        $this->setLogger($logger);
232
233
        // debug function to add headers / last request / response / etc...
234 8
        $debugFn = $options['debugFn'] ?? function ($res, $id) {
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

234
        $debugFn = $options['debugFn'] ?? function ($res, /** @scrutinizer ignore-unused */ $id) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $res is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

234
        $debugFn = $options['debugFn'] ?? function (/** @scrutinizer ignore-unused */ $res, $id) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235 8
            };
236
        $this->setDebugFn($debugFn);
237
238
        // result parsing function
239
        $resFn = $options['resFn'] ?? function ($method, $res) {
240
                return $res;
241
            };
242
        $this->setResFn($resFn);
243
244
        // soapaction function to set in the header
245
        $soapActionFn = $options['soapActionFn'] ?? function ($action, $headers) {
246
                $headers[] = 'SOAPAction: "' . $action . '"';
247
                // 'SOAPAction: "' . $soapAction . '"', pass the soap action in every request from the WSDL if required
248
                return $headers;
249
            };
250
        $this->setSoapActionFn($soapActionFn);
251
252
        // cleanup
253
        unset($options['logger']);
254
        unset($options['debugFn']);
255
        unset($options['resFn']);
256
        unset($options['soapActionFn']);
257
    }
258
259
    /**
260
     * Soap __doRequest() Method with CURL Implementation
261
     *
262
     * @param string $request The XML SOAP request
263
     * @param string $location The URL to request
264
     * @param string $action The SOAP action
265
     * @param int $version The SOAP version
266
     * @param int $one_way If one_way is set to 1, this method returns nothing. Use this where a response is not expected
267
     *
268
     * @return string
269
     * @throws \Exception|\SoapFault
270
     */
271 8
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
272
    {
273 8
        $shouldGetResponse = ($this->soapMethod == static::GET_RESPONSE_CONST);
274
275
        // print xml for debugging testing
276 8
        if ($this->logSoapRequest) {
277
            // debug the request here
278
            if ($this->isLogPrettyXml()) {
279
                $this->logger->debug($this->prettyXml($request));
280
            } else {
281
                $this->logger->debug($request);
282
            }
283
        }
284
        // some .NET Servers only accept action method with ns url!! uncomment it if you get error wrong command
285
        /** return the xml response as its coming from normal soap call */
286 8
        if ($shouldGetResponse && $this->xmlResponse) {
287 8
            return $this->xmlResponse;
288
        }
289
290 8
        $soapRequests = &$this->soapRequests;
291
292
        /** @var $id string represent hashId of each request based on the request body
293
         * to avoid multiple calls for the same request if exists
294
         */
295 8
        $id = sha1($location . $request);
296
297
        /** @var $headers array of headers to be sent with request */
298
        $defaultHeaders = [
299 8
            'Content-type: text/xml',
300 8
            'charset=utf-8',
301 8
            "Accept: text/xml",
302 8
            "Content-length: " . strlen($request),
303
        ];
304
305
        // pass the soap action in every request from the WSDL if required
306 8
        $soapActionFn = $this->soapActionFn;
307 8
        $headers = $soapActionFn($action, $defaultHeaders);
308
309
        // ssl connection sharing
310 8
        if (empty($this->sharedCurlData[$location])) {
311 8
            $shOpt = curl_share_init();
312 8
            curl_share_setopt($shOpt, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
313 8
            curl_share_setopt($shOpt, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
314 8
            curl_share_setopt($shOpt, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
315 8
            $this->sharedCurlData[$location] = $shOpt;
316
        }
317
318 8
        $sh = $this->sharedCurlData[$location];
319
320 8
        $ch = curl_init();
321
        /** CURL_OPTIONS  */
322 8
        curl_setopt($ch, CURLOPT_URL, $location);
323 8
        curl_setopt($ch, CURLOPT_POST, true);
324 8
        curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
325 8
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
326 8
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
327 8
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
328 8
        curl_setopt($ch, CURLOPT_SHARE, $sh);
329
330
        // assign curl options
331 8
        foreach ($this->curlOptions as $key => $value) {
332 3
            curl_setopt($ch, $key, $value);
333
        }
334
335 8
        $soapRequests[$id] = $ch;
336
337 8
        $this->requestIds[$id] = $id;
338 8
        $this->soapMethodArr[$id] = $this->soapMethod;
339 8
        $this->requestXmlArr[$id] = $request;
340 8
        $this->lastRequestId = $id;
341
342 8
        return "";
343
    }
344
345
    /**
346
     * Call Sync method, act like normal soap method with extra implementation if needed
347
     * @param string $method
348
     * @param string $args
349
     * @throws \Exception|\SoapFault
350
     * @return string|mixed
351
     */
352 8
    public function callOne($method, $args)
353
    {
354
        try {
355 8
            parent::__call($method, $args);
356
            /** parse the xml response or throw an exception */
357 5
            $this->xmlResponse = $this->run([$this->lastRequestId]);
358 5
            $res = $this->getResponseResult($method, $args);
359 4
        } catch (\Exception $e) {
360 4
            throw $e;
361
        }
362
363 4
        return $res;
364
    }
365
366
    /**
367
     * Call Parallel method, suppress exception and convert to string
368
     * @param string $method
369
     * @param string $args
370
     * @return string|mixed
371
     */
372 5
    public function callParallel($method, $args)
373
    {
374
        /** generate curl session and add the soap requests to execute it later  */
375
        try {
376 5
            parent::__call($method, $args);
377
            /**
378
             * Return the Request ID to the calling method
379
             * This next 2 lines should be custom implementation based on your solution.
380
             *
381
             * @var $res string ,On multiple calls Simulate the response from Soap API to return the request Id of each call
382
             * to be able to get the response with it
383
             * @note check the example file to understand what to write here
384
             */
385 4
            $res = $this->lastRequestId;
386 1
        } catch (\Exception $ex) {
387
            /** catch any SoapFault [is not a valid method for this service] and return null */
388 1
            $res = self::ERROR_STR . ':' . $method . ' - ' . $ex->getCode() . ' - ' . $ex->getMessage() . ' - rand::' . rand();
389
        }
390
391 5
        return $res;
392
    }
393
394
    /**
395
     * __call Magic method to allow one and Parallel Soap calls with exception handling
396
     *
397
     * @param string $method
398
     * @param string $args
399
     *
400
     * @return string|mixed
401
     * @throws \Exception
402
     * @throws \SoapFault
403
     */
404 12
    public function __call($method, $args)
405
    {
406
        /** set current action to the current method call */
407 12
        $this->soapMethod = $method;
408
409 12
        if (!$this->multi) {
410 8
            return $this->callOne($method, $args);
411
        } else {
412 5
            return $this->callParallel($method, $args);
413
        }
414
    }
415
416
    /**
417
     * Execute all or some items from $this->soapRequests
418
     *
419
     * @param mixed $requestIds
420
     * @param bool $partial
421
     */
422 8
    public function doRequests($requestIds = [], $partial = false)
423
    {
424 8
        $allSoapRequests = &$this->soapRequests;
425 8
        $soapResponses = &$this->soapResponses;
426
427
        /** Determine if its partial call to execute some requests or execute all the request in $soapRequests array otherwise */
428 8
        if ($partial) {
429 5
            $soapRequests = array_intersect_key($allSoapRequests, array_flip($requestIds));
430
        } else {
431 4
            $soapRequests = &$this->soapRequests;
432
        }
433
434
        /** Initialise curl multi handler and execute the requests  */
435 8
        $mh = curl_multi_init();
436 8
        foreach ($soapRequests as $ch) {
437 8
            curl_multi_add_handle($mh, $ch);
438
        }
439
440 8
        $active = null;
441
        do {
442 8
            $mrc = curl_multi_exec($mh, $active);
443 8
        } while ($mrc === CURLM_CALL_MULTI_PERFORM || $active);
444
445 8
        while ($active && $mrc == CURLM_OK) {
446
            if (curl_multi_select($mh) != -1) {
447
                do {
448
                    $mrc = curl_multi_exec($mh, $active);
449
                } while ($mrc == CURLM_CALL_MULTI_PERFORM);
450
            }
451
        }
452
        /** assign the responses for all requests has been performed */
453 8
        foreach ($soapRequests as $id => $ch) {
454
            try {
455 8
                $soapResponses[$id] = curl_multi_getcontent($ch);
456
                // todo if config
457 8
                $curlInfo = curl_getinfo($ch);
458 8
                if ($curlInfo) {
459 8
                    $this->curlInfo[$id] = (object)$curlInfo;
460
                }
461
462
                // @link http://stackoverflow.com/questions/14319696/soap-issue-soapfault-exception-client-looks-like-we-got-no-xml-document
463 8
                if ($soapResponses[$id] === null) {
464 8
                    throw new \SoapFault("HTTP", curl_error($ch));
0 ignored issues
show
Bug introduced by
curl_error($ch) of type string is incompatible with the type integer expected by parameter $code of SoapFault::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

464
                    throw new \SoapFault("HTTP", /** @scrutinizer ignore-type */ curl_error($ch));
Loading history...
465
                }
466
            } catch (\Exception $e) {
467
                $soapResponses[$id] = $e;
468
            }
469 8
            curl_multi_remove_handle($mh, $ch);
470 8
            curl_close($ch);
471
        }
472 8
        curl_multi_close($mh);
473
474
        /** unset the request performed from the class instance variable $soapRequests so we don't request them again */
475 8
        if (!$partial) {
476 4
            $soapRequests = [];
477
        }
478 8
        foreach ($soapRequests as $id => $ch) {
479 5
            unset($allSoapRequests[$id]);
480
        }
481 8
    }
482
483
    /**
484
     * Main method to perform all or some Soap requests
485
     *
486
     * @param array $requestIds
487
     *
488
     * @return string $res
489
     */
490 8
    public function run($requestIds = [])
491
    {
492 8
        $partial = false;
493
494 8
        if (is_array($requestIds) && count($requestIds)) {
495 5
            $partial = true;
496
        }
497 8
        $allSoapResponses = &$this->soapResponses;
498
499
        /** perform all the request */
500 8
        $this->doRequests($requestIds, $partial);
501
502
        /** reset the class to synchronous mode */
503 8
        $this->setMulti(false);
504
505
        /** parse return response of the performed requests  */
506 8
        if ($partial) {
507 5
            $soapResponses = array_intersect_key($allSoapResponses, array_flip($requestIds));
508
        } else {
509 4
            $soapResponses = &$this->soapResponses;
510
        }
511
        /** if its one request return the first element in the array */
512 8
        if ($partial && count($requestIds) == 1) {
513 5
            $res = $soapResponses[$requestIds[0]];
514 5
            unset($allSoapResponses[$requestIds[0]]);
515
        } else {
516 4
            $res = $this->getMultiResponses($soapResponses);
517
        }
518
519 8
        return $res;
520
    }
521
522
    /**
523
     * Parse Response of Soap Requests with parent::__doRequest()
524
     *
525
     * @param array $responses
526
     *
527
     * @return mixed $resArr
528
     */
529 4
    public function getMultiResponses($responses = [])
530
    {
531 4
        $resArr = [];
532 4
        $this->soapMethod = static::GET_RESPONSE_CONST;
533
534 4
        foreach ($responses as $id => $ch) {
535
            try {
536 4
                $this->xmlResponse = $ch;
537 4
                if ($ch instanceof \Exception) {
538
                    throw $ch;
539
                }
540 4
                $res = parent::__call($this->soapMethodArr[$id], []);
0 ignored issues
show
Bug introduced by
array() of type array is incompatible with the type string expected by parameter $arguments of SoapClient::__call(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

540
                $res = parent::__call($this->soapMethodArr[$id], /** @scrutinizer ignore-type */ []);
Loading history...
541
                /**
542
                 * Return the Request ID to the calling method
543
                 * This next lines should be custom implementation based on your solution.
544
                 *
545
                 * @var $resArr string ,On multiple calls Simulate the response from Soap API to return the request Id of each call
546
                 * to be able to get the response with it
547
                 * @note check the example file to understand what to write here
548
                 */
549 4
                $resFn = $this->resFn;
550 4
                $resArr[$id] = $resFn($this->soapMethodArr[$id], $res);
551 4
                $this->addDebugData($res, $id);
552
            } catch (\Exception $ex) {
553
                $this->addDebugData($ex, $this->lastRequestId);
554
                $resArr[$id] = $ex;
555
            }
556 4
            unset($this->soapResponses[$id]);
557
        }
558 4
        $this->xmlResponse = '';
559 4
        $this->soapMethod = '';
560
561 4
        return $resArr;
562
    }
563
564
    /**
565
     * Parse Response of Soap Requests with parent::__doRequest()
566
     *
567
     * @param string $method
568
     * @param string|array $args
569
     *
570
     * @throws \Exception|\SoapFault|
571
     * @return string $res
572
     */
573 5
    public function getResponseResult($method, $args)
574
    {
575 5
        $this->soapMethod = static::GET_RESPONSE_CONST;
576
577
        try {
578 5
            $res = parent::__call($method, $args);
0 ignored issues
show
Bug introduced by
It seems like $args can also be of type array; however, parameter $arguments of SoapClient::__call() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

578
            $res = parent::__call($method, /** @scrutinizer ignore-type */ $args);
Loading history...
579
580 4
            $id = $this->lastRequestId;
581 4
            $this->addDebugData($res, $id);
582 1
        } catch (\Exception $ex) {
583 1
            $this->addDebugData($ex, $this->lastRequestId);
584 1
            throw $ex;
585
        }
586 4
        $this->soapMethod = '';
587
588 4
        $resFn = $this->resFn;
589 4
        return $resFn($method, $res);
590
    }
591
592
593
    /**
594
     * Add curl info to response object
595
     *
596
     * @param $res
597
     * @param $id
598
     *
599
     * @author Mohamed Meabed <[email protected]>
600
     *
601
     */
602 8
    public function addDebugData($res, $id)
603
    {
604 8
        $fn = $this->debugFn;
605 8
        $fn($res, $id);
606 8
    }
607
608
    /**
609
     * Print pretty xml
610
     *
611
     * @param $request
612
     *
613
     * @return string
614
     *
615
     * @author Mohamed Meabed <[email protected]>
616
     *
617
     */
618
    public function prettyXml($request)
619
    {
620
        $dom = new \DOMDocument();
621
        $dom->preserveWhiteSpace = false;
622
        $dom->loadXML($request);
623
        $dom->formatOutput = true;
624
625
        return $dom->saveXml();
626
    }
627
}
628