Completed
Push — master ( 9de6f9...a4eb35 )
by Gareth
03:41
created

ExchangeWebServices   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 477
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 64.84%

Importance

Changes 13
Bugs 1 Features 2
Metric Value
wmc 55
c 13
b 1
f 2
lcom 1
cbo 10
dl 0
loc 477
ccs 118
cts 182
cp 0.6484
rs 6.8

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getClient() 0 4 1
A setClient() 0 6 1
B cleanServerUrl() 0 21 5
A getPrimarySmtpMailbox() 0 4 1
A getPrimarySmtpEmailAddress() 0 8 2
A setPrimarySmtpEmailAddress() 0 8 1
A setTimezone() 0 4 1
A getVersion() 0 4 1
A getServer() 0 4 1
A __construct() 0 12 2
A fromUsernameAndPassword() 0 8 1
A fromCallbackToken() 0 8 1
B createClient() 0 32 3
A __call() 0 8 1
A processResponse() 0 19 3
B drillDownResponseLevels() 0 24 6
B getItemsFromResponse() 0 22 5
B handleNonSuccessfulResponses() 0 18 6
C buildMiddlewareStack() 0 66 10
B executeMiddlewareStack() 0 25 3

How to fix   Complexity   

Complex Class

Complex classes like ExchangeWebServices often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExchangeWebServices, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Contains ExchangeWebServices.
4
 */
5
6
7
namespace garethp\ews\API;
8
9
use garethp\ews\API\Exception\ExchangeException;
10
use garethp\ews\API\Exception\NoResponseReturnedException;
11
use garethp\ews\API\Exception\ServiceUnavailableException;
12
use garethp\ews\API\Exception\UnauthorizedException;
13
use garethp\ews\API\Message;
14
use garethp\ews\API\Type\EmailAddressType;
15
use garethp\ews\API\Type\FindFolderParentType;
16
use garethp\ews\API\Type\FindItemParentType;
17
18
/**
19
 * Base class of the Exchange Web Services application.
20
 *
21
 *
22
 *
23
 * @package php-ews\Client
24
 *
25
 * @method Type AddDelegate($request)
26
 * @method Type ApplyConversationAction($request)
27
 * @method Type ConvertId($request)
28
 * @method Type CopyFolder($request)
29
 * @method Type CopyItem($request)
30
 * @method Type CreateAttachment($request)
31
 * @method Type CreateFolder($request)
32
 * @method Type CreateItem($request)
33
 * @method Type CreateManagedFolder($request)
34
 * @method Type CreateUserConfiguration($request)
35
 * @method Type DeleteAttachment($request)
36
 * @method Type DeleteFolder($request)
37
 * @method Type DeleteItem($request)
38
 * @method Type DeleteUserConfiguration($request)
39
 * @method Type DisconnectPhoneCall($request)
40
 * @method Type EmptyFolder($request)
41
 * @method Type ExpandDL($request)
42
 * @method Type ExportItems($request)
43
 * @method Type FindConversation($request)
44
 * @method Type FindFolder($request)
45
 * @method Type FindItem($request)
46
 * @method Type FindMessageTrackingReport($request)
47
 * @method Type GetAttachment($request)
48
 * @method Type GetDelegate($request)
49
 * @method Type GetEvents($request)
50
 * @method Type GetFolder($request)
51
 * @method Type GetInboxRules($request)
52
 * @method Type GetItem($request)
53
 * @method Type GetMailTips($request)
54
 * @method Type GetMessageTrackingReport($request)
55
 * @method Type GetPasswordExpirationDate($request)
56
 * @method Type GetPhoneCallInformation($request)
57
 * @method Type GetRoomLists($request)
58
 * @method Type GetRooms($request)
59
 * @method Type GetServerTimeZones($request)
60
 * @method Type GetServiceConfiguration($request)
61
 * @method Type GetSharingFolder($request)
62
 * @method Type GetSharingMetadata($request)
63
 * @method Type GetStreamingEvents($request)
64
 * @method Type GetUserAvailability($request)
65
 * @method Type GetUserConfiguration($request)
66
 * @method Type GetUserOofSettings($request)
67
 * @method Type MoveFolder($request)
68
 * @method Type MoveItem($request)
69
 * @method Type PlayOnPhone($request)
70
 * @method Type RefreshSharingFolder($request)
71
 * @method Type RemoveDelegate($request)
72
 * @method Type ResolveNames($request)
73
 * @method Type SendItem($request)
74
 * @method Type SetUserOofSettings($request)
75
 * @method Type Subscribe($request)
76
 * @method Type SyncFolderHierarchy($request)
77
 * @method Type SyncFolderItems($request)
78
 * @method Type Unsubscribe($request)
79
 * @method Type UpdateDelegate($request)
80
 * @method Type UpdateFolder($request)
81
 * @method Type UpdateInboxRules($request)
82
 * @method Type UpdateItem($request)
83
 * @method Type UpdateUserConfiguration($request)
84
 * @method Type UploadItems($request)
85
 */
