ApiObject   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 515
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 88.24%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 1
dl 0
loc 515
c 0
b 0
f 0
ccs 90
cts 102
cp 0.8824
rs 8.48

22 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 40 7
A jsonSerialize() 0 4 1
A lazyLoad() 0 8 2
A assignResults() 0 10 3
A checkInvalid() 0 7 2
A _markInvalid() 0 4 1
A setIfNotNullOrEmpty() 0 7 3
A initializeValues() 0 3 1
A lazyInject() 0 16 5
A lazyCastAll() 0 7 2
A lazyCast() 0 8 3
A lazyCastNeededOnArray() 0 11 3
A lazyCastNeeded() 0 6 2
A fetchAndCastToObjectArray() 0 6 1
A castArrayToObjectArray() 0 12 2
A sendGet() 0 8 1
A sendPost() 0 4 1
A sendPut() 0 4 1
A sendDelete() 0 4 1
A sendRequest() 0 21 4
A apiEndpoint() 0 6 2
A setApiKey() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/PhpPulse/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\DaPulse\Objects;
9
10
use allejo\DaPulse\Exceptions\InvalidObjectException;
11
use allejo\DaPulse\Utilities\UrlQuery;
12
13
/**
14
 * The base class for all DaPulse API objects
15
 *
16
 * @internal
17
 * @package allejo\DaPulse\Objects
18
 * @since   0.1.0
19
 */
