Completed
Push — master ( e29ab2...ab64a4 )
by Gareth
06:05 queued 02:53
created

API::getServerTimezones()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 19
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 12
Metric Value
dl 19
loc 19
ccs 0
cts 12
cp 0
rs 9.4285
cc 3
eloc 10
nc 4
nop 2
crap 12
1
<?php
2
3
namespace garethp\ews;
4
5
use garethp\ews\API\Enumeration\DictionaryURIType;
6
use garethp\ews\API\Enumeration\UnindexedFieldURIType;
7
use garethp\ews\API\Exception\ExchangeException;
8
use garethp\ews\API\ExchangeWebServices;
9
use garethp\ews\API\Message\GetServerTimeZonesType;
10
use garethp\ews\API\Message\SyncFolderItemsResponseMessageType;
11
use garethp\ews\API\Type;
12
use garethp\ews\Calendar\CalendarAPI;
13
use garethp\ews\Mail\MailAPI;
14
15
/**
16
 * A base class for APIs
17
 *
18
 * Class BaseAPI
19
 * @package garethp\ews
20
 */
21
class API
22
{
23
    protected static $defaultClientOptions = array(
24
        'version' => ExchangeWebServices::VERSION_2010
25
    );
26
27 30
    public function __construct(ExchangeWebServices $client = null)
28
    {
29 30
        if ($client) {
30 18
            $this->setClient($client);
31 18
        }
32 30
    }
33
34
    /**
35
     * @return Type\EmailAddressType
36
     */
37 21
    public function getPrimarySmtpMailbox()
38
    {
39 21
        return $this->getClient()->getPrimarySmtpMailbox();
40
    }
41
42
    private $unIndexedFieldUris = array();
43
    private $dictionaryFieldUris = array();
44
45
    /**
46
     * Storing the API client
47
     * @var ExchangeWebServices
48
     */
49
    private $client;
50
51
    /**
52
     * @param $className
53
     * @return array
54
     */
55 5
    public function getFieldUrisFromClass($className)
56
    {
57
        //So, since we have to pass in URI's of everything we update, we need to fetch them
58 5
        $reflection = new \ReflectionClass($className);
59 5
        $constants = $reflection->getConstants();
60 5
        $constantsFound = array();
61
62
        //Loop through all URI's to list them in an array
63 5
        foreach ($constants as $constant) {
64 5
            $exploded = explode(":", $constant);
65 5
            if (count($exploded) == 1) {
66 5
                $exploded = ['item', $exploded[0]];
67 5
            }
68
69 5
            $name = strtolower($exploded[1]);
70 5
            $category = strtolower($exploded[0]);
71
72 5
            if (!isset($constantsFound[$name])) {
73 5
                $constantsFound[$name] = array();
74 5
            }
75 5
            if (count($exploded) == 3) {
76 5
                $entry = strtolower($exploded[2]);
77 5
                if (!isset($constantsFound[$name])) {
78
                    $constantsFound[$name][$category] = array();
79
                }
80
81 5
                $constantsFound[$name][$category][$entry] = $constant;
82 5
            } else {
83 5
                $constantsFound[$name][$category] = $constant;
84
            }
85 5
        }
86
87 5
        return $constantsFound;
88
    }
89
90 5
    public function setupFieldUris()
91
    {
92 5
        $this->unIndexedFieldUris = $this
93 5
            ->getFieldUrisFromClass(UnindexedFieldURIType::class);
94
95 5
        $this->dictionaryFieldUris = $this
96 5
            ->getFieldUrisFromClass(DictionaryURIType::class);
97 5
    }
98
99 5
    public function getFieldUriByName($fieldName, $preference = 'item')
100
    {
101 5
        $fieldName = strtolower($fieldName);
102 5
        $preference = strtolower($preference);
103
104 5
        if (empty($this->unIndexedFieldUris)) {
105 5
            $this->setupFieldUris();
106 5
        }
107
108 5
        if (!isset($this->unIndexedFieldUris[$fieldName])) {
109 1
            return false;
110
        }
111
112 5
        if (!isset($this->unIndexedFieldUris[$fieldName][$preference])) {
113 1
            $preference = 'item';
114 1
        }
115
116 5
        if (!isset($this->unIndexedFieldUris[$fieldName][$preference])) {
117 2
            throw new ExchangeException("Could not find uri $preference:$fieldName");
118
        }
119
120 5
        return $this->unIndexedFieldUris[$fieldName][$preference];
121
    }
122
123 1
    public function getIndexedFieldUriByName($fieldName, $preference = 'item', $entryKey = false)
124
    {
125 1
        $fieldName = strtolower($fieldName);
126 1
        $preference = strtolower($preference);
127
128 1
        if (empty($this->dictionaryFieldUris)) {
129
            $this->setupFieldUris();
130
        }
131
132 1
        if (!isset($this->dictionaryFieldUris[$fieldName])) {
133
            return false;
134
        }
135
136 1
        if (!isset($this->dictionaryFieldUris[$fieldName][$preference])) {
137
            $preference = 'item';
138
        }
139
140 1
        if (!isset($this->dictionaryFieldUris[$fieldName][$preference])) {
141
            throw new ExchangeException("Could not find uri $preference:$fieldName");
142
        }
143
144 1
        $fieldUri = $this->dictionaryFieldUris[$fieldName][$preference];
145 1
        if (is_array($fieldUri)) {
146 1
            if (!$entryKey) {
147
                throw new ExchangeException("Please enter a specific entry key for this fieldURI");
148
            }
149
150 1
            $entryKey = strtolower($entryKey);
151 1
            if (!isset($fieldUri[$entryKey])) {
152
                throw new ExchangeException("Could not find uri $preference:$fieldName:$entryKey");
153
            }
154
155 1
            $fieldUri = $fieldUri[$entryKey];
156 1
        }
157
158 1
        return $fieldUri;
159
    }
160
161
    /**
162
     * Get a calendar item
163
     *
164
     * @param string $name
165
     * @return CalendarAPI
166
     */
167 6
    public function getCalendar($name = null)
168
    {
169 6
        $calendar = new CalendarAPI();
170 6
        $calendar->setClient($this->getClient());
171 6
        $calendar->pickCalendar($name);
172
173 6
        return $calendar;
174
    }
175
176
    /**
177
     * @param string $folderName
178
     * @return MailAPI
179
     */
180 6
    public function getMailbox($folderName = null)
181
    {
182 6
        $mailApi = new MailAPI();
183 6
        $mailApi->setClient($this->getClient());
184 6
        $mailApi->pickMailFolder($folderName);
185
186 6
        return $mailApi;
187
    }
188
189
    /**
190
     * Set the API client
191
     *
192
     * @param ExchangeWebServices $client
193
     * @return $this
194
     */
195 30
    public function setClient($client)
196
    {
197 30
        $this->client = $client;
198 30
        return $this;
199
    }
200
201
    /**
202
     * Get the API client
203
     *
204
     * @return ExchangeWebServices
205
     */
206 28
    public function getClient()
207
    {
208 28
        return $this->client;
209
    }
210
211
    /**
212
     * Instantiate and set a client (ExchangeWebServices) based on the parameters given
213
     *
214
     * @deprecated Since 0.6.3
215
     * @param $server
216
     * @param $username
217
     * @param $password
218
     * @param array $options
219
     * @return $this
220
     */
221
    public function buildClient(
222
        $server,
223
        $username,
224
        $password,
225
        $options = []
226
    ) {
227
        $this->setClient(ExchangeWebServices::fromUsernameAndPassword(
228
            $server,
229
            $username,
230
            $password,
231
            array_replace_recursive(self::$defaultClientOptions, $options)
232
        ));
233
    }
234
235 17
    public static function withUsernameAndPassword($server, $username, $password, $options = [])
236
    {
237 17
        return new static(ExchangeWebServices::fromUsernameAndPassword(
238 17
            $server,
239 17
            $username,
240 17
            $password,
241 17
            array_replace_recursive(self::$defaultClientOptions, $options)
242 17
        ));
243
    }
244
245 1
    public static function withCallbackToken($server, $token, $options = [])
246
    {
247 1
        return new static(ExchangeWebServices::fromCallbackToken(
248 1
            $server,
249 1
            $token,
250 1
            array_replace_recursive(self::$defaultClientOptions, $options)
251 1
        ));
252
    }
253
254 1
    public function getPrimarySmptEmailAddress()
255
    {
256 1
        if ($this->getPrimarySmtpMailbox() == null) {
257 1
            return null;
258
        }
259
260 1
        return $this->getPrimarySmtpMailbox()->getEmailAddress();
261
    }
262
263 1
    public function setPrimarySmtpEmailAddress($emailAddress)
264
    {
265 1
        $this->getClient()->setPrimarySmtpEmailAddress($emailAddress);
266
267 1
        return $this;
268
    }
269
270
    /**
271
     * Create items through the API client
272
     *
273
     * @param $items
274
     * @param array $options
275
     * @return Type
276
     */
277 13 View Code Duplication
    public function createItems($items, $options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
    {
279 13
        if (!is_array($items)) {
280
            $items = array($items);
281
        }
282
283
        $request = array(
284
            'Items' => $items
285 13
        );
286
287 13
        $request = array_replace_recursive($request, $options);
288 13
        $request = Type::buildFromArray($request);
289
290 13
        $response = $this->getClient()->CreateItem($request);
291
292 13
        return $response;
293
    }
294
295 3
    public function updateItems($items, $options = array())
296
    {
297
        $request = array(
298 3
            'ItemChanges' => $items,
299 3
            'MessageDisposition' => 'SaveOnly',
300
            'ConflictResolution' => 'AlwaysOverwrite'
301 3
        );
302
303 3
        $request = array_replace_recursive($request, $options);
304
305 3
        $request = Type::buildFromArray($request);
306
307 3
        return $this->getClient()->UpdateItem($request)->getItems();
0 ignored issues
show
Documentation Bug introduced by
The method getItems does not exist on object<garethp\ews\API\Type>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
308
    }
309
310 3
    protected function getFieldURI($uriType, $key = null, $value = null)
311
    {
312 3
        if (strpos($key, ':') !== false) {
313
            try {
314 1
                $fieldUriValue = substr($key, 0, strpos($key, ':'));
315
316 1
                list ($key, $index) = explode(':', $key);
317
318 1
                if (is_array($value)) {
319 1
                    $key = key($value);
320 1
                    $value = $value[$key];
321 1
                }
322
323 1
                if (is_array($value) && !empty($value['Entry']) && is_array($value['Entry'])) {
324 1
                    $entryKey = $value['Entry']['Key'];
325 1
                    unset($value['Entry']['Key']);
326 1
                    reset($value['Entry']);
327
328 1
                    $fieldKey = key($value['Entry']);
329 1
                    $value['Entry']['Key'] = $entryKey;
330 1
                    $fieldUri = $this->getIndexedFieldUriByName($fieldUriValue, $uriType, $fieldKey);
331 1
                } else {
332
                    $fieldUri = $this->getIndexedFieldUriByName($fieldUriValue, $uriType);
333
                }
334
335 1
                return ['IndexedFieldURI', ['FieldURI' => $fieldUri, 'FieldIndex' => $index], $key, $value];
336
            } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
337
            }
338
        }
339
340 3
        $fullName = $this->getFieldUriByName($key, $uriType);
341 3
        return ['FieldURI', ['FieldURI' => $fullName], $key, $value];
342
    }
343
344
    /**
345
     * @param string $itemType
346
     * @param string $uriType
347
     */
348 3
    protected function buildUpdateItemChanges($itemType, $uriType, $changes)
349
    {
350 3
        $setItemFields = array();
351 3
        $deleteItemFields = array();
352
353 3
        if (isset($changes['deleteFields'])) {
354
            foreach ($changes['deleteFields'] as $key) {
355
                list($fieldUriType, $fieldKey) = $this->getFieldURI($uriType, $key);
356
                $deleteItemFields[] = [$fieldUriType => $fieldKey];
357
            }
358
359
            unset($changes['deleteFields']);
360
        }
361
362
        //Add each property to a setItemField
363 3
        foreach ($changes as $key => $valueArray) {
364 3
            $valueArray = $this->splitDictionaryUpdateEntries($valueArray);
365 3
            if (!is_array($valueArray) || Type::arrayIsAssoc($valueArray)) {
366 3
                $valueArray = array($valueArray);
367 3
            }
368
369 3
            foreach ($valueArray as $value) {
370 3
                list ($fieldUriType, $fieldKey, $valueKey, $value) = $this->getFieldURI($uriType, $key, $value);
371 3
                $setItemFields[] = array(
372 3
                    $fieldUriType => $fieldKey,
373 3
                    $itemType => [$valueKey => $value]
374 3
                );
375 3
            }
376 3
        }
377
378 3
        return array('SetItemField' => $setItemFields, 'DeleteItemField' => $deleteItemFields);
379
    }
380
381 3
    protected function splitDictionaryUpdateEntries($value)
382
    {
383 3
        if (!is_array($value)) {
384 3
            return $value;
385
        }
386
387 1
        reset($value);
388 1
        $fieldKey = key($value);
389
390 1
        if (!is_array($value[$fieldKey]) || empty($value[$fieldKey]['Entry'])) {
391
            return $value;
392
        }
393
394 1
        $entryKey = $value[$fieldKey]['Entry']['Key'];
395 1
        unset($value[$fieldKey]['Entry']['Key']);
396
397 1
        $newValue = [];
398 1
        foreach ($value[$fieldKey]['Entry'] as $key => $updateValue) {
399 1
            $newValue[] = [$fieldKey => ['Entry' => ['Key' => $entryKey, $key => $updateValue]]];
400 1
        }
401
402 1
        $value = $newValue;
403
404 1
        return $value;
405
    }
406
407 2
    public function createFolders($names, Type\FolderIdType $parentFolder, $options = array())
408
    {
409 2
        $request = array('Folders' => array('Folder' => array()));
410 2
        if (!empty($parentFolder)) {
411 2
            $request['ParentFolderId'] = array('FolderId' => $parentFolder->toArray());
412 2
        }
413
414 2
        if (!is_array($names)) {
415 2
            $names = array($names);
416 2
        }
417
418 2
        foreach ($names as $name) {
419 2
            $request['Folders']['Folder'][] = array(
420
                'DisplayName' => $name
421 2
            );
422 2
        }
423
424 2
        $request = array_merge_recursive($request, $options);
425
426 2
        $this->client->CreateFolder($request);
0 ignored issues
show
Documentation Bug introduced by
The method CreateFolder does not exist on object<garethp\ews\API\ExchangeWebServices>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
427
428 2
        return true;
429
    }
430
431 2 View Code Duplication
    public function deleteFolder(Type\FolderIdType $folderId, $options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
432
    {
433
        $request = array(
434 2
            'DeleteType' => 'HardDelete',
435
            'FolderIds' => array(
436 2
                'FolderId' => $folderId->toArray()
437 2
            )
438 2
        );
439
440 2
        $request = array_merge_recursive($request, $options);
441 2
        return $this->client->DeleteFolder($request);
0 ignored issues
show
Documentation Bug introduced by
The method DeleteFolder does not exist on object<garethp\ews\API\ExchangeWebServices>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
442
    }
443
444 View Code Duplication
    public function moveItem(Type\ItemIdType $itemId, Type\FolderIdType $folderId, $options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
445
    {
446
        $request = array(
447
            'ToFolderId' => array('FolderId' => $folderId->toArray()),
448
            'ItemIds' => array('ItemId' => $itemId->toArray())
449
        );
450
451
        $request = array_merge_recursive($request, $options);
452
453
        return $this->client->MoveItem($request);
0 ignored issues
show
Documentation Bug introduced by
The method MoveItem does not exist on object<garethp\ews\API\ExchangeWebServices>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
454
    }
455
456
    /**
457
     * @param $items Type\ItemIdType|Type\ItemIdType[]
458
     * @param array $options
459
     * @return bool
460
     */
461 13
    public function deleteItems($items, $options = array())
462
    {
463 13
        if (!is_array($items) || Type::arrayIsAssoc($items)) {
464 13
            $items = array($items);
465 13
        }
466
467 13
        $itemIds = array();
468 13
        foreach ($items as $item) {
469 13
            if ($item instanceof Type\ItemIdType) {
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Type\ItemIdType 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...
470 12
                $item = $item->toArray();
471 12
            }
472 13
            $item = (array) $item;
473 13
            $itemIds[] = array(
474 13
                'Id' => $item['Id'],
475 13
                'ChangeKey' => $item['ChangeKey']
476 13
            );
477 13
        }
478
479
        $request = array(
480 13
            'ItemIds' => array('ItemId' => $itemIds),
481
            'DeleteType' => 'MoveToDeletedItems'
482 13
        );
483
484 13
        $request = array_replace_recursive($request, $options);
485 13
        $request = Type::buildFromArray($request);
486 13
        $this->getClient()->DeleteItem($request);
487
488
        //If the delete fails, an Exception will be thrown in processResponse before it gets here
489 13
        return true;
490
    }
491
492
    /**
493
     * @param $identifier
494
     * @return Type\BaseFolderType
495
     */
496 20
    public function getFolder($identifier)
497
    {
498
        $request = array(
499
            'FolderShape' => array(
500 20
                'BaseShape' => array('_' => 'Default')
501 20
            ),
502
            'FolderIds' => $identifier
503 20
        );
504 20
        $request = Type::buildFromArray($request);
505
506 20
        $response = $this->getClient()->GetFolder($request);
507 20
        return $response;
508
    }
509
510
    /**
511
     * Get a folder by it's distinguishedId
512
     *
513
     * @param string $distinguishedId
514
     * @return Type\BaseFolderType
515
     */
516 20
    public function getFolderByDistinguishedId($distinguishedId)
517
    {
518 20
        return $this->getFolder(array(
519
            'DistinguishedFolderId' => array(
520 20
                'Id' => $distinguishedId,
521 20
                'Mailbox' => $this->getPrimarySmtpMailbox()
522 20
            )
523 20
        ));
524
    }
525
526
    /**
527
     * @param $folderId
528
     * @return Type\BaseFolderType
529
     */
530 4
    public function getFolderByFolderId($folderId)
531
    {
532 4
        return $this->getFolder(array(
533 4
            'FolderId' => array('Id'=>$folderId, 'Mailbox' => $this->getPrimarySmtpMailbox())
534 4
        ));
535
    }
536
537
    /**
538
     * @param string|Type\FolderIdType $parentFolderId
539
     * @param array $options
540
     * @return bool|Type\BaseFolderType
541
     */
542 19
    public function getChildrenFolders($parentFolderId = 'root', $options = array())
543
    {
544 19
        if (is_string($parentFolderId)) {
545 15
            $parentFolderId = $this->getFolderByDistinguishedId($parentFolderId)->getFolderId();
0 ignored issues
show
Documentation Bug introduced by
The method getFolderId does not exist on object<garethp\ews\API\Type>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
546 15
        }
547
548
        $request = array(
549 19
            'Traversal' => 'Shallow',
550
            'FolderShape' => array(
551
                'BaseShape' => 'AllProperties'
552 19
            ),
553
            'ParentFolderIds' => array(
554 19
                'FolderId' => $parentFolderId->toArray()
555 19
            )
556 19
        );
557
558 19
        $request = array_replace_recursive($request, $options);
559
560 19
        $request = Type::buildFromArray($request);
561
562
        /** @var \garethp\ews\API\Message\FindFolderResponseMessageType $folders */
563 19
        return $this->getClient()->FindFolder($request);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getClient()->FindFolder($request); (garethp\ews\API\Type) is incompatible with the return type documented by garethp\ews\API::getChildrenFolders of type boolean|garethp\ews\API\Type\BaseFolderType.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
564
        return $folders->getFolders();
0 ignored issues
show
Unused Code introduced by
return $folders->getFolders(); 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...
565
    }
566
567
    /**
568
     * @param string $folderName
569
     * @param string|Type\FolderIdType $parentFolderId
570
     * @param array $options
571
     * @return bool|Type\BaseFolderType
572
     */
573 19
    public function getFolderByDisplayName($folderName, $parentFolderId = 'root', $options = array())
574
    {
575 19
        $folders = $this->getChildrenFolders($parentFolderId, $options);
576
577 19
        foreach ($folders as $folder) {
0 ignored issues
show
Bug introduced by
The expression $folders of type boolean|object<garethp\e...PI\Type\BaseFolderType> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
578 19
            if ($folder->getDisplayName() == $folderName) {
579 18
                return $folder;
580
            }
581 13
        }
582
583 3
        return false;
584
    }
585
586
    /**
587
     * @param $itemId array|Type\ItemIdType
588
     * @param array $options
589
     * @return Type
590
     */
591 4 View Code Duplication
    public function getItem($itemId, $options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
592
    {
593 4
        if ($itemId instanceof Type\ItemIdType) {
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Type\ItemIdType 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...
594 3
            $itemId = $itemId->toArray();
595 3
        }
596
597
        $request = array(
598 4
            'ItemShape' => array('BaseShape' => 'AllProperties'),
599 4
            'ItemIds' => array('ItemId' => $itemId)
600 4
        );
601
602 4
        $request = array_replace_recursive($request, $options);
603
604 4
        return $this->getClient()->GetItem($request);
605
    }
606
607
    /**
608
     * Get a list of sync changes on a folder
609
     *
610
     * @param Type\FolderIdType $folderId
611
     * @param null $syncState
612
     * @param array $options
613
     * @return SyncFolderItemsResponseMessageType
614
     */
615 2
    public function listItemChanges($folderId, $syncState = null, $options = array())
616
    {
617
        $request = array(
618 2
            'ItemShape' => array('BaseShape' => 'IdOnly'),
619 2
            'SyncFolderId' => array('FolderId' => $folderId->toXmlObject()),
620 2
            'SyncScope' => 'NormalItems',
621
            'MaxChangesReturned' => '10'
622 2
        );
623
624 2
        if ($syncState != null) {
625 1
            $request['SyncState'] = $syncState;
626 1
            $request['ItemShape']['BaseShape'] = 'AllProperties';
627 1
        }
628
629 2
        $request = array_replace_recursive($request, $options);
630
631 2
        $request = Type::buildFromArray($request);
632 2
        $response = $this->getClient()->SyncFolderItems($request);
633 2
        return $response;
634
    }
635
636 View Code Duplication
    public function getServerTimezones($timezoneIDs = array(), $fullTimezoneData = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
637
    {
638
        $request = GetServerTimeZonesType::buildFromArray(array(
639
            'returnFullTimeZoneData' => $fullTimezoneData
640
        ));
641
642
        if (!empty($timezoneIDs)) {
643
            $request->setIds($timezoneIDs);
644
        }
645
646
        $timezones = $this->getClient()->GetServerTimeZones($request);
0 ignored issues
show
Documentation Bug introduced by
The method GetServerTimeZones does not exist on object<garethp\ews\API\ExchangeWebServices>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
647
        $timezones = $timezones->TimeZoneDefinition;
648
649
        if (!is_array($timezones)) {
650
            $timezones = array($timezones);
651
        }
652
653
        return $timezones;
654
    }
655
656
    /**
657
     * @param Type\ItemIdType $itemId
658
     * @param $fromType
659
     * @param $destinationType
660
     * @param $mailbox
661
     *
662
     * @return Type\ItemIdType
663
     */
664
    public function convertIdFormat(Type\ItemIdType $itemId, $fromType, $destinationType, $mailbox)
665
    {
666
        $result = $this->getClient()->ConvertId(array(
0 ignored issues
show
Documentation Bug introduced by
The method ConvertId does not exist on object<garethp\ews\API\ExchangeWebServices>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
667
            'DestinationFormat' => $destinationType,
668
            'SourceIds' => array(
669
                'AlternateId' => array(
670
                    'Format' => $fromType,
671
                    'Id' => $itemId->getId(),
672
                    'Mailbox' => $mailbox
673
                )
674
            )
675
        ));
676
677
        $itemId->setId($result->getId());
678
679
        return $itemId;
680
    }
681
}
682