Completed
Push — master ( dec0fe...94a512 )
by Chris
02:44
created

Model::hydrate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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