Completed
Push — master ( 54cc50...0eced0 )
by Gareth
04:39
created

API::moveItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 11
loc 11
ccs 0
cts 6
cp 0
rs 9.4285
cc 1
eloc 6
nc 1
nop 3
crap 2
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 12
    public function buildClient(
222
        $server,
223
        $username,
224
        $password,
225
        $options = [ ]
226
    ) {
227 12
        $this->setClient(ExchangeWebServices::fromUsernameAndPassword(
228 12
            $server,
229 12
            $username,
230 12
            $password,
231 12
            array_replace_recursive(self::$defaultClientOptions, $options)
232 12
        ));
233 12
    }
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 API\CreateItemResponseType
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 3
    protected function buildUpdateItemChanges($itemType, $uriType, $changes)
345
    {
346 3
        $setItemFields = array();
347 3
        $deleteItemFields = array();
348
349 3
        if (isset($changes['deleteFields'])) {
350
            foreach ($changes['deleteFields'] as $key) {
351
                list($fieldUriType, $fieldKey) = $this->getFieldURI($uriType, $key);
352
                $deleteItemFields[] = [$fieldUriType => $fieldKey];
353
            }
354
355
            unset($changes['deleteFields']);
356
        }
357
358
        //Add each property to a setItemField
359 3
        foreach ($changes as $key => $valueArray) {
360 3
            $valueArray = $this->splitDictionaryUpdateEntries($valueArray);
361 3
            if (!is_array($valueArray) || Type::arrayIsAssoc($valueArray)) {
362 3
                $valueArray = array($valueArray);
363 3
            }
364
365 3
            foreach ($valueArray as $value) {
366 3
                list ($fieldUriType, $fieldKey, $valueKey, $value) = $this->getFieldURI($uriType, $key, $value);
367 3
                $setItemFields[] = array(
368 3
                    $fieldUriType => $fieldKey,
369 3
                    $itemType => [$valueKey => $value]
370 3
                );
371 3
            }
372 3
        }
373
374 3
        return array('SetItemField' => $setItemFields, 'DeleteItemField' => $deleteItemFields);
375
    }
376
377 3
    protected function splitDictionaryUpdateEntries($value)
378
    {
379 3
        if (!is_array($value)) {
380 3
            return $value;
381
        }
382
383 1
        reset($value);
384 1
        $fieldKey = key($value);
385
386 1
        if (!is_array($value[$fieldKey]) || empty($value[$fieldKey]['Entry'])) {
387
            return $value;
388
        }
389
390 1
        $entryKey = $value[$fieldKey]['Entry']['Key'];
391 1
        unset($value[$fieldKey]['Entry']['Key']);
392
393 1
        $newValue = [];
394 1
        foreach ($value[$fieldKey]['Entry'] as $key => $updateValue) {
395 1
            $newValue[] = [$fieldKey => ['Entry' => ['Key' => $entryKey, $key => $updateValue ]]];
396 1
        }
397
398 1
        $value = $newValue;
399
400 1
        return $value;
401
    }
402
403 2
    public function createFolders($names, Type\FolderIdType $parentFolder, $options = array())
404
    {
405 2
        $request = array('Folders' => array('Folder' => array()));
406 2
        if (!empty($parentFolder)) {
407 2
            $request['ParentFolderId'] = array('FolderId' => $parentFolder->toArray());
408 2
        }
409
410 2
        if (!is_array($names)) {
411 2
            $names = array($names);
412 2
        }
413
414 2
        foreach ($names as $name) {
415 2
            $request['Folders']['Folder'][] = array(
416
                'DisplayName' => $name
417 2
            );
418 2
        }
419
420 2
        $request = array_merge_recursive($request, $options);
421
422 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...
423
424 2
        return true;
425
    }
426
427 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...
428
    {
429
        $request = array(
430 2
            'DeleteType' => 'HardDelete',
431
            'FolderIds' => array(
432 2
                'FolderId' => $folderId->toArray()
433 2
            )
434 2
        );
435
436 2
        $request = array_merge_recursive($request, $options);
437 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...
438
    }
439
440 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...
441
    {
442
        $request = array(
443
            'ToFolderId' => array('FolderId' => $folderId->toArray()),
444
            'ItemIds' => array('ItemId' => $itemId->toArray())
445
        );
446
447
        $request = array_merge_recursive($request, $options);
448
449
        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...
450
    }
451
452
    /**
453
     * @param $items Type\ItemIdType|Type\ItemIdType[]
454
     * @param array $options
455
     * @return bool
456
     */
457 13
    public function deleteItems($items, $options = array())
458
    {
459 13
        if (!is_array($items) || Type::arrayIsAssoc($items)) {
460 13
            $items = array($items);
461 13
        }
462
463 13
        $itemIds = array();
464 13
        foreach ($items as $item) {
465 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...
466 12
                $item = $item->toArray();
467 12
            }
468 13
            $item = (array) $item;
469 13
            $itemIds[] = array(
470 13
                'Id' => $item['Id'],
471 13
                'ChangeKey' => $item['ChangeKey']
472 13
            );
473 13
        }
474
475
        $request = array(
476 13
            'ItemIds' => array('ItemId' => $itemIds),
477
            'DeleteType' => 'MoveToDeletedItems'
478 13
        );
479
480 13
        $request = array_replace_recursive($request, $options);
481 13
        $request = Type::buildFromArray($request);
482 13
        $this->getClient()->DeleteItem($request);
483
484
        //If the delete fails, an Exception will be thrown in processResponse before it gets here
485 13
        return true;
486
    }
487
488
    /**
489
     * @param $identifier
490
     * @return Type\BaseFolderType
491
     */
492 20
    public function getFolder($identifier)
