Completed
Push — master ( 4e7de2...1dbfef )
by Ivo
15s queued 12s
created

SoapClient::dispatch()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 6
cts 7
cp 0.8571
rs 9.7666
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3.0261
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 18
            if (!isset($options['location'])) {
69 18
                $options['location'] = $this->resolveLocation($wsdl);
70
            }
71
        }
72
73 18
        $this->SoapClient($wsdl, $options);
74 18
        $this->options = $options;
75 18
    }
76
77 6
    public function getOptions(): array
78
    {
79 6
        return $this->options;
80
    }
81
82 9
    public function __call($function_name, $arguments)
83
    {
84
        try {
85 9
            $response = parent::__call($function_name, $arguments);
86
            //works only with 'exceptions' => false, we always throw
87 6
            if (is_soap_fault($response)) {
88 6
                throw $response;
89
            }
90 3
        } catch (\Exception $e) {
91 3
            $this->handleFault($function_name, $arguments, $e);
92
        }
93
94 6
        return $response;
95
    }
96
97
    public function __soapCall(
98
        $function_name,
99
        $arguments,
100
        $options = null,
101
        $input_headers = null,
102
        &$output_headers = null
103
    ) {
104
        try {
105
            $response = parent::__soapCall($function_name, $arguments, $options, $input_headers, $output_headers);
106
            //works only with 'exceptions' => false, we always throw
107
            if (is_soap_fault($response)) {
108
                throw $response;
109
            }
110
        } catch (\Exception $e) {
111
            $this->handleFault($function_name, $arguments, $e);
112
        }
113
114
        return $response;
115
    }
116
117
    /**
118
     * @param string $request
119
     * @param string $location
120
     * @param string $action
121
     * @param int $version
122
     * @param null $one_way
123
     * @return bool|string
124
     */
125 6
    public function __doRequest($request, $location, $action, $version, $one_way = null)
126
    {
127 6
        $id = Uuid::uuid1();
128
129 6
        foreach ($this->mockRequests as $key => $mockRequest) {
130 6
            if (is_string($key)) {
131 6
                if (strrpos($action, $key) !== false) {
132 6
                    $request = file_get_contents($mockRequest);
133 6
                    break;
134
                }
135
            } else {
136
                if (is_callable($mockRequest)) {
137
                    if ($requestFilePath = $mockRequest($request, $location, $action, $version, $one_way)) {
138
                        $request = file_get_contents($requestFilePath);
139
                        break;
140
                    }
141
                }
142
            }
143
        }
144
145 6
        $this->preCall($id->toString(), (string)$action, $request);
146
147 6
        foreach ($this->mockResponses as $key => $mockResponse) {
148 6
            if (is_string($key)) {
149 3
                if (strrpos($action, $key) !== false) {
150 3
                    $response = file_get_contents($mockResponse);
151
152 3
                    $this->postCall($id->toString(), $action, $response);
153
154 3
                    return $response;
155
                }
156
            } else {
157 3
                if (is_callable($mockResponse)) {
158 3
                    if ($responseFilePath = $mockResponse($request, $location, $action, $version, $one_way)) {
159 3
                        $response = file_get_contents($responseFilePath);
160
161 3
                        $this->postCall($id->toString(), $action, $response);
162
163 3
                        return $response;
164
                    }
165
                }
166
            }
167
        }
168
169
        /* workaround for working timeout */
170
        $socketTimeout = false;
171
        if (isset($this->options['connection_timeout'])
172
            && (int)$this->options['connection_timeout'] > (int)ini_get('default_socket_timeout')
173
        ) {
174
            $socketTimeout = ini_set('default_socket_timeout', $this->options['connection_timeout']);
175
        }
176
177
        $response = parent::__doRequest($request, $location, $action, $version, $one_way);
178
179
        $this->postCall($id->toString(), (string)$action, $response);
180
181
        if ($socketTimeout !== false) {
182
            ini_set('default_socket_timeout', $socketTimeout);
183
        }
184
185
        return $response;
186
    }
187
188
    /**
189
     * Triggered before a request is executed
190
     *
191
     * @param string $id
192
     * @param string $resource
193
     * @param string $requestContent
194
     */
