Completed
Push — master ( 9c0313...1fcb8c )
by Chris
02:44
created

Model::convertToArray()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 17
rs 8.8571
cc 6
eloc 10
nc 6
nop 1
1
<?php
2
namespace Darya\ORM;
3
4
use ArrayAccess;
5
use ArrayIterator;
6
use Countable;
7
use IteratorAggregate;
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, 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
	 * @var array Errors that occured with validation
43
	 */
44
	protected $errors = array();
45
	
46
	/**
47
	 * The attribute that uniquely identifies the model.
48
	 * 
49
	 * @var string
50
	 */
51
	protected $key;
52
	
53
	/**
54
	 * Attributes that have been modified.
55
	 * 
56
	 * @var array
57
	 */
58
	protected $changed = array();
59
	
60
	/**
61
	 * Instantiate a new model.
62
	 * 
63
	 * @param array $data [optional] Attributes to set on the model
64
	 */
65
	public function __construct($data = array()) {
66
		$this->setMany($data);
67
	}
68
	
69
	/**
70
	 * Generate multiple instances of the model using arrays of attributes.
71
	 * 
72
	 * @param array $rows
73
	 * @return array
74
	 */
75
	public static function generate(array $rows = array()) {
76
		$instances = array();
77
		
78
		foreach ($rows as $key => $attributes) {
79
			$instances[$key] = new static($attributes);
80
		}
81
		
82
		return $instances;
83
	}
84
	
85
	/**
86
	 * Generate multiple instances of the model with the assumption that they
87
	 * aren't new.
88
	 * 
89
	 * Equivalent to generate() but with a reinstate() call on each new model.
90
	 * 
91
	 * @param array $rows
92
	 * @return array
93
	 */
94
	public static function hydrate(array $rows = array()) {
95
		$instances = static::generate($rows);
96
		
97
		foreach ($instances as $instance) {
98
			$instance->reinstate();
99
		}
100
		
101
		return $instances;
102
	}
103
	
104
	/**
105
	 * Recursively convert a model, or set of models, to an array.
106
	 * 
107
	 * @param Model[]|Model $model
108
	 * @return array
109
	 */
110
	public static function convertToArray($model) {
111
		if (is_object($model)) {
112
			if (method_exists($model, 'toArray')) {
113
				$model = $model->toArray();
114
			} else {
115
				$model = (array) $model;
116
			}
117
		}
118
		
119
		if (is_array($model)) {
120
			foreach ($model as $key => $value) {
121
				$model[$key] = $value ? static::convertToArray($value) : $value;
122
			}
123
		}
124
		
125
		return $model;
126
	}
127
	
128
	/**
129
	 * Recursively convert a model, or set of models, to JSON.
130
	 * 
131
	 * @param Model[]|Model $model
132
	 * @param int           $options [optional] json_encode() options
133
	 * @return string
134
	 */
135
	public static function convertToJson($model, $options = null) {
136
		return json_encode(static::convertToArray($model), $options);
137
	}
138
	
139
	/**
140
	 * Prepare the given attribute name.
141
	 * 
142
	 * @param string $attribute
143
	 * @return string
144
	 */
145
	protected function prepareAttribute($attribute) {
146
		$attribute = strtolower($attribute);
147
		
148
		if ($attribute === 'id') {
149
			return $this->key();
150
		}
151
		
152
		return $attribute;
153
	}
154
	
155
	/**
156
	 * Retrieve the attribute names of the model.
157
	 * 
158
	 * @return array
159
	 */
160
	public function attributes() {
161
		return array_keys($this->attributes) ?: array_keys($this->data ?: array());
162
	}
163
	
164
	/**
165
	 * Retrieve the attribute types of the model.
166
	 * 
167
	 * Attribute names are keys and types are values.
168
	 * 
169
	 * @return array
170
	 */
171
	public function attributeTypes() {
172
		return $this->attributes;
173
	}
174
	
175
	/**
176
	 * Get the transformer for accessing attributes.
177
	 * 
178
	 * @return Transformer
179
	 */
180
	public function getAttributeAccessor() {
181
		return new Accessor($this->dateFormat());
182
	}
183
	
184
	/**
185
	 * Get the transformer for mutating attributes.
186
	 * 
187
	 * @return Transformer
188
	 */
189
	public function getAttributeMutator() {
190
		return new Mutator($this->dateFormat());
191
	}
192
	
193
	/**
194
	 * Retrieve the name of the attribute that uniquely identifies this model.
195
	 * 
196
	 * Defaults to 'id' if the `key` property is unset.
197
	 * 
198
	 * @return string
199
	 */
200
	public function key() {
201
		if (!isset($this->key)) {
202
			return 'id';
203
		}
204
		
205
		return $this->prepareAttribute($this->key);
206
	}