86
class ExchangeWebServices
87
{
88
    const VERSION_2007 = 'Exchange2007';
89
90
    const VERSION_2007_SP1 = 'Exchange2007_SP1';
91
92
    const VERSION_2010 = 'Exchange2010';
93
94
    const VERSION_2010_SP1 = 'Exchange2010_SP1';
95
96
    const VERSION_2010_SP2 = 'Exchange2010_SP2';
97
98
    const VERSION_2013 = 'Exchange2013';
99
100
    const VERSION_2013_SP1 = 'Exchange2013_SP1';
101
102
    /**
103
     * Password to use when connecting to the Exchange server.
104
     *
105
     * @var string
106
     */
107
    protected $password = null;
108
109
    /**
110
     * Location of the Exchange server.
111
     *
112
     * @var string
113
     */
114
    protected $server = null;
115
116
    /**
117
     * SOAP client used to make the request
118
     *
119
     * @var NTLMSoapClient
120
     */
121
    protected $soap = null;
122
123
    /**
124
     * Username to use when connecting to the Exchange server.
125
     *
126
     * @var string
127
     */
128
    protected $username = null;
129
130
    /**
131
     * @var EmailAddressType
132
     */
133
    protected $primarySmtpMailbox = null;
134
135
    protected static $middlewareStack = false;
136
137
    /**
138
     * A setting to check whether or not responses should be drilled down before being
139
     * returned. Setting this to false
140
     * will return the raw responses without any filtering
141
     *
142
     * @var bool
143
     */
144
    protected $drillDownResponses = true;
145
146
    /**
147
     * Miscrosoft Exchange version that we are going to connect to
148
     *
149
     * @var string
150
     */
151
    protected $version = null;
152
153
    protected $options = null;
154
155
    /**
156
     * The timezone for the client
157
     *
158
     * @var bool
159
     */
160
    protected $timezone = false;
161
162
    /**
163
     * @return EmailAddressType
164
     */
165 25
    public function getPrimarySmtpMailbox()
166
    {
167 25
        return $this->primarySmtpMailbox;
168
    }
169
170 1
    public function getPrimarySmtpEmailAddress()
171
    {
172 1
        if ($this->primarySmtpMailbox == null) {
173 1
            return null;
174
        }
175
176 1
        return $this->primarySmtpMailbox->getEmailAddress();
177
    }
178
179 2
    public function setPrimarySmtpEmailAddress($emailAddress)
180
    {
181 2
        $mailbox = new EmailAddressType();
182 2
        $mailbox->setEmailAddress($emailAddress);
183 2
        $this->primarySmtpMailbox = $mailbox;
184
185 2
        return $this;
186
    }
187
188
    /**
189
     * @param boolean $timezone
190
     */
191
    public function setTimezone($timezone)
192
    {
193
        $this->timezone = $timezone;
194
    }
195
196
    /**
197
     * @return string
198
     */
199
    public function getVersion()
200
    {
201
        return $this->version;
202
    }
203
204
    /**
205
     * @return string
206
     */
207
    public function getServer()
208
    {
209
        return $this->server;
210
    }
211
212
    /**
213
     * Constructor for the ExchangeWebServices class
214
     *
215
     * @param string $server
216
     * @param string $username
217
     * @param string $password
218
     * @param array $options
219
     */
220 33
    protected function __construct($server = null, $username = null, $password = null, $options = array())
221
    {
222 33
        if ($server !== null) {
223
            $this->createClient(
224
                $server,
225
                ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password),
226
                $options
227
            );
228
        }
229
230 33
        $this->buildMiddlewareStack();
231 33
    }