195 6
    protected function preCall(string $id, string $resource, string $requestContent = null)
196
    {
197 6
        $this->dispatch(new RequestEvent($id, $resource, $requestContent), Events::REQUEST);
198 6
    }
199
200
    /**
201
     * @param string $id
202
     * @param string $resource
203
     * @param string $response
204
     */
205 6
    protected function postCall(string $id, string $resource, string $response = null)
206
    {
207 6
        $responseEvent = new ResponseEvent(
208 6
            $id,
209 6
            $resource,
210 6
            $this->__getLastRequest(),
211 6
            $this->__getLastRequestHeaders(),
212 6
            $response,
213 6
            $this->__getLastResponseHeaders()
214
        );
215 6
        $this->dispatch($responseEvent, Events::RESPONSE);
216 6
    }
217
218
    /**
219
     * @param string $id
220
     * @param string $resource
221
     * @param string $requestContent
222
     * @param \Exception $exception
223
     */
224 3
    protected function faultCall(string $id, string $resource, string $requestContent, \Exception $exception)
225
    {
226 3
        $this->dispatch(
227 3
            new FaultEvent($id, $exception, new RequestEvent($id, $resource, $requestContent)),
228 3
            Events::FAULT
229
        );
230 3
    }
231
232
    /**
233
     * @param string $wsdl
234
     * @return string
235
     */
236 18
    protected function resolveLocation($wsdl)
237
    {
238 18
        $wsdlUrl = parse_url($wsdl);
239
240 18
        return ((isset($wsdlUrl['scheme'])) ? $wsdlUrl['scheme'].'://' : '')
241 18
            .((isset($wsdlUrl['user'])) ? $wsdlUrl['user']
242 18
                .((isset($wsdlUrl['pass'])) ? ':'.$wsdlUrl['pass'] : '').'@' : '')
243 18
            .((isset($wsdlUrl['host'])) ? $wsdlUrl['host'] : '')
244 18
            .((isset($wsdlUrl['port'])) ? ':'.$wsdlUrl['port'] : '')
245 18
            .((isset($wsdlUrl['path'])) ? $wsdlUrl['path'] : '');
246
    }
247
248
    /**
249
     * @param array $mockRequests
250
     */
251 12
    public function setMockRequests(array $mockRequests)
252
    {
253 12
        $this->mockRequests = $mockRequests;
254 12
    }
255
256
    /**
257
     * @param array $mockResponses
258
     */
259 12
    public function setMockResponses(array $mockResponses)
260
    {
261 12
        $this->mockResponses = $mockResponses;
262 12
    }
263
264
    /**
265
     * @param EventDispatcherInterface $dispatcher
266
     * @required
267
     */
268 18
    public function setDispatcher(EventDispatcherInterface $dispatcher)
269
    {
270 18
        if (class_exists(LegacyEventDispatcherProxy::class)) {
271 12
            $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
272
        } else {
273 6
            $this->dispatcher = $dispatcher;
274
        }
275 18
    }
276
277
    /**
278
     * @param $function_name
279
     * @param $arguments
280
     * @param $e
281
     */
282 3
    protected function handleFault($function_name, $arguments, $e): void
283
    {
284 3
        $request = $this->__getLastRequest();
285 3
        if ($request === null) { //only dispatch this when no request was fired
286 3
            $request = implode(' ', $arguments);
287 3
            $id      = Uuid::uuid1();
288 3
            $this->faultCall($id->toString(), $function_name, $request, $e);
289
        }
290
291 3
        throw $e;
292
    }
293
294
    /**
295
     * @param Event  $event
296
     * @param string $eventName
297
     */
298 9
    private function dispatch(Event $event, $eventName)
299
    {
300 9
        if (null === $this->dispatcher) {
301
            return;
302
        }
303
304
        // LegacyEventDispatcherProxy exists in Symfony >= 4.3
305 9
        if (class_exists(LegacyEventDispatcherProxy::class)) {
306
            // New Symfony 4.3 EventDispatcher signature
307 6
            $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...
308
        } else {
309
            // Old EventDispatcher signature
310 3
            $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...
311
        }
312 9
    }
313
}
314