Completed
Push — master ( 6ac80e...ea27d1 )
by Chris
03:18
created

Model   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 586
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 586
rs 3.28
c 0
b 0
f 0
wmc 64
lcom 2
cbo 3

42 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A generate() 0 10 2
A hydrate() 0 10 2
A convertToArray() 0 18 6
A convertToJson() 0 4 1
A prepareAttribute() 0 10 2
A attributes() 0 4 3
A attributeTypes() 0 4 1
A getAttributeAccessor() 0 4 1
A getAttributeMutator() 0 4 1
A key() 0 8 2
A id() 0 4 1
A changed() 0 4 1
A reinstate() 0 4 1
A data() 0 10 2
A rawData() 0 4 1
A has() 0 4 1
A mutable() 0 4 1
A access() 0 20 3
A mutate() 0 12 2
A get() 0 8 2
A set() 0 14 4
A setMany() 0 6 2
A remove() 0 4 1
A dateFormat() 0 4 1
A stamp() 0 9 3
A validate() 0 4 1
A errors() 0 4 1
A toArray() 0 4 1
A toJson() 0 4 1
A __isset() 0 4 1
A __get() 0 4 1
A __set() 0 4 1
A __unset() 0 4 1
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 1
A getIterator() 0 4 1
A serialize() 0 4 1
A unserialize() 0 4 1
A jsonSerialize() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Model, and based on these observations, apply Extract Interface, too.

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);
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
		$attribute = strtolower($attribute);
155
156
		if ($attribute === 'id') {
157
			return $this->key();
158
		}
159
160
		return $attribute;
161
	}
162
163
	/**
164
	 * Retrieve the attribute names of the model.
165
	 *
166
	 * @return array
167
	 */
168
	public function attributes()
169
	{
170
		return array_keys($this->attributes) ?: array_keys($this->data ?: array());
171
	}
172
173
	/**
174
	 * Retrieve the attribute types of the model.
175
	 *
176
	 * Attribute names are keys and types are values.
177
	 *
178
	 * @return array
179
	 */
180
	public function attributeTypes()
181
	{
182
		return $this->attributes;
183
	}
184
185
	/**
186
	 * Get the transformer for accessing attributes.
187
	 *
188
	 * @return Transformer
189
	 */
190
	public function getAttributeAccessor()
191
	{
192
		return new Accessor($this->dateFormat());
193
	}
194
195
	/**
196
	 * Get the transformer for mutating attributes.
197
	 *
198
	 * @return Transformer
199
	 */
200
	public function getAttributeMutator()
201
	{
202
		return new Mutator($this->dateFormat());
203
	}
204
205
	/**
206
	 * Retrieve the name of the attribute that uniquely identifies this model.
207
	 *
208
	 * Defaults to 'id' if the `key` property is unset.
209
	 *
210
	 * @return string
211
	 */
212
	public function key()
213
	{
214
		if (!isset($this->key)) {
215
			return 'id';
216
		}
217
218
		return $this->prepareAttribute($this->key);
219
	}
220
221
	/**
222
	 * Retrieve the value of the attribute that uniquely identifies this model.
223
	 *
224
	 * @return mixed
225
	 */
226
	public function id()
227
	{
228
		return $this->access($this->key());
229
	}
230
231
	/**
232
	 * Retrieve raw attributes that have changed.
233
	 *
234
	 * @return array
235
	 */
236
	public function changed()
237
	{
238
		return array_intersect_key($this->data, array_flip($this->changed));
239
	}
240
241
	/**
242
	 * Clear the record of changed attributes on the model, declaring the
243
	 * current attributes as unmodified.
244
	 */
245
	public function reinstate()
246
	{
247
		$this->changed = array();
248
	}
249
250
	/**
251
	 * Retrieve the model's attributes.
252
	 *
253
	 * This method uses accessors to retrieve the data.
254
	 *
255
	 * @return array
256
	 */
257
	public function data()
258
	{
259
		$data = array();
260
261
		foreach (array_keys($this->data) as $attribute) {
262
			$data[$attribute] = $this->get($attribute);
263
		}
264
265
		return $data;
266
	}
