Completed
Push — master ( 86d4f9...53bee2 )
by
unknown
04:49
created

Cursor   D

Complexity

Total Complexity 89

Size/Duplication

Total Lines 505
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 95.24%

Importance

Changes 5
Bugs 2 Features 1
Metric Value
wmc 89
c 5
b 2
f 1
lcom 1
cbo 6
dl 0
loc 505
ccs 220
cts 231
cp 0.9524
rs 4.8717

39 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A createObject() 0 8 2
A fetchBefore() 0 8 2
A getObjects() 0 3 1
A getIndexRight() 0 3 1
C assureResponseData() 0 54 17
A isJsonObject() 0 13 3
A prependResponse() 0 15 3
A appendResponse() 0 19 4
A getDefaultUseImplicitFetch() 0 3 1
A setDefaultUseImplicitFetch() 0 3 1
A getUseImplicitFetch() 0 5 2
A setUseImplicitFetch() 0 3 1
A getBefore() 0 6 2
A getAfter() 0 6 2
A createUndirectionalizedRequest() 0 12 3
A getPrevious() 0 15 3
A getNext() 0 15 3
A createRequestFromUrl() 0 11 2
A createBeforeRequest() 0 4 2
A createAfterRequest() 0 4 2
A fetchAfter() 0 8 2
A getArrayCopy() 0 9 2
A getResponse() 0 3 1
A getLastResponse() 0 3 1
A getIndexLeft() 0 3 1
A rewind() 0 3 1
A end() 0 3 1
A seekTo() 0 4 2
A current() 0 5 2
A key() 0 3 1
A prev() 0 16 4
A next() 0 16 4
A valid() 0 3 1
A count() 0 3 1
A offsetSet() 0 7 2
A offsetExists() 0 3 1
A offsetUnset() 0 3 1
A offsetGet() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like Cursor 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 Cursor, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
4
 *
5
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
6
 * use, copy, modify, and distribute this software in source code or binary
7
 * form for use in connection with the web services and APIs provided by
8
 * Facebook.
9
 *
10
 * As with any software that integrates with the Facebook platform, your use
11
 * of this software is subject to the Facebook Developer Principles and