232
233 32
    public static function fromUsernameAndPassword($server, $username, $password, $options)
234
    {
235 32
        $self = new self();
236 32
        $self->createClient($server, ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password), $options);
237 32
        $self->options = $options;
238
239 32
        return $self;
240
    }
241
242 1
    public static function fromCallbackToken($server, $token, $options)
243
    {
244 1
        $self = new self();
245 1
        $self->createClient($server, ExchangeWebServicesAuth::fromCallbackToken($token), $options);
246 1
        $self->options = $options;
247
248 1
        return $self;
249
    }
250
251 33
    protected function createClient($server, $auth, $options)
252
    {
253 33
        $location = 'https://' . $this->cleanServerUrl($server) . '/EWS/Exchange.asmx';
254
255 33
        $options = array_replace_recursive([
256 33
            'version' => self::VERSION_2007,
257 33
            'trace' => 1,
258 33
            'exceptions' => true,
259 33
            'classmap' => ClassMap::getClassMap(),
260
            'drillDownResponses' => true
261 33
        ], $options);
262
263 33
        $this->server = $server;
264 33
        $this->version = $options['version'];
265
266 33
        $this->soap = new NTLMSoapClient(
267 33
            $location,
268 33
            $auth,
269 33
            dirname(__FILE__) . '/../../Resources/wsdl/services.wsdl',
270
            $options
271 33
        );
272
273 33
        if (isset($options['primarySmtpEmailAddress'])) {
274 1
            $this->setPrimarySmtpEmailAddress($options['primarySmtpEmailAddress']);
275 1
        }
276
277 33
        if (isset($options['impersonation'])) {
278 1
            $this->setPrimarySmtpEmailAddress($options['impersonation']);
279 1
        }
280
281 33
        $this->drillDownResponses = $options['drillDownResponses'];
282 33
    }
283
284
    /**
285
     * @codeCoverageIgnore
286
     *
287
     * @param $name
288
     * @param $arguments
289
     * @return Type
290
     * @throws \garethp\ews\API\Exception
291
     */
292
    public function __call($name, $arguments)
293
    {
294
        $request = MiddlewareRequest::newRequest($name, $arguments, $this->options);
295
        $response = $this->executeMiddlewareStack(self::$middlewareStack, $request);
0 ignored issues
show
Documentation introduced by
self::$middlewareStack is of type boolean, but the function expects a array.

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...
296
        $response = $response->getResponse();
297
        return $response;
298
        return $this->processResponse($response);
0 ignored issues
show
Unused Code introduced by
return $this->processResponse($response); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
299
    }
300
301
    /**
302
     * Returns the SOAP Client that may be used to make calls against the server
303
     *
304
     * @return NTLMSoapClient
305
     */
306 31
    public function getClient()
307
    {
308 31
        return $this->soap;
309
    }
310
311
    /**
312
     * Sets the client
313
     *
314
     * @param NTLMSoapClient $client
315
     * @return $this
316
     */
317 2
    public function setClient($client)
318
    {
319 2
        $this->soap = $client;
320
321 2
        return $this;
322
    }
323
324
    /**
325
     * Cleans the server URL for usage
326
     *
327
     * @param $server
328
     * @return string
329
     */
330 40
    public function cleanServerUrl($server)