207
	
208
	/**
209
	 * Retrieve the value of the attribute that uniquely identifies this model.
210
	 * 
211
	 * @return mixed
212
	 */
213
	public function id() {
214
		return $this->access($this->key());
215
	}
216
	
217
	/**
218
	 * Retrieve raw attributes that have changed.
219
	 * 
220
	 * @return array
221
	 */
222
	public function changed() {
223
		return array_intersect_key($this->data, array_flip($this->changed));
224
	}
225
	
226
	/**
227
	 * Clear the record of changed attributes on the model, declaring the
228
	 * current attributes as unmodified.
229
	 */
230
	public function reinstate() {
231
		$this->changed = array();
232
	}
233
	
234
	/**
235
	 * Retrieve the model's attributes.
236
	 * 
237
	 * This method uses accessors to retrieve the data.
238
	 * 
239
	 * @return array
240
	 */
241
	public function data() {
242
		$data = array();
243
		
244
		foreach (array_keys($this->data) as $attribute) {
245
			$data[$attribute] = $this->get($attribute);
246
		}
247
		
248
		return $data;
249
	}
250
	
251
	/**
252
	 * Retrieve the model's raw attributes.
253
	 * 
254
	 * This method returns the raw data without using any accessors.
255
	 * 
256
	 * @return array
257
	 */
258
	public function rawData() {
259
		return $this->data;
260
	}
261
	
262
	/**
263
	 * Determine whether the given attribute is set on the model.
264
	 * 
265
	 * @param string $attribute
266
	 * @return bool
267
	 */
268
	public function has($attribute) {
269
		return isset($this->data[$this->prepareAttribute($attribute)]);
270
	}
271
	
272
	/**
273
	 * Determine whether the given attribute has a defined type.
274
	 * 
275
	 * @param string $attribute
276
	 * @return bool
277
	 */
278
	protected function mutable($attribute) {
279
		return isset($this->attributes[$this->prepareAttribute($attribute)]);
280
	}
281
	
282
	/**
283
	 * Unmutate the given attribute to be retrieved.
284
	 * 
285
	 * @param string $attribute
286
	 * @return mixed
287
	 */
288
	protected function access($attribute) {
289
		if ($this->has($attribute)) {
290
			$attribute = $this->prepareAttribute($attribute);
291
			
292
			$value = $this->data[$attribute];
293
			
294
			if (!$this->mutable($attribute)) {
295
				return $value;
296
			}
297
			
298
			$type = $this->attributes[$attribute];
299
			
300
			$accessor = $this->getAttributeAccessor();
301
			
302
			return $accessor->transform($value, $type);
303
		}
304
		
305
		return null;
306
	}
307
	
308
	/**
309
	 * Mutate the given attribute to be set on the model.
310
	 * 
311
	 * @param string $attribute
312
	 * @param mixed  $value [optional]
313
	 * @return mixed
314
	 */
315
	protected function mutate($attribute, $value = null) {
316
		if (!$this->mutable($attribute)) {
317
			return $value;
318
		}
319
		
320
		$type = $this->attributes[$this->prepareAttribute($attribute)];
321
		
322
		$mutator = $this->getAttributeMutator();
323
		
324
		return $mutator->transform($value, $type);
325
	}
326
	
327
	/**
328
	 * Retrieve the given attribute from the model.
329
	 * 
330
	 * @param string $attribute
331
	 * @return mixed
332
	 */
333
	public function get($attribute) {
334
		if ($attribute === 'id') {
335
			return $this->id();
336
		}
337
		
338
		return $this->access($attribute);
339
	}
340
	
341
	/**
342
	 * Set the value of an attribute on the model.
343
	 * 
344
	 * If attribute is an array it will be forwarded to `setMany()`.
345
	 * 
346
	 * @param array|string $attribute
347
	 * @param mixed        $value [optional]
348
	 */
349
	public function set($attribute, $value = null) {
350
		if (is_array($attribute)) {
351
			return $this->setMany($attribute);
352
		}
353
		
354
		$attribute = $this->prepareAttribute($attribute);
355
		$value     = $this->mutate($attribute, $value);
356
		
357
		if (!$this->has($attribute) || $value !== $this->data[$attribute]) {
358
			$this->data[$attribute] = $value;
359
			$this->changed = array_merge($this->changed, array($attribute));
360
		}
361
	}
362
	
363
	/**
364
	 * Set the values of the given attributes on the model.
365
	 * 
366
	 * @param array $values
367
	 */
368
	public function setMany($values) {
369
		foreach ((array) $values as $attribute => $value) {
370
			$this->set($attribute, $value);
371
		}
372
	}
373
	
374
	/**
375
	 * Remove the value of an attribute.
376
	 * 
377
	 * @param string $attribute
378
	 */
