Model::prepareAttribute()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
namespace Darya\ORM;
3
4
use ArrayAccess;
5
use ArrayIterator;
6
use IteratorAggregate;
7
use JsonSerializable;
8
use Serializable;
9
use Darya\ORM\Model\Accessor;
10
use Darya\ORM\Model\Mutator;
11
use Darya\ORM\Model\Transformer;
12
13
/**
14
 * Darya's abstract model implementation.
15
 *
16
 * @author Chris Andrew <[email protected]>
17
 */
18
abstract class Model implements ArrayAccess, IteratorAggregate, JsonSerializable, Serializable
19
{
20
	/**
21
	 * Attribute names as keys and types as values.
22
	 *
23
	 * @var array
24
	 */
25
	protected $attributes = array();
26
27
	/**
28
	 * The model data.
29
	 *
30
	 * @var array
31
	 */
32
	protected $data = array();
33
34
	/**
35
	 * Whether the model is currently in a valid state.
36
	 *
37
	 * @var bool
38
	 */
39
	protected $valid = false;
40
41
	/**
42
	 * Errors that occurred with validation.
43
	 *
44
	 * @var array
45
	 */
46
	protected $errors = array();
47
48
	/**
49
	 * The attribute that uniquely identifies the model.
50
	 *
51
	 * @var string
52
	 */
53
	protected $key;
54
55
	/**
56
	 * Attributes that have been modified.
57
	 *
58
	 * @var array
59
	 */
60
	protected $changed = array();
61
62
	/**
63
	 * Instantiate a new model.
64
	 *
65
	 * @param array $data [optional] Attributes to set on the model
66
	 */
67
	public function __construct($data = array())
68
	{
69
		$this->setMany($data);
70
	}
71
72
	/**
73
	 * Generate multiple instances of the model using arrays of attributes.
74
	 *
75
	 * @param array $rows
76
	 * @return array
77
	 */
78
	public static function generate(array $rows = array())
79
	{
80
		$instances = array();
81
82
		foreach ($rows as $key => $attributes) {
83
			$instances[$key] = new static($attributes);
84
		}
85
86
		return $instances;
87
	}
88
89
	/**
90
	 * Generate multiple instances of the model with the assumption that they
91
	 * aren't new.
92
	 *
93
	 * Equivalent to generate() but with a reinstate() call on each new model.
94
	 *
95
	 * @param array $rows
96
	 * @return array
97
	 */
98
	public static function hydrate(array $rows = array())
99
	{
100
		$instances = static::generate($rows);
101
102
		foreach ($instances as $instance) {
103
			$instance->reinstate();
104
		}
105
106
		return $instances;
107
	}
108
109
	/**
110
	 * Recursively convert a model, or set of models, to an array.
111
	 *
112
	 * @param Model[]|Model $model
113
	 * @return array
114
	 */
115
	public static function convertToArray($model)
116
	{
117
		if (is_object($model)) {
118
			if (method_exists($model, 'toArray')) {
119
				$model = $model->toArray();
120
			} else {
121
				$model = (array) $model;
122
			}
123
		}
124
125
		if (is_array($model)) {
126
			foreach ($model as $key => $value) {
127
				$model[$key] = $value ? static::convertToArray($value) : $value;
128
			}
129
		}
130
131
		return $model;
132
	}
133
134
	/**
135
	 * Recursively convert a model, or set of models, to JSON.
136
	 *
137
	 * @param Model[]|Model $model
138
	 * @param int           $options [optional] json_encode() options
139
	 * @return string
140
	 */
141
	public static function convertToJson($model, $options = null)
142
	{
143
		return json_encode(static::convertToArray($model), $options);
0 ignored issues
show
Bug introduced by
It seems like $options can also be of type null; however, parameter $flags of json_encode() does only seem to accept integer, 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

143
		return json_encode(static::convertToArray($model), /** @scrutinizer ignore-type */ $options);
Loading history...
144
	}
145
146
	/**
147
	 * Prepare the given attribute name.
148
	 *
149
	 * @param string $attribute
150
	 * @return string
151
	 */
152
	protected function prepareAttribute($attribute)
153
	{
154
		return strtolower($attribute);
155
	}
156
157
	/**
158
	 * Retrieve the attribute names of the model.
159
	 *
160
	 * @return array
161
	 */
162
	public function attributes()
163
	{
164
		return array_keys($this->attributes) ?: array_keys($this->data ?: []);
165
	}
166
167
	/**
168
	 * Retrieve the attribute types of the model.
169
	 *
170
	 * Attribute names are keys and types are values.
171
	 *
172
	 * @return array
173
	 */
174
	public function attributeTypes()
175
	{
176
		return $this->attributes;
177
	}
178
179
	/**
180
	 * Get the transformer for accessing attributes.
181
	 *
182
	 * @return Transformer
183
	 */
184
	public function getAttributeAccessor()
185
	{
186
		return new Accessor($this->dateFormat());
187
	}
188
189
	/**
190
	 * Get the transformer for mutating attributes.
191
	 *
192
	 * @return Transformer
193
	 */
194
	public function getAttributeMutator()
195
	{
196
		return new Mutator($this->dateFormat());
197
	}
198
199
	/**
200
	 * Retrieve the name of the attribute that uniquely identifies this model.
201
	 *
202
	 * Defaults to `'id'` if the `key` property is unset.
203
	 *
204
	 * @return string
205
	 */
206
	public function key()
207
	{
208
		if (!isset($this->key)) {
209
			return 'id';
210
		}
211
212
		return $this->prepareAttribute($this->key);
213
	}
214
215
	/**
216
	 * Retrieve the value of the attribute that uniquely identifies this model.
217
	 *
218
	 * @return mixed
219
	 */
220
	public function id()
221
	{
222
		return $this->access($this->key());
223
	}
224
225
	/**
226
	 * Retrieve raw attributes that have changed.
227
	 *
228
	 * @return array
229
	 */
230
	public function changed()
231
	{
232
		return array_intersect_key($this->data, array_flip($this->changed));
233
	}
234
235
	/**
236
	 * Clear the record of changed attributes on the model, declaring the
237
	 * current attributes as unmodified.
238
	 */
239
	public function reinstate()
240
	{
241
		$this->changed = array();
242
	}
243
244
	/**
245
	 * Retrieve the model's attributes.
246
	 *
247
	 * This method uses accessors to retrieve the data.
248
	 *
249
	 * @return array
250
	 */
251
	public function data()
252
	{
253
		$data = array();
254
255
		foreach (array_keys($this->data) as $attribute) {
256
			$data[$attribute] = $this->get($attribute);
257
		}
258
259
		return $data;
260
	}
261
262
	/**
263
	 * Retrieve the model's raw attributes.
264
	 *
265
	 * This method returns the raw data without using any accessors.
266
	 *
267
	 * @return array
268
	 */
269
	public function rawData()
270
	{
271
		return $this->data;
272
	}
273
274
	/**
275
	 * Determine whether the given attribute is set on the model.
276
	 *
277
	 * @param string $attribute
278
	 * @return bool
279
	 */
280
	public function has($attribute)
281
	{
282
		return isset($this->data[$this->prepareAttribute($attribute)]);
283
	}
284
285
	/**
286
	 * Determine whether the given attribute has a defined type.
287
	 *
288
	 * @param string $attribute
289
	 * @return bool
290
	 */
291
	protected function mutable($attribute)
292
	{
293
		return isset($this->attributes[$this->prepareAttribute($attribute)]);
294
	}
295
296
	/**
297
	 * Unmutate the given attribute to be retrieved.
298
	 *
299
	 * @param string $attribute
300
	 * @return mixed
301
	 */
302
	protected function access($attribute)
303
	{
304
		if (!$this->has($attribute)) {
305
			return null;
306
		}
307
308
		$attribute = $this->prepareAttribute($attribute);
309
310
		$value = $this->data[$attribute];
311
312
		if (!$this->mutable($attribute)) {
313
			return $value;
314
		}
315
316
		$type = $this->attributes[$attribute];
317
318
		$accessor = $this->getAttributeAccessor();
319
320
		return $accessor->transform($value, $type);
321
	}
322
323
	/**
324
	 * Mutate the given attribute to be set on the model.
325
	 *
326
	 * @param string $attribute
327
	 * @param mixed  $value [optional]
328
	 * @return mixed
329
	 */
330
	protected function mutate($attribute, $value = null)
331
	{
332
		if (!$this->mutable($attribute)) {
333
			return $value;
334
		}
335
336
		$type = $this->attributes[$this->prepareAttribute($attribute)];
337
338
		$mutator = $this->getAttributeMutator();
339
340
		return $mutator->transform($value, $type);
341
	}
342
343
	/**
344
	 * Retrieve the given attribute from the model.
345
	 *
346
	 * @param string $attribute
347
	 * @return mixed
348
	 */
349
	public function get($attribute)
350
	{
351
		return $this->access($attribute);
352
	}
353
354
	/**
355
	 * Set the value of an attribute on the model.
356
	 *
357
	 * If attribute is an array it will be forwarded to `setMany()`.
358
	 *
359
	 * @param array|string $attribute
360
	 * @param mixed        $value [optional]
361
	 */
362
	public function set($attribute, $value = null)
363
	{
364
		if (is_array($attribute)) {
365
			return $this->setMany($attribute);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setMany($attribute) targeting Darya\ORM\Model::setMany() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
366
		}
367
368
		$attribute = $this->prepareAttribute($attribute);
369
		$value     = $this->mutate($attribute, $value);
370
371
		if (!$this->has($attribute) || $value !== $this->data[$attribute]) {
372
			$this->data[$attribute] = $value;
373
			$this->changed = array_merge($this->changed, array($attribute));
374
		}
375
	}
376
377
	/**
378
	 * Set the values of the given attributes on the model.
379
	 *
380
	 * @param array $values
381
	 */
382
	public function setMany($values)
383
	{
384
		foreach ((array) $values as $attribute => $value){
385
			$this->set($attribute, $value);
386
		}
387
	}
388
389
	/**
390
	 * Remove the value of an attribute.
391
	 *
392
	 * @param string $attribute
393
	 */
394
	public function remove($attribute)
395
	{
396
		unset($this->data[$this->prepareAttribute($attribute)]);
397
	}
398
399
	/**
400
	 * Retrieve the format to use for date attributes.
401
	 *
402
	 * @return string
403
	 */
404
	public function dateFormat()
405
	{
406
		return 'Y-m-d H:i:s';
407
	}
408
409
	/**
410
	 * Set the `created` and `modified` attributes using the given timestamp.
411
	 *
412
	 * Defaults to the current system time if none is given. Only sets `created`
413
	 * attribute if `id` evaluates to false.
414
	 *
415
	 * @param int $time [optional]
416
	 */
417
	public function stamp($time = null)
418
	{
419
		$time = $time ?: time();
420
		$this->set('modified', $time);
421
422
		if (!$this->has('created')) {
423
			$this->set('created', $time);
424
		}
425
	}
426
427
	/**
428
	 * Validate all of the model's attributes.
429
	 *
430
	 * @return bool
431
	 */
432
	public function validate()
433
	{
434
		return $this->valid = !count($this->errors);
435
	}
436
437
	/**
438
	 * Retrieve an array of error strings generated by the last validation
439
	 * attempt.
440
	 *
441
	 * @return array
442
	 */
443
	public function errors()
444
	{
445
		return $this->errors;
446
	}
447
448
	/**
449
	 * Recursively convert the model to an array.
450
	 *
451
	 * @return array
452
	 */
453
	public function toArray()
454
	{
455
		return static::convertToArray($this->data());
456
	}
457
458
	/**
459
	 * Serialize the model as a JSON string.
460
	 *
461
	 * @return string
462
	 */
463
	public function toJson()
464
	{
465
		return json_encode($this->jsonSerialize());
466
	}
467
468
	/**
469
	 * Determine whether an attribute is set on the model. Shortcut for `has()`.
470
	 *
471
	 * @param string $property
472
	 * @return bool
473
	 */
474
	public function __isset($property)
475
	{
476
		return $this->has($property);
477
	}
478
479
	/**
480
	 * Retrieve an attribute from the model. Shortcut for `get()`.
481
	 *
482
	 * @param string $property
483
	 * @return mixed
484
	 */
485
	public function __get($property)
486
	{
487
		return $this->get($property);
488
	}
489
490
	/**
491
	 * Set an attribute's value. Shortcut for `set()`.
492
	 *
493
	 * @param string $property
494
	 * @param mixed  $value
495
	 */
496
	public function __set($property, $value)
497
	{
498
		$this->set($property, $value);
499
	}
500
501
	/**
502
	 * Unset an attribute's value. Shortcut for `remove()`.
503
	 *
504
	 * @param string $property
505
	 */
506
	public function __unset($property)
507
	{
508
		$this->remove($property);
509
	}
510
511
	/**
512
	 * Determine whether an attribute is set at the given offset.
513
	 *
514
	 * @param mixed $offset
515
	 * @return bool
516
	 */
517
	public function offsetExists($offset)
518
	{
519
		return $this->has($offset);
520
	}
521
522
	/**
523
	 * Get the attribute at the given offset.
524
	 *
525
	 * @param mixed $offset
526
	 * @return mixed
527
	 */
528
	public function offsetGet($offset)
529
	{
530
		return $this->get($offset);
531
	}
532
533
	/**
534
	 * Set the attribute at the given offset.
535
	 *
536
	 * @param mixed $offset
537
	 * @param mixed $value
538
	 */
539
	public function offsetSet($offset, $value)
540
	{
541
		$this->set($offset, $value);
542
	}
543
544
	/**
545
	 * Unset the attribute at the given offset.
546
	 *
547
	 * @param mixed $offset
548
	 */
549
	public function offsetUnset($offset)
550
	{
551
		$this->remove($offset);
552
	}
553
554
	/**
555
	 * Retrieve an iterator for the model's data.
556
	 *
557
	 * @return \Traversable
558
	 */
559
	public function getIterator()
560
	{
561
		return new ArrayIterator($this->data());
562
	}
563
564
	/**
565
	 * Serialize the model's raw data.
566
	 *
567
	 * @return string
568
	 */
569
	public function serialize()
570
	{
571
		return serialize($this->data);
572
	}
573
574
	/**
575
	 * Unserialize the model's raw data.
576
	 *
577
	 * @param string $serialized
578
	 */
579
	public function unserialize($serialized)
580
	{
581
		$this->data = unserialize($serialized);
582
	}
583
584
	/**
585
	 * Prepare the model's attributes for JSON serialization.
586
	 *
587
	 * @return array
588
	 */
589
	public function jsonSerialize()
590
	{
591
		return $this->toArray();
592
	}
593
}
594