HydratableTrait   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 97.67%

Importance

Changes 0
Metric Value
wmc 19
lcom 1
cbo 2
dl 0
loc 148
ccs 42
cts 43
cp 0.9767
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A hydrateClass() 0 18 4
B hydrateProperty() 0 27 7
A resolvePropertyName() 0 11 3
A isHydratableValue() 0 4 2
A checkObjectForErrors() 0 8 3
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: PBX_g33k
5
 * Date: 24-May-16
6
 * Time: 00:07
7
 */
8
9
namespace Pbxg33k\Traits;
10
11
/**
12
 * Class HydratableTrait
13
 *
14
 * This trait allows you to hydrate a class/object by passing an array or stdObj and let it hydrate itself by
15
 * calling either of the following methods:
16
 *  - hydrateClass($rawData)
17
 *
18
 * @author  Oguzhan Uysal <[email protected]>
19
 * @package Pbxg33k\Traits
20
 */
21
trait HydratableTrait
22
{
23
    use PropertyTrait;
24
    use ReflectionTrait;
25
26
    /**
27
     * List of types which are not used as objects
28
     *
29
     * @var array
30
     */
31
    public static $nonObjectTypes = ['string', 'int', 'integer', 'bool', 'boolean', 'array', 'float', 'mixed', 'null'];
32
33
    /**
34
     * List of classes which will take string arguments in constructor
35
     *
36
     * @var array
37
     */
38
    protected $giveDataInConstructor = ['\DateTime'];
39
40
    /**
41
     * Object constructor arguments to be passed when creating an object during conversion
42
     *
43
     * @var mixed
44
     */
45
    protected $objectConstructorArguments;
46
47
    /**
48
     * Converts a stdClass to models loaded in current context
49
     *
50
     * This method iterates over the passed $class
51
     * For each key, it looks for a setter and type.
52
     * If the value is an object, it initializes the object and assignes the initialized object.
53
     *
54
     * @param  object|array $class       class
55
     * @param  boolean      $failOnError Throw Exception if any error(s) occur
56
     *
57
     * @return object
58
     *
59
     * @throws \Exception if hydration failes AND $failOnError is true
60
     */
61 4
    public function hydrateClass($class, $failOnError = false)
62
    {
63 4
        $reflection = new \ReflectionClass($this);
64
65
        // Iterate over $class for properties
66 4
        foreach ($class as $itemKey => $itemValue) {
67
            // Convert key to a propertyname in $this
68
            try {
69 4
                $this->hydrateProperty($itemKey, $itemValue, $reflection);
70 4
            } catch (\Exception $e) {
71 4
                if ($failOnError) {
72
                    throw $e;
73
                }
74
            }
75 4
        }
76
77 4
        return $this;
78
    }
79
80
    /**
81
     * Hydrates property with value.
82
     * Value can be anything. If the property is supposed to be a Class of anykind we will try to instantiate it
83
     * and assign the class to the property
84
     *
85
     * @param                  $key
86
     * @param                  $value
87
     * @param \ReflectionClass $reflection
88
     *
89
     * @throws \Exception
90
     */
91 4
    protected function hydrateProperty($key, $value, \ReflectionClass $reflection)
92
    {
93 4
        $propertyName = $this->resolvePropertyName($key);
94
95
        try {
96
            // Check if property exists and assign a ReflectionProperty class to $reflectionProperty
97 4
            $reflectionProperty = $reflection->getProperty($propertyName);
98
            // Get the expected property class from the property's DocBlock
99 4
            $propertyClassName = ReflectionTrait::getClassFromDocComment($reflectionProperty->getDocComment(), true, $reflection);
100
            // Set argument for constructor (if any), in case we're dealing with an object (IE: DateTime)
101 4
            $this->objectConstructorArguments = (in_array($propertyClassName, $this->giveDataInConstructor)) ? $value : null;
102
103 4
            if (!in_array($propertyClassName, self::$nonObjectTypes) && class_exists($propertyClassName)) {
104 4
                $object = new $propertyClassName($this->objectConstructorArguments);
105 4
                $this->checkObjectForErrors($object, true);
106
107 4
                if (method_exists($object, 'hydrateClass') && $this->isHydratableValue($value)) {
108 4
                    $object->hydrateClass($value);
109 4
                }
110 4
                $value = $object;
111 4
            }
112
113 4
            $this->setPropertyValue($propertyName, $value, true);
114 4
        } catch (\Exception $e) {
115 4
            throw $e;
116
        }
117 4
    }
118
119
    /**
120
     * Resolves and returns propertyname
121
     *
122
     * @param $key
123
     *
124
     * @return mixed|string
125
     */
126 4
    private function resolvePropertyName($key)
127
    {
128 4
        $propertyName = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
129
130 4
        return (property_exists($this, $propertyName)) ? $propertyName :
131 4
            (property_exists($this, lcfirst($propertyName)) ? lcfirst($propertyName) :
132 4
                preg_replace_callback('/([A-Z])/', function($match) {
133 4
                    return strtolower('_' . $match[1]);
134 4
                }, lcfirst($propertyName))
135 4
            );
136
    }
137
138
    /**
139
     * Checks if the value can be hydrated for iteration
140
     *
141
     * @param $value
142
     *
143
     * @return bool
144
     */
145 4
    private function isHydratableValue($value)
146
    {
147 4
        return (is_array($value) || is_object($value));
148
    }
149
150
    /**
151
     * Checks (and fixes) objects against known errors
152
     *
153
     * @param Object &$object
154
     * @param bool   $fix Fix errors
155
     *
156
     * @return void
157
     *
158
     * @throws \Exception
159
     */
160 4
    private function checkObjectForErrors(&$object, $fix = false)
0 ignored issues
show
Unused Code introduced by
The parameter $fix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
161
    {
162 4
        if ($object instanceof \DateTime) {
163 4
            if ($this->objectConstructorArguments == null) {
164 4
                $object = null;
165 4
            }
166 4
        }
167
    }
168
}