379
	public function remove($attribute) {
380
		unset($this->data[$this->prepareAttribute($attribute)]);
381
	}
382
	
383
	/**
384
	 * Retrieve the format to use for date attributes.
385
	 * 
386
	 * @return string
387
	 */
388
	public function dateFormat() {
389
		return 'Y-m-d H:i:s';
390
	}
391
	
392
	/**
393
	 * Set the `created` and `modified` attributes using the given timestamp.
394
	 * 
395
	 * Defaults to the current system time if none is given. Only sets `created`
396
	 * attribute if `id` evaluates to false.
397
	 * 
398
	 * @param int $time [optional]
399
	 */
400
	public function stamp($time = null) {
401
		$time = $time ?: time();
402
		$this->set('modified', $time);
403
		
404
		if (!$this->id()) {
405
			$this->set('created', $time);
406
		}
407
	}
408
	
409
	/**
410
	 * Validate all of the model's attributes.
411
	 * 
412
	 * @return bool
413
	 */
414
	public function validate() {
415
		return $this->valid = !count($this->errors);
416
	}
417
	
418
	/**
419
	 * Retrieve an array of error strings generated by the last validation
420
	 * attempt.
421
	 * 
422
	 * @return array
423
	 */
424
	public function errors() {
425
		return $this->errors;
426
	}
427
	
428
	/**
429
	 * Recursively convert the model to an array.
430
	 * 
431
	 * @return array
432
	 */
433
	public function toArray() {
434
		return static::convertToArray($this->data());
435
	}
436
	
437
	/**
438
	 * Serialize the model as a JSON string.
439
	 * 
440
	 * @return string
441
	 */
442
	public function toJson() {
443
		return json_encode($this->jsonSerialize());
444
	}
445
	
446
	/**
447
	 * Determine whether an attribute is set on the model. Shortcut for `set()`.
448
	 * 
449
	 * @param string $property
450
	 * @return bool
451
	 */
452
	public function __isset($property) {
453
		return $this->has($property);
454
	}
455
	
456
	/**
457
	 * Retrieve an attribute from the model. Shortcut for `get()` and `id()`.
458
	 * 
459
	 * @param string $property
460
	 * @return mixed
461
	 */
462
	public function __get($property) {
463
		return $this->get($property);
464
	}
465
	
466
	/**
467
	 * Set an attribute's value. Shortcut for `set()`.
468
	 * 
469
	 * @param string $property
470
	 * @param mixed  $value
471
	 */
472
	public function __set($property, $value) {
473
		$this->set($property, $value);
474
	}
475
	
476
	/**
477
	 * Unset an attribute's value. Shortcut for `remove()`.
478
	 * 
479
	 * @param string $property
480
	 */
481
	public function __unset($property) {
482
		$this->remove($property);
483
	}
484
	
485
	/**
486
	 * Determine whether an attribute is set at the given offset.
487
	 * 
488
	 * @param mixed $offset
489
	 * @return bool
490
	 */
491
	public function offsetExists($offset) {
492
		return $this->has($offset);
493
	}
494
	
495
	/**
496
	 * Get the attribute at the given offset.
497
	 * 
498
	 * @param mixed $offset
499
	 * @return mixed
500
	 */
501
	public function offsetGet($offset) {
502
		return $this->get($offset);
503
	}
504
	
505
	/**
506
	 * Set the attribute at the given offset.
507
	 * 
508
	 * @param mixed $offset
509
	 * @param mixed $value
510
	 */
511
	public function offsetSet($offset, $value) {
512
		$this->set($offset, $value);
513
	}
514
	
515
	/**
516
	 * Unset the attribute at the given offset.
517
	 * 
518
	 * @param mixed $offset
519
	 */
520
	public function offsetUnset($offset) {
521
		$this->remove($offset);
522
	}
523
	
524
	/**
525
	 * Retrieve an iterator for the model's data.
526
	 * 
527
	 * @return \Traversable
528
	 */
529
	public function getIterator() {
530
		return new ArrayIterator($this->data());
531
	}
532
	
533
	/**
534
	 * Serialize the model's raw data.
535
	 * 
536
	 * @return string
537
	 */
538
	public function serialize() {
539
		return serialize($this->data);
540
	}
541
	
542
	/**
543
	 * Unserialize the model's raw data.
544
	 * 
545
	 * @param string $serialized
546
	 */
547
	public function unserialize($serialized) {
548
		$this->data = unserialize($serialized);
549
	}
550
	
551
	/**
552
	 * Prepare the model's attributes for JSON serialization.
553
	 * 
554
	 * @return array
555
	 */
556
	public function jsonSerialize() {
557
		return $this->toArray();
558
	}
559
	
560
}
561