493
    {
494
        $request = array(
495
            'FolderShape' => array(
496 20
                'BaseShape' => array('_' => 'Default')
497 20
            ),
498
            'FolderIds' => $identifier
499 20
        );
500 20
        $request = Type::buildFromArray($request);
501
502 20
        $response = $this->getClient()->GetFolder($request);
503 20
        return $response;
504
    }
505
506
    /**
507
     * Get a folder by it's distinguishedId
508
     *
509
     * @param string $distinguishedId
510
     * @return Type\BaseFolderType
511
     */
512 20
    public function getFolderByDistinguishedId($distinguishedId)
513
    {
514 20
        return $this->getFolder(array(
515
            'DistinguishedFolderId' => array(
516 20
                'Id' => $distinguishedId,
517 20
                'Mailbox' => $this->getPrimarySmtpMailbox()
518 20
            )
519 20
        ));
520
    }
521
522
    /**
523
     * @param $folderId
524
     * @return Type\BaseFolderType
525
     */
526 4
    public function getFolderByFolderId($folderId)
527
    {
528 4
        return $this->getFolder(array(
529 4
            'FolderId' => array('Id'=>$folderId, 'Mailbox' => $this->getPrimarySmtpMailbox())
530 4
        ));
531
    }
532
533
    /**
534
     * @param string|Type\FolderIdType $parentFolderId
535
     * @param array $options
536
     * @return bool|Type\BaseFolderType
537
     */
538 19
    public function getChildrenFolders($parentFolderId = 'root', $options = array())
539
    {
540 19
        if (is_string($parentFolderId)) {
541 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...
542 15
        }
543
544
        $request = array(
545 19
            'Traversal' => 'Shallow',
546
            'FolderShape' => array(
547
                'BaseShape' => 'AllProperties'
548 19
            ),
549
            'ParentFolderIds' => array(
550 19
                'FolderId' => $parentFolderId->toArray()
551 19
            )
552 19
        );
553
554 19
        $request = array_replace_recursive($request, $options);
555
556 19
        $request = Type::buildFromArray($request);
557
558
        /** @var \garethp\ews\API\Message\FindFolderResponseMessageType $folders */
559 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...
560
        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...
561
    }
562
563
    /**
564
     * @param $folderName
565
     * @param string|Type\FolderIdType $parentFolderId
566
     * @param array $options
567
     * @return bool|Type\BaseFolderType
568
     */
569 19
    public function getFolderByDisplayName($folderName, $parentFolderId = 'root', $options = array())
570
    {
571 19
        $folders = $this->getChildrenFolders($parentFolderId, $options);
572
573 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...
574 19
            if ($folder->getDisplayName() == $folderName) {
575 18
                return $folder;
576
            }
577 13
        }
578
579 3
        return false;
580
    }
581
582
    /**
583
     * @param $itemId array|Type\ItemIdType
584
     * @param array $options
585
     * @return Type
586
     */
587 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...
588
    {
589 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...
590 3
            $itemId = $itemId->toArray();
591 3
        }
592
593
        $request = array(
594 4
            'ItemShape' => array('BaseShape' => 'AllProperties'),
595 4
            'ItemIds' => array('ItemId' => $itemId)
596 4
        );
597
598 4
        $request = array_replace_recursive($request, $options);
599
600 4
        return $this->getClient()->GetItem($request);
601
    }
602
603
    /**
604
     * Get a list of sync changes on a folder
605
     *
606
     * @param Type\FolderIdType $folderId
607
     * @param null $syncState
608
     * @param array $options
609
     * @return SyncFolderItemsResponseMessageType
610
     */
611 2
    public function listItemChanges($folderId, $syncState = null, $options = array())
612
    {
613
        $request = array(
614 2
            'ItemShape' => array('BaseShape' => 'IdOnly'),
615 2
            'SyncFolderId' => array('FolderId' => $folderId->toXmlObject()),
616 2
            'SyncScope' => 'NormalItems',
617
            'MaxChangesReturned' => '10'
618 2
        );
619
620 2
        if ($syncState != null) {
621 1
            $request['SyncState'] = $syncState;
622 1
            $request['ItemShape']['BaseShape'] = 'AllProperties';
623 1
        }
624
625 2
        $request = array_replace_recursive($request, $options);
626
627 2
        $request = Type::buildFromArray($request);
628 2
        $response = $this->getClient()->SyncFolderItems($request);
629 2
        return $response;
630
    }
631
632 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...
633
    {
634
        $request = GetServerTimeZonesType::buildFromArray(array(
635
            'returnFullTimeZoneData' => $fullTimezoneData
636
        ));
637
638
        if (!empty($timezoneIDs)) {
639
            $request->setIds($timezoneIDs);
640
        }
641
642
        $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...
643
        $timezones = $timezones->TimeZoneDefinition;
644
645
        if (!is_array($timezones)) {
646
            $timezones = array($timezones);
647
        }
648
649
        return $timezones;
650
    }
651
652
    /**
653
     * @param Type\ItemIdType $itemId
654
     * @param $fromType
655
     * @param $destinationType
656
     * @param $mailbox
657
     *
658
     * @return Type\ItemIdType
659
     */
660
    public function convertIdFormat(Type\ItemIdType $itemId, $fromType, $destinationType, $mailbox)
661
    {
662
        $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...
663
            'DestinationFormat' => $destinationType,
664
            'SourceIds' => array (
665
                'AlternateId' => array(
666
                    'Format' => $fromType,
667
                    'Id' => $itemId->getId(),
668
                    'Mailbox' => $mailbox
669
                )
670
            )
671
        ));
672
673
        $itemId->setId($result->getId());
674
675
        return $itemId;
676
    }
677
}
678