267
268
	/**
269
	 * Retrieve the model's raw attributes.
270
	 *
271
	 * This method returns the raw data without using any accessors.
272
	 *
273
	 * @return array
274
	 */
275
	public function rawData()
276
	{
277
		return $this->data;
278
	}
279
280
	/**
281
	 * Determine whether the given attribute is set on the model.
282
	 *
283
	 * @param string $attribute
284
	 * @return bool
285
	 */
286
	public function has($attribute)
287
	{
288
		return isset($this->data[$this->prepareAttribute($attribute)]);
289
	}
290
291
	/**
292
	 * Determine whether the given attribute has a defined type.
293
	 *
294
	 * @param string $attribute
295
	 * @return bool
296
	 */
297
	protected function mutable($attribute)
298
	{
299
		return isset($this->attributes[$this->prepareAttribute($attribute)]);
300
	}
301
302
	/**
303
	 * Unmutate the given attribute to be retrieved.
304
	 *
305
	 * @param string $attribute
306
	 * @return mixed
307
	 */
308
	protected function access($attribute)
309
	{
310
		if (!$this->has($attribute)) {
311
			return null;
312
		}
313
314
		$attribute = $this->prepareAttribute($attribute);
315
316
		$value = $this->data[$attribute];
317
318
		if (!$this->mutable($attribute)) {
319
			return $value;
320
		}
321
322
		$type = $this->attributes[$attribute];
323
324
		$accessor = $this->getAttributeAccessor();
325
326
		return $accessor->transform($value, $type);
327
	}
328
329
	/**
330
	 * Mutate the given attribute to be set on the model.
331
	 *
332
	 * @param string $attribute
333
	 * @param mixed  $value [optional]
334
	 * @return mixed
335
	 */
336
	protected function mutate($attribute, $value = null)
337
	{
338
		if (!$this->mutable($attribute)) {
339
			return $value;
340
		}
341
342
		$type = $this->attributes[$this->prepareAttribute($attribute)];
343
344
		$mutator = $this->getAttributeMutator();
345
346
		return $mutator->transform($value, $type);
347
	}
348
349
	/**
350
	 * Retrieve the given attribute from the model.
351
	 *
352
	 * @param string $attribute
353
	 * @return mixed
354
	 */
355
	public function get($attribute)
356
	{
357
		if ($attribute === 'id') {
358
			return $this->id();
359
		}
360
361
		return $this->access($attribute);
362
	}
363
364
	/**
365
	 * Set the value of an attribute on the model.
366
	 *
367
	 * If attribute is an array it will be forwarded to `setMany()`.
368
	 *
369
	 * @param array|string $attribute
370
	 * @param mixed        $value [optional]
371
	 */
372
	public function set($attribute, $value = null)
373
	{
374
		if (is_array($attribute)) {
375
			return $this->setMany($attribute);
376
		}
377
378
		$attribute = $this->prepareAttribute($attribute);
379
		$value     = $this->mutate($attribute, $value);
380
381
		if (!$this->has($attribute) || $value !== $this->data[$attribute]) {
382
			$this->data[$attribute] = $value;
383
			$this->changed = array_merge($this->changed, array($attribute));
384
		}
385
	}
386
387
	/**
388
	 * Set the values of the given attributes on the model.
389
	 *
390
	 * @param array $values
391
	 */
392
	public function setMany($values)
393
	{
394
		foreach ((array) $values as $attribute => $value){
395
			$this->set($attribute, $value);
396
		}
397
	}
398
399
	/**
400
	 * Remove the value of an attribute.
401
	 *
402
	 * @param string $attribute
403
	 */
404
	public function remove($attribute)
405
	{
406
		unset($this->data[$this->prepareAttribute($attribute)]);
407
	}
408
409
	/**
410
	 * Retrieve the format to use for date attributes.
411
	 *
412
	 * @return string
413
	 */
414
	public function dateFormat()
415
	{
416
		return 'Y-m-d H:i:s';
417
	}
418
419
	/**
420
	 * Set the `created` and `modified` attributes using the given timestamp.
421
	 *
422
	 * Defaults to the current system time if none is given. Only sets `created`
423
	 * attribute if `id` evaluates to false.
424
	 *
425
	 * @param int $time [optional]
426
	 */