331
    {
332 40
        $url = parse_url($server);
333 40
        if (!isset($url['host']) && isset($url['path'])) {
334 35
            $url['host'] = $url['path'];
335 35
            unset($url['path']);
336 35
        }
337
338 40
        $server = $url['host'];
339 40
        if (isset($url['port'])) {
340 2
            $server .= ':' . $url['port'];
341 2
        }
342
343 40
        if (isset($url['path'])) {
344 4
            $server .= $url['path'];
345 4
        }
346
347 40
        $server = rtrim($server, "/");
348
349 40
        return $server;
350
    }
351
352
    /**
353
     * Process a response to verify that it succeeded and take the appropriate
354
     * action
355
     *
356
     * @param \garethp\ews\API\Message\BaseResponseMessageType $response
357
     * @return Type[]
358
     * @throws \garethp\ews\API\Exception
359
     */
360 29
    protected function processResponse($response)
361
    {
362
        // If the soap call failed then we need to thow an exception.
363 29
        $code = $this->getClient()->getResponseCode();
364 29
        $this->handleNonSuccessfulResponses($response, $code);
365
366
        if (!$this->drillDownResponses) {
367
            return $response;
368
        }
369
370
        if (!$response->exists('responseMessages')) {
371
            return $response;
372
        }
373
374
        $response = $response->getResponseMessages();
375
        $response = $this->drillDownResponseLevels($response);
376
377
        return $response;
378
    }
379
380
    /**
381
     * @param $response
382
     * @return array
383
     * @throws \garethp\ews\API\Exception
384
     */
385
    public function drillDownResponseLevels($response)
386
    {
387
        $items = $this->getItemsFromResponse($response);
388
389
        if (count($items) == 1) {
390
            reset($items);
391
            $key = key($items);
392
            $methodName = "get$key";
393
            $response = $response->$methodName();
394
395
            return $this->drillDownResponseLevels($response);
396
        }
397
398
        if (is_array($items) && isset($items[1]) && $items[1] instanceof Message\ResponseMessageType) {
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Message\ResponseMessageType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
399
            $response = array();
400
            foreach ($items as $responseItem) {
401
                $response[] = $this->drillDownResponseLevels($responseItem);
402
            }
403
404
            return $response;
405
        }
406
407
        return $response;
408
    }
409
410
    /**
411
     * @param $response
412
     * @return array
413
     * @throws ExchangeException
414
     */
415
    protected function getItemsFromResponse($response)
416
    {
417
        $items = array();
418
        if ($response instanceof Type) {
419
            $items = $response->getNonNullItems();
420
        }
421
422
        if (is_array($response)) {
423
            $items = $response;
424
        }
425
426
        if ($response instanceof Message\ResponseMessageType) {
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Message\ResponseMessageType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
427
            if ($response->getResponseClass() !== "Success") {
428
                throw new ExchangeException($response->getMessageText());
429
            }
430
431
            unset($items['responseClass']);
432
            unset($items['responseCode']);
433
        }
434
435
        return $items;
436
    }
437
438
    /**
439
     * @param Message\BaseResponseMessageType $response
440
     * @param $code
441
     * @throws ExchangeException
442
     * @throws NoResponseReturnedException
443
     * @throws ServiceUnavailableException
444
     * @throws UnauthorizedException
445
     */
446 29
    protected function handleNonSuccessfulResponses($response, $code)
447
    {
448 29
        if ($code == 401) {
449 27
            throw new UnauthorizedException();
450
        }
451
452 2
        if ($code == 503) {
453
            throw new ServiceUnavailableException();
454
        }
455
456 2
        if ($code >= 300) {
457 2
            throw new ExchangeException('SOAP client returned status of ' . $code, $code);
458
        }
459
460
        if (empty($response) || empty($response->getNonNullResponseMessages())) {
461
            throw new NoResponseReturnedException();
462
        }
463
    }
464
465 33
    protected function buildMiddlewareStack()
