Completed
Push — develop ( 5430ad...61b35f )
by Jimmy
27s queued 11s
created

Model::unserialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Spinen\ConnectWise\Support;
4
5
use ArrayAccess;
6
use ArrayIterator;
7
use Carbon\Carbon;
8
use Countable;
9
use Illuminate\Contracts\Support\Arrayable;
10
use Illuminate\Contracts\Support\Jsonable;
11
use Illuminate\Support\Str;
12
use InvalidArgumentException;
13
use IteratorAggregate;
14
use JsonSerializable;
15
use Serializable;
16
use Spinen\ConnectWise\Api\Client;
17
18
/**
19
 * Class Model
20
 *
21
 * This class is heavily modeled after Laravel's Eloquent model.  We are wanting the API to be familiar as we use
22
 * Laravel for most of our projects & want it to be very easily to have our developers use it.  Additionally, it is
23
 * just so well done, that there there is not a reason to not copy/reuse some of the code.
24
 *
25
 * @package Spinen\ConnectWise\Support
26
 */
27
abstract class Model implements
28
    ArrayAccess,
29
    Arrayable,
30
    Countable,
31
    IteratorAggregate,
32
    Jsonable,
33
    JsonSerializable,
34
    Serializable
35
{
36
    /**
37
     * The collection of attributes for the model
38
     *
39
     * @var array
40
     */
41
    protected $attributes = [];
42
43
    /**
44
     * Properties that need to be casts to a specific object or type
45
     *
46
     * @var array
47
     */
48
    protected $casts = [];
49
50
    /**
51
     * Client instance to go get related properties
52
     *
53
     * @var Client|null
54
     */
55
    protected $client;
56
57
    /**
58
     * Model constructor.
59
     *
60
     * @param array $attributes
61
     * @param Client|null $client
62
     */
63 13
    public function __construct(array $attributes, Client $client = null)
64
    {
65 13
        $this->client = $client;
66 13
        $this->fill($attributes);
67
    }
68
69
    /**
70
     * Magic method to allow getting related items
71
     *
72
     * @param string $method
73
     * @param mixed $arguments
74
     *
75
     * @return mixed
76
     */
77
    public function __call($method, $arguments)
78
    {
79
        // Call existing method
80
        if (method_exists($this, $method)) {
81
            return call_user_func_array([$this, $method], $arguments);
82
        }
83
84
        // Look to see if the property has a relationship to call
85
        if ($this->client && ($this->{$method}->_info ?? null)) {
86
            foreach ($this->{$method}['_info'] as $k => $v) {
87
                if (Str::startsWith($v, $this->client->getUrl())) {
88
                    // Cache so that other request will not trigger additional calls
89
                    $this->{$method} = $this->client->get(Str::replaceFirst($this->client->getUrl(), '', $v));
90
91
                    return $this->{$method};
92
                }
93
            }
94
        }
95
96
        trigger_error('Call to undefined method ' . __CLASS__ . '::' . $method . '()', E_USER_ERROR);
97
    }
98
99
    /**
100
     * Only return the attributes for a var_dump
101
     *
102
     * This object proxies the properties to the keys in the attributes array,so only
103
     * expose it when doing a var_dump as the other properties are not needed in debugging.
104
     *
105
     * @return array
106
     */
107 1
    public function __debugInfo()
108
    {
109 1
        return $this->attributes;
110
    }
111
112
    /**
113
     * Allow the attributes of the model to be accessed like a public property
114
     *
115
     * @param string $attribute
116
     *
117
     * @return mixed
118
     */
119 4
    public function __get($attribute)
120
    {
121 4
        return $this->getAttribute($attribute);
122
    }
123
124
    /**
125
     * Allow checking to see if the model has an attribute set
126
     *
127
     * @param string $attribute
128
     *
129
     * @return bool
130
     */
131 4
    public function __isset($attribute)
132
    {
133 4
        return array_key_exists($attribute, $this->attributes);
134
    }
135
136
    /**
137
     * Set a property on the model in the attributes
138
     *
139
     * @param string $attribute
140
     * @param mixed  $value
141
     */
142 1
    public function __set($attribute, $value)
143
    {
144 1
        $this->setAttribute($attribute, $value);
145
    }
146
147
    /**
148
     * Convert the model to its string representation.
149
     *
150
     * @return string
151
     */
152 1
    public function __toString()
153
    {
154 1
        return $this->toJson();
155
    }
156
157
    /**
158
     * Unset a property on the model in the attributes
159
     *
160
     * @param string $attribute
161
     */
162 1
    public function __unset($attribute)
163
    {
164 1
        unset($this->attributes[$attribute]);
165
    }
166
167
    /**
168
     * Cast a item to a specific object or type
169
     *
170
     * @param mixed  $value
171
     * @param string $cast
172
     *
173
     * @return mixed
174
     */
175 13
    public function castTo($value, $cast)
176
    {
177 13
        if (is_null($value) || is_object($value)) {
178 11
            return $value;
179
        }
180
181 13
        if (Carbon::class === $cast) {
182 11
            return Carbon::parse($value);
183
        }
184
185 13
        if (class_exists($cast)) {
186 11
            return new $cast((array) $value);
187
        }
188
189 13
        if (strcasecmp('json', $cast) == 0) {
190 11
            return json_encode((array)$value);
191
        }
192
193 13
        if (strcasecmp('collection', $cast) == 0) {
194 11
            return new Collection((array)$value);
195
        }
196
197 13
        if (in_array($cast, ['bool', 'boolean'])) {
198 13
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
199
        }
200
201
        $cast_types = [
202 13
            'array',
203
            'bool',
204
            'boolean',
205
            'double',
206
            'float',
207
            'int',
208
            'integer',
209
            'null',
210
            'object',
211
            'string',
212
        ];
213
214 13
        if (!in_array($cast, $cast_types)) {
215 1
            throw new InvalidArgumentException(sprintf("Attributes cannot be casted to [%s] type.", $cast));
216
        }
217
218 13
        settype($value, $cast);
219
220
        // settype returns true/false for pass/fail, not the value
221 13
        return $value;
222
    }
223
224
    /**
225
     * Count the number of properties.
226
     *
227
     * @return int
228
     */
229 1
    public function count()
230
    {
231 1
        return count($this->attributes);
232
    }
233
234
    /**
235
     * Store the collection of attributes on the model
236
     *
237
     * @param array $attributes
238
     *
239
     * @return $this
240
     */
241 13
    public function fill(array $attributes)
242
    {
243 13
        foreach ($attributes as $attribute => $value) {
244 13
            $this->setAttribute($attribute, $value);
245
        }
246
247 13
        return $this;
248
    }
249
250
    /**
251
     * Check to see if there is a getter for the attribute
252
     *
253
     * @param string $attribute
254
     *
255
     * @return bool
256
     */
257 4
    public function hasGetter($attribute)
258
    {
259 4
        return method_exists($this, $this->getterMethodName($attribute));
260
    }
261
262
    /**
263
     * Check to see if there is a setter for the attribute
264
     *
265
     * @param string $attribute
266
     *
267
     * @return bool
268
     */
269 13
    public function hasSetter($attribute)
270
    {
271 13
        return method_exists($this, $this->setterMethodName($attribute));
272
    }
273
274
    /**
275
     * Is the attribute supposed to be cast
276
     *
277
     * @param string $attribute
278
     *
279
     * @return bool
280
     */
281 13
    public function hasCast($attribute)
282
    {
283 13
        $cast = $this->getCasts($attribute);
284
285 13
        return !empty($cast) && is_string($cast);
286
    }
287
288
    /**
289
     * Get the attribute from the model
290
     *
291
     * @param string $attribute
292
     *
293
     * @return mixed
294
     */
295 4
    public function getAttribute($attribute)
296
    {
297 4
        if (!$attribute) {
298
            return;
299
        }
300
301 4
        if ($this->hasGetter($attribute)) {
302 1
            return $this->{$this->getterMethodName($attribute)}();
303
        }
304
305 4
        if (isset($this->$attribute)) {
306 4
            return $this->attributes[$attribute];
307
        };
308
309
        trigger_error('Undefined property:'. __CLASS__ . '::$' . $attribute);
310
    }
311
312
    /**
313
     * Get the array of cast or a specific cast for an attribute
314
     *
315
     * @param null $attribute
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $attribute is correct as it would always require null to be passed?
Loading history...
316
     *
317
     * @return mixed
318
     */
319 13
    public function getCasts($attribute = null)
320
    {
321 13
        if (array_key_exists($attribute, $this->casts)) {
322 13
            return $this->casts[$attribute];
323
        }
324
325 2
        return $this->casts;
326
    }
327
328
    /**
329
     * Get an iterator for the attributes.
330
     *
331
     * @return ArrayIterator
332
     */
333 1
    public function getIterator()
334
    {
335 1
        return new ArrayIterator($this->attributes);
336
    }
337
338
    /**
339
     * Build the name of the getter for an attribute
340
     *
341
     * @param string $attribute
342
     *
343
     * @return string
344
     */
345 4
    protected function getterMethodName($attribute)
346
    {
347 4
        return 'get' . Str::studly($attribute) . 'Attribute';
348
    }
349
350
    /**
351
     * Serialize Json (convert it to an array)
352
     *
353
     * @return array
354
     */
355 2
    public function jsonSerialize()
356
    {
357 2
        return $this->toArray();
358
    }
359
360
    /**
361
     * Allow the model to behave like an associate array, so see if attribute is set
362
     *
363
     * @param string $attribute
364
     *
365
     * @return boolean
366
     */
367 2
    public function offsetExists($attribute)
368
    {
369 2
        return isset($this->{$attribute});
370
    }
371
372
    /**
373
     * Allow the model to behave like an associate array, so get attribute
374
     *
375
     * @param string $attribute
376
     *
377
     * @return mixed
378
     */
379 4
    public function offsetGet($attribute)
380
    {
381 4
        return $this->{$attribute};
382
    }
383
384
    /**
385
     * Allow the model to behave like an associate array, so set attribute
386
     *
387
     * @param string $attribute
388
     * @param mixed  $value
389
     */
390 1
    public function offsetSet($attribute, $value)
391
    {
392 1
        $this->{$attribute} = $value;
393
    }
394
395
    /**
396
     * Allow the model to behave like an associate array, so unset attribute
397
     *
398
     * @param mixed $attribute
399
     *
400
     * @return void
401
     */
402 1
    public function offsetUnset($attribute)
403
    {
404 1
        unset($this->{$attribute});
405
    }
406
407
    /**
408
     * Serialize the attributes
409
     *
410
     * @return string
411
     */
412 1
    public function serialize()
413
    {
414 1
        return serialize($this->attributes);
415
    }
416
417
    /**
418
     * Set value on an attribute
419
     *
420
     * Since there can be a setter for an attribute, look to see if there is one to delegate the setting.  Then see if
421
     * the attribute is supposed to be cast to a specific value before setting.  Finally, store the value on the model.
422
     *
423
     * @param string $attribute
424
     * @param mixed  $value
425
     *
426
     * @return $this
427
     */
428 13
    public function setAttribute($attribute, $value)
429
    {
430 13
        if ($this->hasSetter($attribute)) {
431 1
            return $this->{$this->setterMethodName($attribute)}($value);
432
        }
433
434 13
        if ($this->hasCast($attribute)) {
435 13
            $value = $this->castTo($value, $this->getCasts($attribute));
0 ignored issues
show
Bug introduced by
It seems like $this->getCasts($attribute) can also be of type array; however, parameter $cast of Spinen\ConnectWise\Support\Model::castTo() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

435
            $value = $this->castTo($value, /** @scrutinizer ignore-type */ $this->getCasts($attribute));
Loading history...
436
        }
437
438 13
        $this->attributes[$attribute] = $value;
439
440 13
        return $this;
441
    }
442
443
    /**
444
     * Build the name of the setter for an attribute
445
     *
446
     * @param string $attribute
447
     *
448
     * @return string
449
     */
450 13
    protected function setterMethodName($attribute)
451
    {
452 13
        return 'set' . Str::studly($attribute) . 'Attribute';
453
    }
454
455
    /**
456
     * Return the model as an array
457
     *
458
     * @return array
459
     */
460 3
    public function toArray()
461
    {
462
        // TODO: Need to actually roll through the attributes & make sure that nested objects are converted
463 3
        return $this->attributes;
464
    }
465
466
    /**
467
     * Return the model as JSON
468
     *
469
     * @param int $options
470
     *
471
     * @return string
472
     */
473 2
    public function toJson($options = 0)
474
    {
475 2
        return json_encode($this->jsonSerialize(), $options);
476
    }
477
478
    /**
479
     * Unserialize the attributes
480
     *
481
     * @param string $serialized
482
     */
483 1
    public function unserialize($serialized)
484
    {
485 1
        $this->attributes = unserialize($serialized);
486
    }
487
}
488