20
abstract class ApiObject implements \JsonSerializable
21
{
22
    /**
23
     * The namespace used for all main PhpPulse objects. This is value is prepended before PhpPulse objects when being
24
     * checked with `instanceof`.
25
     *
26
     * @internal
27
     */
28
    const OBJ_NAMESPACE = "\\allejo\\DaPulse\\";
29
30
    /**
31
     * The default API protocol used for URL calls
32
     *
33
     * @internal
34
     */
35
    const API_PROTOCOL = "https";
36
37
    /**
38
     * The API end point for URL calls
39
     *
40
     * @internal
41
     */
42
    const API_ENDPOINT = "api.monday.com";
43
44
    /**
45
     * The API version used for URL calls
46
     *
47
     * @internal
48
     */
49
    const API_VERSION = "v1";
50
51
    /**
52
     * The suffix that is appended to the URL to access functionality for certain objects
53
     *
54
     * @internal
55
     */
56
    const API_PREFIX = "";
57
58
    /**
59
     * The API key used to make the URL calls
60
     *
61
     * @var string
62
     */
63
    protected static $apiKey;
64
65
    /**
66
     * When set to true, the object can only be constructed from an associative array of data. It will not attempt
67
     * to fetch the data with an API call; this is intended for objects are not directly accessible via the API.
68
     *
69
     * @var bool
70
     */
71
    protected $arrayConstructionOnly = false;
72
73
    /**
74
     * Set to true if the object has been deleted via an API call but the instance still exists. This variable will
75
     * prevent further API calls to a nonexistent object.
76
     *
77
     * @var bool
78
     */
79
    protected $deletedObject = false;
80
81
    /**
82
     * An associative array representing the original JSON response from DaPulse
83
     *
84
     * @var array
85
     */
86
    protected $jsonResponse;
87
88
    protected $urlEndPoint;
89
90
    /**
91
     * The ID for the object we're handling
92
     *
93
     * @var int
94
     */
95
    protected $id;
96
97
    /**
98
     * Create an object from an API call
99
     *
100
     * @param int|array $idOrArray Either the numerical ID of an object or an associative array representing a JSON
101
     *                             response from an API call
102
     * @param bool      $lazyLoad  When set to true, an initial API call will not be made. An API call will be made when
103
     *                             the information is requested
104
     *
105
     * @throws \InvalidArgumentException The specified object cannot be created directly from an API call but instead
106
     *                                   requires an associative array of information gathered from other API calls.
107
     *
108
     * @since 0.1.0
109
     */
110 115
    public function __construct ($idOrArray, $lazyLoad = false)
111
    {
112 115
        $staticClass = explode("\\", get_called_class());
113 115
        $staticClass = end($staticClass);
114
115 115
        if (is_null($idOrArray))
116
        {
117
            throw new \InvalidArgumentException("You may not initialize $staticClass with null.");
118
        }
119
120 115
        if (!is_array($idOrArray))
121
        {
122 115
            $this->urlEndPoint = sprintf("%s/%d.json", self::apiEndpoint(), $idOrArray);
123
        }
124
125 115
        if ($this->arrayConstructionOnly && !is_array($idOrArray))
126
        {
127
            throw new \InvalidArgumentException("A $staticClass cannot be fetched from an ID.");
128
        }
129
130 115
        $this->initializeValues();
131
132 115
        if (is_array($idOrArray))
133
        {
134 97
            $this->jsonResponse = $idOrArray;
135 97
            $this->assignResults();
136
        }
137
        else
138
        {
139 115
            if ($lazyLoad)
140
            {
141 17
                $this->id           = $idOrArray;
142 17
                $this->jsonResponse = [];
143
            }
144
            else
145
            {
146 111
                $this->lazyLoad();
147
            }
148
        }
149 115
    }
150
151
    /**
152
     * Access the JSON response from DaPulse used to create this wrapper object
153
     *
154
     * If a wrapper getter function isn't available for a certain value, use this function to access the value directly.
155
     *
156
     * @api
157
     * @since 0.2.0
158
     * @return mixed
159
     */
160
    public function jsonSerialize ()
161
    {
162
        return $this->jsonResponse;
163
    }
164
165 111
    protected function lazyLoad ()
166
    {
167 111
        if (empty($this->jsonResponse))
168
        {
169 111
            $this->jsonResponse = $this->sendGet($this->urlEndPoint);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->sendGet($this->urlEndPoint) of type * is incompatible with the declared type array of property $jsonResponse.

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

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

Loading history...
170 111
            $this->assignResults();
171
        }
172 111
    }
173
174
    // ================================================================================================================
175
    //   Helper functions
176
    // ================================================================================================================
177
178
    /**
179
     * Assign an associative array from a JSON response and map them to instance variables
180
     *
181
     * @since 0.1.0
182
     */
183 113
    final protected function assignResults ()
184
    {
185 113
        foreach ($this->jsonResponse as $key => $val)
186
        {
187 113
            if (property_exists(get_called_class(), $key))
188
            {
189 113
                $this->$key = $val;
190
            }
191
        }
192 113
    }
193
194
    /**
195
     * Check if the current object has been marked as deleted from DaPulse. If so, throw an exception.
196
     *
197
     * @throws InvalidObjectException
198
     */
199 4
    final protected function checkInvalid ()
200
    {
201 4
        if ($this->deletedObject)
202
        {
203 2
            throw new InvalidObjectException("This object no longer exists on DaPulse", 2);
204
        }
205 4
    }
206
207
    /**
208
     * Mark an object as deleted
209
     *
210
     * @internal
211
     */
212
    final public function _markInvalid ()
213
    {
214
        $this->deletedObject = true;
215
    }
216
217
    /**
218
     * Store the value in an array if the value is not null. This function is a shortcut of setting values in an array
219
     * only if they are not null, if not leave them unset; used ideally for PUT requests.
220
     *
221
     * @param array  $array The array that will store all of the POST parameters
222
     * @param string $name  The name of the field
223
     * @param mixed  $value The value to be stored in a given field
224
     */
225 45
    final protected static function setIfNotNullOrEmpty (&$array, $name, $value)
226
    {
227 45
        if (!is_null($value) && !empty($value))
228
        {
229 40
            $array[$name] = $value;
230
        }
231 45
    }
232
233
    // ================================================================================================================
234
    //   Empty functions
235
    // ================================================================================================================
236
237
    /**
238
     * Overload this function if any class variables need to be initialized to a default value
239
     */
240 102
    protected function initializeValues ()
241
    {
242 102
    }
243
244
    // ================================================================================================================
245
    //   Lazy loading functions
246
    // ================================================================================================================
247
248
    /**
249
     * Inject data into the array that will be mapped into individual instance variables. This function must be called
250
     * **before** lazyCastAll() is called and maps the associative array to objects.
251
     *
252
     * @param array $target An array of associative arrays with data to be converted into objects
253
     * @param array $array  An associative array containing data to be merged with the key being the name of the
254
     *                      instance variable.
255
     *
256
     * @throws \InvalidArgumentException If either parameters are not arrays
257
     *
258
     * @since 0.1.0
259
     */
260 40
    final protected static function lazyInject (&$target, $array)
261
    {
262 40
        if (!is_array($target) || !is_array($array))
263
        {
264
            throw new \InvalidArgumentException("Both the target and array must be arrays");
265
        }
266
267
        // If the first element is an array, let's assume $target hasn't been lazily casted into objects
268 40
        if (is_array($target[0]))
269
        {
270 40
            foreach ($target as &$element)
271
            {
272 40
                $element = array_merge($element, $array);
273
            }
274
        }
275 40
    }
276
277
    /**
278
     * Convert the specified array into an array of object types if needed
279
     *
280
     * @param  array  $array      The array to check
281
     * @param  string $objectType The class name of the Objects the items should be
282
     * @param  bool   $lazyLoad   Should the casted objects be created lazily
283
     *
284
     * @since  0.2.0
285
     */
286 49
    final protected static function lazyCastAll (&$array, $objectType, $lazyLoad = false)
287
    {
288 49
        if (self::lazyCastNeededOnArray($objectType, $array))
289
        {
290 45
            $array = self::castArrayToObjectArray($objectType, $array, $lazyLoad);
291
        }
292 49
    }
293
294
    /**
295
     * Convert the specified item into the specified object if needed
296
     *
297
     * @param mixed  $target     The item to check
298
     * @param string $objectType The class name of the Objects the items should be
299
     *
300
     * @since 0.2.0
301
     */
302 11
    final protected static function lazyCast (&$target, $objectType)
303
    {
304 11
        if (self::lazyCastNeeded($target, $objectType))
305
        {
306 11
            $object = ($objectType[0] == "\\") ? $objectType : self::OBJ_NAMESPACE . $objectType;
307 11
            $target = new $object($target);
308
        }
309 11
    }
310
311
    /**
312
     * Check whether it is required for an array of JSON data to be converted into an array of the specified objects
313
     *
314
     * @param  string $objectType The class name of the Objects the items should be
315
     * @param  array  $array      The array to check
316
     *
317
     * @since  0.2.0
318
     *
319
     * @return bool True if the array needs to converted into an array of objects
320
     */
321 49
    final protected static function lazyCastNeededOnArray ($objectType, $array)
322
    {
323 49
        if (is_array($array) && count($array) == 0)
324
        {
325 1
            return false;
326
        }
327
328 48
        $firstItem = $array[0];
329
330 48
        return self::lazyCastNeeded($firstItem, $objectType);
331
    }
332
333
    /**
334
     * Check if an individual item needs to be lazily converted into an object
335
     *
336
     * @param  mixed  $target     The item to check
337
     * @param  string $objectType The class name of the Objects the items should be
338
     *
339
     * @since  0.2.0
340
     *
341
     * @return bool
342
     */
343 57
    final protected static function lazyCastNeeded ($target, $objectType)
344
    {
345 57
        $objectDefinition = ($objectType[0] === "\\") ? $objectType : self::OBJ_NAMESPACE . $objectType;
346
347 57
        return !($target instanceof $objectDefinition);
348
    }
349
350
    /**
351
     * Sends a GET request for a JSON array and casts the response into an array of objects
352
     *
353
     * @param  string $url       The API endpoint to call to get the JSON response from
354
     * @param  string $className The class name of the Object type to cast to
355
     * @param  array  $params    An associative array of URL parameters that will be passed to the specific call. For
356
     *                           example, limiting the number of results or the pagination of results. **Warning** The
357
     *                           API key does NOT need to be passed here
358
     *
359
     * @since  0.2.0
360
     *
361
     * @return array
362
     */
363 28
    final protected static function fetchAndCastToObjectArray ($url, $className, $params = [])
364
    {
365 28
        $objects = self::sendGet($url, $params);
366
367 28
        return self::castArrayToObjectArray($className, $objects);
368
    }
369
370
    /**
371
     * Convert an array of associative arrays into a specific object
372
     *
373
     * @param  string $className The class name of the Object type
374
     * @param  array  $objects   An associative array to be converted into an object
375
     * @param  bool   $lazyLoad  Should the casted objects be created lazily
376
     *
377
     * @since  0.2.0
378
     *
379
     * @return array An array of the specified objects
380
     */
381 68
    final protected static function castArrayToObjectArray ($className, $objects, $lazyLoad = false)
382
    {
383 68
        $class = self::OBJ_NAMESPACE . $className;
384 68
        $array = [];
385
386 68
        foreach ($objects as $post)
387
        {
388 68
            $array[] = new $class($post, $lazyLoad);
389
        }
390
391 68
        return $array;
392
    }
393
394
    // ================================================================================================================
395
    //   URL jobs functions
396
    // ================================================================================================================
397
398
    /**
399
     * Send a GET request to fetch the data from the specified URL
400
     *
401
     * @param  string $url    The API endpoint to call
402
     * @param  array  $params An associative array of URL parameters that will be passed to the specific call. For
403
     *                        example, limiting the number of results or the pagination of results. **Warning** The API
404
     *                        key does NOT need to be passed here
405
     *
406
     * @since  0.1.0
407
     *
408
     * @return mixed          An associative array of the JSON response from DaPulse
409
     */
410 111
    final protected static function sendGet ($url, $params = [])
411
    {
412 111
        $params["api_key"] = self::$apiKey;
413
414 111
        $urlQuery = new UrlQuery($url, $params);
415
416 111
        return $urlQuery->sendGet();
417
    }
418
419
    /**
420
     * Send a POST request to a specified URL
421
     *
422
     * @param  string $url
423
     * @param  array  $postParams
424
     * @param  array  $getParams
425
     *
426
     * @since  0.1.0
427
     *
428
     * @return mixed
429
     */
430 9
    final protected static function sendPost ($url, $postParams, $getParams = [])
431
    {
432 9
        return self::sendRequest("POST", $url, $postParams, $getParams);
433
    }
434
435
    /**
436
     * Send a PUT request to a specified URL
437
     *
438
     * @param  string $url
439
     * @param  array  $postParams
440
     * @param  array  $getParams
441
     *
442
     * @since  0.1.0
443
     *
444
     * @return mixed
445
     */
446 11
    final protected static function sendPut ($url, $postParams, $getParams = [])
447
    {
448 11
        return self::sendRequest("PUT", $url, $postParams, $getParams);
449
    }
450
451
    /**
452
     * Send a DELETE request to a specified URL
453
     *
454
     * @param  string $url
455
     * @param  array  $getParams
456
     *
457
     * @since  0.1.0
458
     *
459
     * @return mixed
460
     */
461 6
    final protected static function sendDelete ($url, $getParams = [])
462
    {
463 6
        return self::sendRequest("DELETE", $url, null, $getParams);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
464
    }
465
466
    /**
467
     * Send the appropriate URL request
468
     *
469
     * @param  string $type
470
     * @param  string $url
471
     * @param  array  $postParams
472
     * @param  array  $getParams
473
     *
474
     * @throws \InvalidArgumentException if $type not 'POST', 'PUT', or 'DELETE'
475
     *
476
     * @since  0.1.0
477
     *
478
     * @return mixed
479
     */
480 26
    private static function sendRequest ($type, $url, $postParams, $getParams)
481
    {
482 26
        $getParams["api_key"] = self::$apiKey;
483
484 26
        $urlQuery = new UrlQuery($url, $getParams);
485
486
        switch ($type)
487
        {
488 26
            case "POST":
489 9
                return $urlQuery->sendPost($postParams);
490
491 17
            case "PUT":
492 11
                return $urlQuery->sendPut($postParams);
493
494 6
            case "DELETE":
495 6
                return $urlQuery->sendDelete();
496
497
            default:
498
                throw new \InvalidArgumentException();
499
        }
500
    }
501
502
    // ================================================================================================================
503
    //   API key functions
504
    // ================================================================================================================
505
506
    /**
507
     * Get the base URL to use in all of the API calls
508
     *
509
     * @param  string|null $apiPrefix If the API end point is different from the class's constant, this value will be
510
     *                                used as the suffix for the API endpoint
511
     *
512
     * @since  0.1.0
513
     *
514
     * @return string The base URL to call
515
     */
516 115
    final protected static function apiEndpoint ($apiPrefix = null)
517
    {
518 115
        $apiSection = isset($apiPrefix) ? $apiPrefix : static::API_PREFIX;
519
520 115
        return sprintf("%s://%s/%s/%s", self::API_PROTOCOL, self::API_ENDPOINT, self::API_VERSION, $apiSection);
521
    }
522
523
    /**
524
     * Set the API for all calls to the API
525
     *
526
     * @param string $apiKey The API key used to access the DaPulse API
527
     *
528
     * @since 0.1.0
529
     */
530
    final public static function setApiKey ($apiKey)
531
    {
532
        self::$apiKey = $apiKey;
533
    }
534
}
535