427
	public function stamp($time = null)
428
	{
429
		$time = $time ?: time();
430
		$this->set('modified', $time);
431
432
		if (!$this->id()) {
433
			$this->set('created', $time);
434
		}
435
	}
436
437
	/**
438
	 * Validate all of the model's attributes.
439
	 *
440
	 * @return bool
441
	 */
442
	public function validate()
443
	{
444
		return $this->valid = !count($this->errors);
445
	}
446
447
	/**
448
	 * Retrieve an array of error strings generated by the last validation
449
	 * attempt.
450
	 *
451
	 * @return array
452
	 */
453
	public function errors()
454
	{
455
		return $this->errors;
456
	}
457
458
	/**
459
	 * Recursively convert the model to an array.
460
	 *
461
	 * @return array
462
	 */
463
	public function toArray()
464
	{
465
		return static::convertToArray($this->data());
466
	}
467
468
	/**
469
	 * Serialize the model as a JSON string.
470
	 *
471
	 * @return string
472
	 */
473
	public function toJson()
474
	{
475
		return json_encode($this->jsonSerialize());
476
	}
477
478
	/**
479
	 * Determine whether an attribute is set on the model. Shortcut for `set()`.
480
	 *
481
	 * @param string $property
482
	 * @return bool
483
	 */
484
	public function __isset($property)
485
	{
486
		return $this->has($property);
487
	}
488
489
	/**
490
	 * Retrieve an attribute from the model. Shortcut for `get()` and `id()`.
491
	 *
492
	 * @param string $property
493
	 * @return mixed
494
	 */
495
	public function __get($property)
496
	{
497
		return $this->get($property);
498
	}
499
500
	/**
501
	 * Set an attribute's value. Shortcut for `set()`.
502
	 *
503
	 * @param string $property
504
	 * @param mixed  $value
505
	 */
506
	public function __set($property, $value)
507
	{
508
		$this->set($property, $value);
509
	}
510
511
	/**
512
	 * Unset an attribute's value. Shortcut for `remove()`.
513
	 *
514
	 * @param string $property
515
	 */
516
	public function __unset($property)
517
	{
518
		$this->remove($property);
519
	}
520
521
	/**
522
	 * Determine whether an attribute is set at the given offset.
523
	 *
524
	 * @param mixed $offset
525
	 * @return bool
526
	 */
527
	public function offsetExists($offset)
528
	{
529
		return $this->has($offset);
530
	}
531
532
	/**
533
	 * Get the attribute at the given offset.
534
	 *
535
	 * @param mixed $offset
536
	 * @return mixed
537
	 */
538
	public function offsetGet($offset)
539
	{
540
		return $this->get($offset);
541
	}
542
543
	/**
544
	 * Set the attribute at the given offset.
545
	 *
546
	 * @param mixed $offset
547
	 * @param mixed $value
548
	 */
549
	public function offsetSet($offset, $value)
550
	{
551
		$this->set($offset, $value);
552
	}
553
554
	/**
555
	 * Unset the attribute at the given offset.
556
	 *
557
	 * @param mixed $offset
558
	 */
559
	public function offsetUnset($offset)
560
	{
561
		$this->remove($offset);
562
	}
563
564
	/**
565
	 * Retrieve an iterator for the model's data.
566
	 *
567
	 * @return \Traversable
568
	 */
569
	public function getIterator()
570
	{
571
		return new ArrayIterator($this->data());
572
	}
573
574
	/**
575
	 * Serialize the model's raw data.
576
	 *
577
	 * @return string
578
	 */
579
	public function serialize()
580
	{
581
		return serialize($this->data);
582
	}
583
584
	/**
585
	 * Unserialize the model's raw data.
586
	 *
587
	 * @param string $serialized
588
	 */
589
	public function unserialize($serialized)
590
	{
591
		$this->data = unserialize($serialized);
592
	}
593
594
	/**
595
	 * Prepare the model's attributes for JSON serialization.
596
	 *
597
	 * @return array
598
	 */
599
	public function jsonSerialize()
600
	{
601
		return $this->toArray();
602
	}
603
}
604