Completed
Push — develop ( 21c609...db7143 )
by Vladimir
04:02
created

ApiObject   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 521
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 79.77%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 50
c 9
b 0
f 0
lcom 1
cbo 1
dl 0
loc 521
ccs 71
cts 89
cp 0.7977
rs 8.6206

23 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 40 7
A jsonSerialize() 0 4 1
A lazyLoad() 0 8 2
A getJson() 0 4 1
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
B lazyInject() 0 16 5
A lazyCastAll() 0 7 2
A lazyCast() 0 8 3
A lazyCastNeededOnArray() 0 8 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.dapulse.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 70
     *                             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 70
     *                             the information is requested
104 70
     *
105 70
     * @throw \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 70
     *
108
     * @since 0.1.0
109
     */
110
    public function __construct ($idOrArray, $lazyLoad = false)
111
    {
112 70
        $staticClass = explode("\\", get_called_class());
113
        $staticClass = end($staticClass);
114 70
115
        if (is_null($idOrArray))
116
        {
117 70
            throw new \InvalidArgumentException("You may not initialize $staticClass with null.");
118
        }
119
120
        if (!is_array($idOrArray))
121
        {
122 70
            $this->urlEndPoint = sprintf("%s/%d.json", self::apiEndpoint(), $idOrArray);
123
        }
124 70
125 70
        if ($this->arrayConstructionOnly && !is_array($idOrArray))
126 70
        {
127
            throw new \InvalidArgumentException("A $staticClass cannot be fetched from an ID.");
128
        }
129
130
        $this->initializeValues();
131
132
        if (is_array($idOrArray))
133
        {
134
            $this->jsonResponse = $idOrArray;
135
            $this->assignResults();
136
        }
137
        else
138
        {
139
            if ($lazyLoad)
140
            {
141
                $this->id = $idOrArray;
142
                $this->jsonResponse = [];
143
            }
144
            else
145
            {
146
                $this->lazyLoad();
147
            }
148
        }
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function jsonSerialize()
155
    {
156
        return $this->jsonResponse;
157
    }
158
159
    protected function lazyLoad()
160
    {
161
        if (empty($this->jsonResponse))
162
        {
163
            $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...
164 70
            $this->assignResults();
165
        }
166 70
    }
167
168 70
    // ================================================================================================================
169
    //   Getter functions
170 70
    // ================================================================================================================
171
172
    /**
173 70
     * Access the JSON response from DaPulse directly
174
     *
175
     * @api
176
     * @deprecated 0.3.0 Feed this object to json_encode() to get the JSON representation of this object instead or call
177
     *                   `jsonSerialize()`
178
     * @since  0.1.0
179
     * @todo   Remove at 0.4.0 or next breaking release
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
180
     * @return array
181
     */
182
    final public function getJson ()
183
    {
184
        return $this->jsonSerialize();
185
    }
186
187
    // ================================================================================================================
188
    //   Helper functions
189
    // ================================================================================================================
190
191
    /**
192
     * Assign an associative array from a JSON response and map them to instance variables
193
     *
194
     * @since 0.1.0
195
     */
196
    final protected function assignResults ()
197
    {
198
        foreach ($this->jsonResponse as $key => $val)
199
        {
200
            if (property_exists(get_called_class(), $key))
201
            {
202
                $this->$key = $val;
203
            }
204
        }
205
    }
206
207
    /**
208
     * Check if the current object has been marked as deleted from DaPulse. If so, throw an exception.
209
     *
210
     * @throws InvalidObjectException
211 63
     */
212
    final protected function checkInvalid ()
213 63
    {
214
        if ($this->deletedObject)
215
        {
216
            throw new InvalidObjectException("This object no longer exists on DaPulse", 2);
217
        }
218
    }
219
220
    /**
221
     * Mark an object as deleted
222
     *
223
     * @internal
224
     */
225
    final public function _markInvalid ()
226
    {
227
        $this->deletedObject = true;
228
    }
229
230
    /**
231 15
     * Store the value in an array if the value is not null. This function is a shortcut of setting values in an array
232
     * only if they are not null, if not leave them unset; used ideally for PUT requests.
233 15
     *
234
     * @param array  $array The array that will store all of the POST parameters
235
     * @param string $name  The name of the field
236
     * @param mixed  $value The value to be stored in a given field
237
     */
238
    final protected static function setIfNotNullOrEmpty (&$array, $name, $value)
239 15
    {
240
        if (!is_null($value) || !empty($value))
241 15
        {
242
            $array[$name] = $value;
243 15
        }
244
    }
245
246 15
    // ================================================================================================================
247
    //   Empty functions
248
    // ================================================================================================================
249
250
    /**
251
     * Overload this function if any class variables need to be initialized to a default value
252
     */
253
    protected function initializeValues ()
254
    {
255
    }
256 18
257
    // ================================================================================================================
258 18
    //   Lazy loading functions
259
    // ================================================================================================================
260 17
261
    /**
262 18
     * Inject data into the array that will be mapped into individual instance variables. This function must be called
263
     * **before** lazyCastAll() is called and maps the associative array to objects.
264
     *
265
     * @param array $target An array of associative arrays with data to be converted into objects
266
     * @param array $array  An associative array containing data to be merged with the key being the name of the
267
     *                      instance variable.
268
     *
269
     * @since 0.1.0
270
     *
271
     * @throw \InvalidArgumentException If either parameters are not arrays
272 4
     */
273
    final protected static function lazyInject (&$target, $array)
274 4
    {
275
        if (!is_array($target) || !is_array($array))
276 4
        {
277 4
            throw new \InvalidArgumentException("Both the target and array must be arrays");
278
        }
279 4
280
        // If the first element is an array, let's assume $target hasn't been lazily casted into objects
281
        if (is_array($target[0]))
282
        {
283
            foreach ($target as &$element)
284
            {
285
                $element = array_merge($element, $array);
286
            }
287
        }
288
    }
289
290
    /**
291 18
     * Convert the specified array into an array of object types if needed
292
     *
293 18
     * @param  string $objectType The class name of the Objects the items should be
294
     * @param  array  $array      The array to check
295 17
     *
296
     * @since  0.2.0
297 17
     */
298
    final protected static function lazyCastAll (&$array, $objectType)
299
    {
300
        if (self::lazyCastNeededOnArray($objectType, $array))
301
        {
302
            $array = self::castArrayToObjectArray($objectType, $array);
303
        }
304
    }
305
306
    /**
307
     * Convert the specified item into the specified object if needed
308
     *
309
     * @param mixed  $target     The item to check
310 21
     * @param string $objectType The class name of the Objects the items should be
311
     *
312 21
     * @since 0.2.0
313
     */
314 21
    final protected static function lazyCast (&$target, $objectType)
315
    {
316
        if (self::lazyCastNeeded($target, $objectType))
317
        {
318
            $object = ($objectType[0] == "\\") ? $objectType : self::OBJ_NAMESPACE . $objectType;
319
            $target = new $object($target);
320
        }
321
    }
322
323
    /**
324
     * Check whether it is required for an array of JSON data to be converted into an array of the specified objects
325
     *
326
     * @param  string $objectType The class name of the Objects the items should be
327
     * @param  array  $array      The array to check
328
     *
329
     * @since  0.2.0
330 20
     *
331
     * @return bool True if the array needs to converted into an array of objects
332 20
     */
333
    final protected static function lazyCastNeededOnArray ($objectType, $array)
334 20
    {
335
        if (is_array($array) && count($array) == 0) { return false; }
336
337
        $firstItem = $array[0];
338
339
        return self::lazyCastNeeded($firstItem, $objectType);
340
    }
341
342
    /**
343
     * Check if an individual item needs to be lazily converted into an object
344
     *
345
     * @param  mixed  $target     The item to check
346
     * @param  string $objectType The class name of the Objects the items should be
347 35
     *
348
     * @since  0.2.0
349 35
     *
350 35
     * @return bool
351
     */
352 35
    final protected static function lazyCastNeeded ($target, $objectType)
353
    {
354 35
        $objectDefinition = ($objectType[0] === "\\") ? $objectType : self::OBJ_NAMESPACE . $objectType;
355
356
        return !($target instanceof $objectDefinition);
357 35
    }
358
359
    /**
360
     * Sends a GET request for a JSON array and casts the response into an array of objects
361
     *
362
     * @param  string $url       The API endpoint to call to get the JSON response from
363
     * @param  string $className The class name of the Object type to cast to
364
     * @param  array  $params    An associative array of URL parameters that will be passed to the specific call. For
365
     *                           example, limiting the number of results or the pagination of results. **Warning** The
366
     *                           API key does NOT need to be passed here
367
     *
368
     * @since  0.2.0
369
     *
370
     * @return array
371
     */
372
    final protected static function fetchAndCastToObjectArray ($url, $className, $params = array())
373
    {
374
        $objects = self::sendGet($url, $params);
375
376 70
        return self::castArrayToObjectArray($className, $objects);
377
    }
378 70
379
    /**
380 70
     * Convert an array of associative arrays into a specific object
381
     *
382 70
     * @param  string $className The class name of the Object type
383
     * @param  array  $objects   An associative array to be converted into an object
384
     *
385
     * @since  0.2.0
386
     *
387
     * @return array An array of the specified objects
388
     */
389
    final protected static function castArrayToObjectArray ($className, $objects)
390
    {
391
        $class = self::OBJ_NAMESPACE . $className;
392
        $array = array();
393
394
        foreach ($objects as $post)
395
        {
396 3
            $array[] = new $class($post);
397
        }
398 3
399
        return $array;
400
    }
401
402
    // ================================================================================================================
403
    //   URL jobs functions
404
    // ================================================================================================================
405
406
    /**
407
     * Send a GET request to fetch the data from the specified URL
408
     *
409
     * @param  string $url    The API endpoint to call
410
     * @param  array  $params An associative array of URL parameters that will be passed to the specific call. For
411
     *                        example, limiting the number of results or the pagination of results. **Warning** The API
412 7
     *                        key does NOT need to be passed here
413
     *
414 7
     * @since  0.1.0
415
     *
416
     * @return mixed          An associative array of the JSON response from DaPulse
417
     */
418
    final protected static function sendGet ($url, $params = array())
419
    {
420
        $params["api_key"] = self::$apiKey;
421
422
        $urlQuery = new UrlQuery($url, $params);
423
424
        return $urlQuery->sendGet();
425
    }
426
427
    /**
428
     * Send a POST request to a specified URL
429
     *
430
     * @param  string $url
431
     * @param  array  $postParams
432
     * @param  array  $getParams
433
     *
434
     * @since  0.1.0
435
     *
436
     * @return mixed
437
     */
438
    final protected static function sendPost ($url, $postParams, $getParams = array())
439
    {
440
        return self::sendRequest("POST", $url, $postParams, $getParams);
441
    }
442
443
    /**
444 10
     * Send a PUT request to a specified URL
445
     *
446 10
     * @param  string $url
447
     * @param  array  $postParams
448 10
     * @param  array  $getParams
449
     *
450
     * @since  0.1.0
451
     *
452 10
     * @return mixed
453 3
     */
454
    final protected static function sendPut ($url, $postParams, $getParams = array())
455 7
    {
456 7
        return self::sendRequest("PUT", $url, $postParams, $getParams);
457
    }
458
459
    /**
460
     * Send a DELETE request to a specified URL
461
     *
462
     * @param  string $url
463
     * @param  array  $getParams
464
     *
465
     * @since  0.1.0
466
     *
467
     * @return mixed
468
     */
469
    final protected static function sendDelete ($url, $getParams = array())
470
    {
471
        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...
472
    }
473
474
    /**
475
     * Send the appropriate URL request
476
     *
477
     * @param  string $type
478
     * @param  string $url
479
     * @param  array  $postParams
480 70
     * @param  array  $getParams
481
     *
482 70
     * @since  0.1.0
483
     *
484 70
     * @return mixed
485
     */
486
    private static function sendRequest ($type, $url, $postParams, $getParams)
487
    {
488
        $getParams["api_key"] = self::$apiKey;
489
490
        $urlQuery = new UrlQuery($url, $getParams);
491
492
        switch ($type)
493
        {
494 70
            case "POST":
495
                return $urlQuery->sendPost($postParams);
496 70
497 70
            case "PUT":
498
                return $urlQuery->sendPut($postParams);
499
500
            case "DELETE":
501
                return $urlQuery->sendDelete();
502
503
            default:
504
                throw new \InvalidArgumentException();
505
        }
506
    }
507
508
    // ================================================================================================================
509
    //   API key functions
510
    // ================================================================================================================
511
512
    /**
513
     * Get the base URL to use in all of the API calls
514
     *
515
     * @param  string|null $apiPrefix If the API end point is different from the class's constant, this value will be
516
     *                                used as the suffix for the API endpoint
517
     *
518
     * @since  0.1.0
519
     *
520
     * @return string The base URL to call
521
     */
522
    final protected static function apiEndpoint ($apiPrefix = NULL)
523
    {
524
        $apiSection = isset($apiPrefix) ? $apiPrefix : static::API_PREFIX;
525
526
        return sprintf("%s://%s/%s/%s", self::API_PROTOCOL, self::API_ENDPOINT, self::API_VERSION, $apiSection);
527
    }
528
529
    /**
530
     * Set the API for all calls to the API
531
     *
532
     * @param string $apiKey The API key used to access the DaPulse API
533
     *
534
     * @since 0.1.0
535
     */
536
    final public static function setApiKey ($apiKey)
537
    {
538
        self::$apiKey = $apiKey;
539
    }
540
}
541