12
 * Policies [http://developers.facebook.com/policy/]. This copyright notice
13
 * shall be included in all copies or substantial portions of the software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
 * DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
namespace FacebookAds;
26
27
use FacebookAds\Http\RequestInterface;
28
use FacebookAds\Http\ResponseInterface;
29
use FacebookAds\Http\Util;
30
use FacebookAds\Object\AbstractObject;
31
32
class Cursor implements \Iterator, \Countable, \arrayaccess {
33
  /**
34
   * @var ResponseInterface
35
   */
36
  protected $response;
37
38
  /**
39
   * @var Api
40
   */
41
  protected $api;
42
43
  /**
44
   * @var AbstractObject[]
45
   */
46
  protected $objects = array();
47
48
  /**
49
   * @var int|null
50
   */
51
  protected $indexLeft;
52
53
  /**
54
   * @var int|null
55
   */
56
  protected $indexRight;
57
58
  /**
59
   * @var int|null
60
   */
61
  protected $position;
62
63
  /**
64
   * @var AbstractObject
65
   */
66
  protected $objectPrototype;
67
68
  /**
69
   * @var bool
70
   */
71
  protected static $defaultUseImplicitFetch = false;
72
73
  /**
74
   * @var bool
75
   */
76
  protected $useImplicitFectch;
77
78 24
  public function __construct(
79
    ResponseInterface $response,
80
    AbstractObject $object_prototype,
81
    Api $api = null) {
82 24
    $this->response = $response;
83 24
    $this->objectPrototype = $object_prototype;
84 24
    $this->api = $api !== null ? $api : Api::instance();
85 24
    $this->appendResponse($response);
86 18
  }
87
88
  /**
89
   * @param array $object_data
90
   * @return AbstractObject
91
   */
92 16
  protected function createObject(array $object_data) {
93 16
    $object = clone $this->objectPrototype;
94 16
    $object->setDataWithoutValidation($object_data);
95 16
    if ($object instanceof AbstractCrudObject) {
0 ignored issues
show
Bug introduced by
The class FacebookAds\AbstractCrudObject 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...
96
      $object->setApi($this->api);
97
    }
98 16
    return $object;
99
  }
100
101
  /**
102
   * @param ResponseInterface $response
103
   * @return array
104
   * @throws \InvalidArgumentException
105
   */
106 24
  protected function assureResponseData(ResponseInterface $response) {
107 24
    $content = $response->getContent();
108
109
    // First, check if the content contains data
110 24
    if (isset($content['data']) && is_array($content['data'])) {
111 18
      $data = $content['data'];
112
113
      // If data is an object wrap the object into an array
114 18
      if ($this->isJsonObject($data)) {
115
        $data = array($data);
116
      }
117 18
      return $data;
118
    }
119
120
    // Second, check if the content contains special entries
121 6
    if (isset($content['targetingsentencelines'])) {
122
      return $content['targetingsentencelines'];
123
    }
124 6
    if (isset($content['adaccounts'])) {
125
      return $content['adaccounts'];
126
    }
127 6
    if (isset($content['users'])) {
128
      return $content['users'];
129
    }
130
131
    // Third, check if the content is an array of objects indexed by id
132 6
    $is_id_indexed_array = true;
133 6
    $objects = array();
134 6
    if (is_array($content) && count($content) >= 1) {
135 4
      foreach ($content as $key => $value) {
136 4
        if ($key === '__fb_trace_id__') {
137
          continue;
138
        }
139
140 4
        if ($value !== null &&
141 4
            $this->isJsonObject($value) &&
142 4
            isset($value['id']) &&
143 4
            $value['id'] !== null &&
144 4
            $value['id'] === $key) {
145
          $objects[] = $value;
146
        } else {
147 4
          $is_id_indexed_array = false;
148 4
          break;
149
        }
150 4
      }
151 4
    } else {
152 2
      $is_id_indexed_array = false;
153
    }
154 6
    if ($is_id_indexed_array) {
155
      return $objects;
156
    }
157
158 6
    throw new \InvalidArgumentException("Malformed response data");
159
  }
160
161 20
  private function isJsonObject($object) {
162 20
    if (!is_array($object)) {
163 2
      return false;
164
    }
165
166
    // Consider an empty array as not object
167 18
    if (empty($object)) {
168 10
      return false;
169
    }
170
171
    // A json object is represented by a map instead of a pure list
172 16
    return array_keys($object) !== range(0, count($object) - 1);
173
  }
174
175
  /**
176
   * @param ResponseInterface $response
177
   */
178 2
  protected function prependResponse(ResponseInterface $response) {
179 2
    $this->response = $response;
180 2
    $data = $this->assureResponseData($response);
181 2
    if (empty($data)) {
182 2
      return;
183
    }
184
185 2
    $left_index = $this->indexLeft;
186 2
    $count = count($data);
187 2
    $position = $count - 1;
188 2
    for ($i = $left_index - 1; $i >= $left_index - $count; $i--) {
189 2
      $this->objects[$i] = $this->createObject($data[$position--]);
190 2
      --$this->indexLeft;
191 2
    }
192 2
  }
193
194
  /**
195
   * @param ResponseInterface $response
196
   */
197 24
  protected function appendResponse(ResponseInterface $response) {
198 24
    $this->response = $response;
199 24
    $data = $this->assureResponseData($response);
200 18
    if (empty($data)) {
201 10
      return;
202
    }
203
204 16
    if ($this->indexRight === null) {
205 16
      $this->indexLeft = 0;
206 16
      $this->indexRight = -1;
207 16
      $this->position = 0;
208 16
    }
209
210 16
    $this->indexRight += count($data);
211
212 16
    foreach ($data as $object_data) {
213 16
      $this->objects[] = $this->createObject($object_data);
214 16
    }
215 16
  }
216
217
  /**
218
   * @return bool
219
   */
220 2
  public static function getDefaultUseImplicitFetch() {
221 2
    return static::$defaultUseImplicitFetch;
222
  }
223
224
  /**
225
   * @param bool $use_implicit_fectch
226
   */
227 2
  public static function setDefaultUseImplicitFetch($use_implicit_fectch) {
228 2
    static::$defaultUseImplicitFetch = $use_implicit_fectch;
229 2
  }
230
231
  /**
232
   * @return bool
233
   */
234 4
  public function getUseImplicitFetch() {
235 4
    return $this->useImplicitFectch !== null
236 4
      ? $this->useImplicitFectch
237 4
      : static::$defaultUseImplicitFetch;
238
  }
239
240
  /**
241
   * @param bool $use_implicit_fectch
242
   */
243 2
  public function setUseImplicitFetch($use_implicit_fectch) {
244 2
    $this->useImplicitFectch = $use_implicit_fectch;
245 2
  }
246
247
  /**
248
   * @return string|null
249
   */
250 7
  public function getBefore() {
251 7
    $content = $this->getLastResponse()->getContent();
252 7
    return isset($content['paging']['cursors']['before'])
253 7
      ? $content['paging']['cursors']['before']
254 7
      : null;
255
  }
256
257
  /**
258
   * @return string|null
259
   */
260 8
  public function getAfter() {
261 8
    $content = $this->getLastResponse()->getContent();
262 8
    return isset($content['paging']['cursors']['after'])
263 8
      ? $content['paging']['cursors']['after']
264 8
      : null;
265
  }
266
267
  /**
268
   * @return RequestInterface
269
   */
270 4
  protected function createUndirectionalizedRequest() {
271 4
    $request = $this->getLastResponse()->getRequest()->createClone();
272 4
    $params = $request->getQueryParams();
273 4
    if (array_key_exists('before', $params)) {
274 2
      unset($params['before']);
275 2
    }
276 4
    if (array_key_exists('after', $params)) {
277 2
      unset($params['after']);
278 2
    }
279
280 4
    return $request;
281
  }
282
283
  /**
284
   * @return string|null
285
   */
286 6
  public function getPrevious() {
287 6
    $content = $this->getLastResponse()->getContent();
288 6
    if (isset($content['paging']['previous'])) {
289 1
      return $content['paging']['previous'];
290
    }
291
292 5
    $before = $this->getBefore();
293 5
    if ($before !== null) {
294 1
      $request = $this->createUndirectionalizedRequest();
295 1
      $request->getQueryParams()->offsetSet('before', $before);
296 1
      return $request->getUrl();
297
    }
298
299 4
    return null;
300
  }
301
302
  /**
303
   * @return string|null
304
   */
305 10
  public function getNext() {
306 10
    $content = $this->getLastResponse()->getContent();
307 10
    if (isset($content['paging']['next'])) {
308 4
      return $content['paging']['next'];
309
    }
310
311 6
    $after = $this->getAfter();
312 6
    if ($after !== null) {
313 4
      $request = $this->createUndirectionalizedRequest();
314 4
      $request->getQueryParams()->offsetSet('after', $after);
315 4
      return $request->getUrl();
316
    }
317
318 2
    return null;
319
  }
320
321
  /**
322
   * @param string $url
323
   * @return RequestInterface
324
   */
325 8
  protected function createRequestFromUrl($url) {
326 8
    $components = parse_url($url);
327 8
    $request = $this->getLastResponse()->getRequest()->createClone();
328 8
    $request->setDomain($components['host']);
329 8
    $query = isset($components['query'])
330 8
      ? Util::parseUrlQuery($components['query'])
331 8
      : array();
332 8
    $request->getQueryParams()->enhance($query);
0 ignored issues
show
Unused Code introduced by
The call to the method FacebookAds\Http\Parameters::enhance() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
333
334 8
    return $request;
335
  }
336
337
  /**
338
   * @return RequestInterface|null
339
   */
340 6
  public function createBeforeRequest() {
341 6
    $url = $this->getPrevious();
342 6
    return $url !== null ? $this->createRequestFromUrl($url) : null;
343
  }
344
345
  /**
346
   * @return RequestInterface|null
347
   */
348 10
  public function createAfterRequest() {
349 10
    $url = $this->getNext();
350 10
    return $url !== null ? $this->createRequestFromUrl($url) : null;
351
  }
352
353 6
  public function fetchBefore() {
354 6
    $request = $this->createBeforeRequest();
355 6
    if (!$request) {
356 4
      return;
357
    }
358
359 2
    $this->prependResponse($request->execute());
360 2
  }
361
362 10
  public function fetchAfter() {
363 10
    $request = $this->createAfterRequest();
364 10
    if (!$request) {
365 2
      return;
366
    }
367
368 8
    $this->appendResponse($request->execute());
369 8
  }
370
371
  /**
372
   * @deprecated Use getArrayCopy()
373
   * @return AbstractObject[]
374
   */
375
  public function getObjects() {
376
    return $this->objects;
377
  }
378
379
  /**
380
   * @param bool $ksort
381
   * @return AbstractObject[]
382
   */
383 2
  public function getArrayCopy($ksort = false) {
384 2
    if ($ksort) {
385
      // Sort the main array to improve best case performance in future
386
      // invocations
387 2
      ksort($this->objects);
388 2
    }
389
390 2
    return $this->objects;
391
  }
392
393
  /**
394
   * @deprecated Use getLastResponse()
395
   * @return ResponseInterface
396
   */
397
  public function getResponse() {
398
    return $this->response;
399
  }
400
401
  /**
402
   * @return ResponseInterface
403
   */
404 12
  public function getLastResponse() {
405 12
    return $this->response;
406
  }
407
408
  /**
409
   * @return int
410
   */
411 8
  public function getIndexLeft() {
412 8
    return $this->indexLeft;
413
  }
414
415
  /**
416
   * @return int
417
   */
418 6
  public function getIndexRight() {
419 6
    return $this->indexRight;
420
  }
421
422 4
  public function rewind() {
423 4
    $this->position = $this->indexLeft;
424 4
  }
425
426 2
  public function end() {
427 2
    $this->position = $this->indexRight;
428 2
  }
429
430
  /**
431
   * @param int $position
432
   */
433 2
  public function seekTo($position) {
434 2
    $position = array_key_exists($position, $this->objects) ? $position : null;
435 2
    $this->position = $position;
436 2
  }
437
438
  /**
439
   * @return AbstractObject|bool
440
   */
441 2
  public function current() {
442 2
    return isset($this->objects[$this->position])
443 2
      ? $this->objects[$this->position]
444 2
      : false;
445
  }
446
447
  /**
448
   * @return int
449
   */
450 4
  public function key() {
451 4
    return $this->position;
452
  }
453
454 4
  public function prev() {
455 4
    if ($this->position == $this->getIndexLeft()) {
456 2
      if ($this->getUseImplicitFetch()) {
457 2
        $this->fetchBefore();
458 2
        if ($this->position == $this->getIndexLeft()) {
459 2
          $this->position = null;
460 2
        } else {
461 2
          --$this->position;
462
        }
463 2
      } else {
464 2
        $this->position = null;
465
      }
466 2
    } else {
467 4
      --$this->position;
468
    }
469 4
  }
470
471 6
  public function next() {
472 6
    if ($this->position == $this->getIndexRight()) {
473 4
      if ($this->getUseImplicitFetch()) {
474 2
        $this->fetchAfter();
475 2
        if ($this->position == $this->getIndexRight()) {
476 2
          $this->position = null;
477 2
        } else {
478 2
          ++$this->position;
479
        }
480 2
      } else {
481 2
        $this->position = null;
482
      }
483 4
    } else {
484 6
      ++$this->position;
485
    }
486 6
  }
487
488
  /**
489
   * @return bool
490
   */
491 4
  public function valid() {
492 4
    return isset($this->objects[$this->position]);
493
  }
494
495
  /**
496
   * @return int
497
   */
498 10
  public function count() {
499 10
    return count($this->objects);
500
  }
501
502
  /**
503
   * @param mixed $offset
504
   * @param mixed $value
505
   */
506 2
  public function offsetSet($offset, $value) {
507 2
    if ($offset === null) {
508 2
      $this->objects[] = $value;
509 2
    } else {
510 2
      $this->objects[$offset] = $value;
511
    }
512 2
  }
513
514
  /**
515
   * @param mixed $offset
516
   * @return bool
517
   */
518 2
  public function offsetExists($offset) {
519 2
    return isset($this->objects[$offset]);
520
  }
521
522
  /**
523
   * @param mixed $offset
524
   */
525 2
  public function offsetUnset($offset) {
526 2
    unset($this->objects[$offset]);
527 2
  }
528
529
  /**
530
   * @param mixed $offset
531
   * @return mixed
532
   */
533 2
  public function offsetGet($offset) {
534 2
    return isset($this->objects[$offset]) ? $this->objects[$offset] : null;
535
  }
536
}
537