Completed
Push — master ( 9ddc72...f1aaa9 )
by Chris
02:34
created

Model::offsetSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

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