466
    {
467 33
        if (self::$middlewareStack === false) {
468 1
            $ews = $this;
469
470 1
            self::$middlewareStack = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array(function (\garethp... return $response; }) of type array<integer,object<Clo..."4":"object<Closure>"}> is incompatible with the declared type boolean of property $middlewareStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
471
                //Make the actual SOAP call
472
                function (MiddlewareRequest $request, callable $next = null) use ($ews) {
0 ignored issues
show
Unused Code introduced by
The parameter $next is not used and could be removed.

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

Loading history...
473 27
                    $client = $ews->getClient();
474 27
                    $response = $client->__call($request->getName(), $request->getArguments());
0 ignored issues
show
Documentation introduced by
$request->getArguments() is of type array<integer,?,{"0":"?"}>, but the function expects a 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...
475 27
                    $response = MiddlewareResponse::newResponse($response);
476
477 27
                    return $response;
478 1
                },
479
480
                //Transform an objcet of type Type to an XML Object
481
                function (MiddlewareRequest $request, callable $next) {
482 27
                    if ($request->getRequest() instanceof Type) {
483 27
                        $request->setRequest($request->getRequest()->toXmlObject());
484 27
                    }
485
486 27
                    return $next($request);
487 1
                },
488
489
                //The SyncScope option isn't available for Exchange 2007 SP1 and below
490
                function (MiddlewareRequest $request, callable $next) {
491 27
                    $options = $request->getOptions();
492 27
                    $version2007SP1 = ($options['version'] == ExchangeWebServices::VERSION_2007
493 27
                        || $options['version'] == ExchangeWebServices::VERSION_2007_SP1);
494
495 27
                    $requestObj = $request->getName();
496
497 27
                    if ($request->getName() == "SyncFolderItems" && $version2007SP1 && isset($requestObj->SyncScope)) {
498
                        unset($requestObj->SyncScope);
499
                        $request->setRequest($requestObj);
500
                    }
501
502 27
                    return $next($request);
503 1
                },
504
505
                //Add response processing
506
                function (MiddlewareRequest $request, callable $next) use ($ews) {
507 27
                    $response = $next($request);
508
509 27
                    $response->setResponse($ews->processResponse($response->getResponse()));
510
511
                    return $response;
512 1
                },
513
514
                //Adds last request to FindFolder and FindItem responses
515
                function (MiddlewareRequest $request, callable $next) {
516 27
                    $response = $next($request);
517
518
                    $responseObject = $response->getResponse();
519
                    if (($responseObject instanceof FindItemParentType
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Type\FindItemParentType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
520
                            || $responseObject instanceof FindFolderParentType)
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Type\FindFolderParentType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
521
                        && !$responseObject->isIncludesLastItemInRange()) {
522
                        $responseObject->setLastRequest($request->getRequest());
523
                        $response->setResponse($responseObject);
524
                    }
525
526
                    return $response;
527
                }
528 1
            ];
529 1
        }
530 33
    }
531
532
    /**
533
     * @param array $middlewareStack
534
     * @param MiddlewareRequest $request
535
     * @return MiddlewareResponse
536
     */
537 27
    protected function executeMiddlewareStack(array $middlewareStack, MiddlewareRequest $request)
538
    {
539 27
        $newStack = [];
540 27
        foreach ($middlewareStack as $key => $current) {
541
            /** @var $current callable */
542 27
            if ($key == 0) {
543
                $last = function (MiddlewareRequest $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

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

Loading history...
544 27
                };
545 27
            } else {
546 27
                $last = $newStack[$key - 1];
547
            }
548
549 27
            $newStack[] = function (MiddlewareRequest $request) use ($current, $last) {
550 27
                return $current($request, $last);
551
            };
552 27
        }
553
554
        /** @var $newStack callable[] */
555 27
        $newStack = array_reverse($newStack);
556
557 27
        $top = $newStack[0];
558
559
        /** @var $top callable */
560 27
        return $top($request);
561
    }
562
}
563