Completed
Push — master ( d345fb...2f5ae6 )
by Ivo
01:52
created

SoapClient::__construct()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 20
cts 20
cp 1
rs 8.7057
c 0
b 0
f 0
cc 6
nc 12
nop 2
crap 6
1
<?php
2
3
namespace Freshcells\SoapClientBundle\SoapClient;
4
5
use Freshcells\SoapClientBundle\Event\Event;
6
use Freshcells\SoapClientBundle\Event\Events;
7
use Freshcells\SoapClientBundle\Event\FaultEvent;
8
use Freshcells\SoapClientBundle\Event\RequestEvent;
9
use Freshcells\SoapClientBundle\Event\ResponseEvent;
10
use Ramsey\Uuid\Uuid;
11
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
12
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
13
14
/**
15
 * Class SoapClient
16
 */
17
class SoapClient extends \SoapClient implements SoapClientInterface
18
{
19
    /**
20
     * @var array
21
     */
22
    protected $options;
23
    /**
24
     * @var EventDispatcherInterface
25
     */
26
    protected $dispatcher;
27
    /**
28
     * @var array
29
     */
30
    private $mockRequests = [];
31
    /**
32
     * @var array
33
     */
34
    private $mockResponses = [];
35
36
    /**
37
     * SoapClient constructor.
38
     * @param null $wsdl
39
     * @param array|null $options
40
     */
41 18
    public function __construct($wsdl = null, array $options = [])
42
    {
43
44 18
        if (isset($options['mock_requests'])) {
45 12
            $this->setMockRequests($options['mock_requests']);
46 12
            unset($options['mock_requests']);
47
        }
48 18
        if (isset($options['mock_responses'])) {
49 9
            $this->setMockResponses($options['mock_responses']);
50 9
            unset($options['mock_responses']);
51
        }
52
53
        $defaults = array(
54 18
            'compression'        => (SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP),
55 18
            'cache_wsdl'         => WSDL_CACHE_BOTH,
56 18
            'connection_timeout' => 60,
57
            'exceptions'         => true,
58 18
            'features'           => SOAP_SINGLE_ELEMENT_ARRAYS,
59 18
            'soap_version'       => SOAP_1_2,
60
            'trace'              => true,
61 18
            'user_agent'         => 'freshcells/soap-client-bundle',
62
        );
63
64 18
        $options = array_merge($defaults, $options);
65
66 18
        if (!is_null($wsdl)) {
67
            // if option 'location' not set explicit use WSDL URL as service location
68
            // this was added for some obscure reason
69
            // dont use for local wsdl
70 18
            if (!isset($options['location']) && substr($wsdl, 0, 4) === "http") {
71 18
                $options['location'] = $this->resolveLocation($wsdl);
72
            }
73
        }
74
75 18
        $this->SoapClient($wsdl, $options);
76 18
        $this->options = $options;
77 18
    }
78
79 6
    public function getOptions(): array
80
    {
81 6
        return $this->options;
82
    }
83
84 9
    public function __call($function_name, $arguments)
85
    {
86
        try {
87 9
            $response = parent::__call($function_name, $arguments);
88
            //works only with 'exceptions' => false, we always throw
89 6
            if (is_soap_fault($response)) {
90 6
                throw $response;
91
            }
92 3
        } catch (\Exception $e) {
93 3
            $this->handleFault($function_name, $arguments, $e);
94
        }
95
96 6
        return $response;
97
    }
98
99
    public function __soapCall(
100
        $function_name,
101
        $arguments,
102
        $options = null,
103
        $input_headers = null,
104
        &$output_headers = null
105
    ) {
106
        try {
107
            $response = parent::__soapCall($function_name, $arguments, $options, $input_headers, $output_headers);
108
            //works only with 'exceptions' => false, we always throw
109
            if (is_soap_fault($response)) {
110
                throw $response;
111
            }
112
        } catch (\Exception $e) {
113
            $this->handleFault($function_name, $arguments, $e);
114
        }
115
116
        return $response;
117
    }
118
119
    /**
120
     * @param string $request
121
     * @param string $location
122
     * @param string $action
123
     * @param int $version
124
     * @param null $one_way
125
     * @return bool|string
126
     */
127 6
    public function __doRequest($request, $location, $action, $version, $one_way = null)
128
    {
129 6
        $id = Uuid::uuid1();
130
131 6
        foreach ($this->mockRequests as $key => $mockRequest) {
132 6
            if (is_string($key)) {
133 6
                if (strrpos($action, $key) !== false) {
134 6
                    $request = file_get_contents($mockRequest);
135 6
                    break;
136
                }
137
            } else {
138
                if (is_callable($mockRequest)) {
139
                    if ($requestFilePath = $mockRequest($request, $location, $action, $version, $one_way)) {
140
                        $request = file_get_contents($requestFilePath);
141
                        break;
142
                    }
143
                }
144
            }
145
        }
146
147 6
        $this->preCall($id->toString(), (string)$action, $request);
148
149 6
        foreach ($this->mockResponses as $key => $mockResponse) {
150 6
            if (is_string($key)) {
151 3
                if (strrpos($action, $key) !== false) {
152 3
                    $response = file_get_contents($mockResponse);
153
154 3
                    $this->postCall($id->toString(), $action, $response);
155
156 3
                    return $response;
157
                }
158
            } else {
159 3
                if (is_callable($mockResponse)) {
160 3
                    if ($responseFilePath = $mockResponse($request, $location, $action, $version, $one_way)) {
161 3
                        $response = file_get_contents($responseFilePath);
162
163 3
                        $this->postCall($id->toString(), $action, $response);
164
165 3
                        return $response;
166
                    }
167
                }
168
            }
169
        }
170
171
        /* workaround for working timeout */
172
        $socketTimeout = false;
173
        if (isset($this->options['connection_timeout'])
174
            && (int)$this->options['connection_timeout'] > (int)ini_get('default_socket_timeout')
175
        ) {
176
            $socketTimeout = ini_set('default_socket_timeout', $this->options['connection_timeout']);
177
        }
178
179
        $response = parent::__doRequest($request, $location, $action, $version, $one_way);
180
181
        $this->postCall($id->toString(), (string)$action, $response);
182
183
        if ($socketTimeout !== false) {
184
            ini_set('default_socket_timeout', $socketTimeout);
185
        }
186
187
        return $response;
188
    }
189
190
    /**
191
     * Triggered before a request is executed
192
     *
193
     * @param string $id
194
     * @param string $resource
195
     * @param string $requestContent
196
     */
197 6
    protected function preCall(string $id, string $resource, string $requestContent = null)
198
    {
199 6
        $this->dispatch(new RequestEvent($id, $resource, $requestContent), Events::REQUEST);
200 6
    }
201
202
    /**
203
     * @param string $id
204
     * @param string $resource
205
     * @param string $response
206
     */
207 6
    protected function postCall(string $id, string $resource, string $response = null)
208
    {
209 6
        $responseEvent = new ResponseEvent(
210 6
            $id,
211 6
            $resource,
212 6
            $this->__getLastRequest(),
213 6
            $this->__getLastRequestHeaders(),
214 6
            $response,
215 6
            $this->__getLastResponseHeaders()
216
        );
217 6
        $this->dispatch($responseEvent, Events::RESPONSE);
218 6
    }
219
220
    /**
221
     * @param string $id
222
     * @param string $resource
223
     * @param string $requestContent
224
     * @param \Exception $exception
225
     */
226 3
    protected function faultCall(string $id, string $resource, string $requestContent, \Exception $exception)
227
    {
228 3
        $this->dispatch(
229 3
            new FaultEvent($id, $exception, new RequestEvent($id, $resource, $requestContent)),
230 3
            Events::FAULT
231
        );
232 3
    }
233
234
    /**
235
     * @param string $wsdl
236
     * @return string
237
     */
238 18
    protected function resolveLocation($wsdl)
239
    {
240 18
        $wsdlUrl = parse_url($wsdl);
241
242 18
        return ((isset($wsdlUrl['scheme'])) ? $wsdlUrl['scheme'].'://' : '')
243 18
            .((isset($wsdlUrl['user'])) ? $wsdlUrl['user']
244 18
                .((isset($wsdlUrl['pass'])) ? ':'.$wsdlUrl['pass'] : '').'@' : '')
245 18
            .((isset($wsdlUrl['host'])) ? $wsdlUrl['host'] : '')
246 18
            .((isset($wsdlUrl['port'])) ? ':'.$wsdlUrl['port'] : '')
247 18
            .((isset($wsdlUrl['path'])) ? $wsdlUrl['path'] : '');
248
    }
249
250
    /**
251
     * @param array $mockRequests
252
     */
253 12
    public function setMockRequests(array $mockRequests)
254
    {
255 12
        $this->mockRequests = $mockRequests;
256 12
    }
257
258
    /**
259
     * @param array $mockResponses
260
     */
261 12
    public function setMockResponses(array $mockResponses)
262
    {
263 12
        $this->mockResponses = $mockResponses;
264 12
    }
265
266
    /**
267
     * @param EventDispatcherInterface $dispatcher
268
     * @required
269
     */
270 18
    public function setDispatcher(EventDispatcherInterface $dispatcher)
271
    {
272 18
        if (class_exists(LegacyEventDispatcherProxy::class)) {
273 6
            $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
274
        } else {
275 12
            $this->dispatcher = $dispatcher;
276
        }
277 18
    }
278
279
    /**
280
     * @param $function_name
281
     * @param $arguments
282
     * @param $e
283
     */
284 3
    protected function handleFault($function_name, $arguments, $e): void
285
    {
286 3
        $request = $this->__getLastRequest();
287 3
        if ($request === null) { //only dispatch this when no request was fired
288 3
            $request = implode(' ', $arguments);
289 3
            $id      = Uuid::uuid1();
290 3
            $this->faultCall($id->toString(), $function_name, $request, $e);
291
        }
292
293 3
        throw $e;
294
    }
295
296
    /**
297
     * @param Event $event
298
     * @param string $eventName
299
     */
300 9
    private function dispatch(Event $event, $eventName)
301
    {
302 9
        if (null === $this->dispatcher) {
303
            return;
304
        }
305
306
        // LegacyEventDispatcherProxy exists in Symfony >= 4.3
307 9
        if (class_exists(LegacyEventDispatcherProxy::class)) {
308
            // New Symfony 4.3 EventDispatcher signature
309 3
            $this->dispatcher->dispatch($event, $eventName);
0 ignored issues
show
Documentation introduced by
$event is of type object<Freshcells\SoapClientBundle\Event\Event>, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
310
        } else {
311
            // Old EventDispatcher signature
312 6
            $this->dispatcher->dispatch($eventName, $event);
0 ignored issues
show
Documentation introduced by
$eventName is of type string, but the function expects a object<Symfony\Contracts\EventDispatcher\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$event is of type object<Freshcells\SoapClientBundle\Event\Event>, but the function expects a null|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
313
        }
314 9
    }
315
}
316