Completed
Push — master ( fb3047...828efc )
by Rafael
02:41
created

Array2Object::camelize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * LICENSE: This file is subject to the terms and conditions defined in
5
 * file 'LICENSE', which is part of this source code package.
6
 *
7
 * @copyright 2016 Copyright(c) - All rights reserved.
8
 */
9
10
namespace Rafrsr\LibArray2Object;
11
12
use Symfony\Component\PropertyAccess\PropertyAccessor;
13
14
/**
15
 * Using the property names and the common property annotations
16
 * populate a object instance with the values of the array recursively
17
 */
18
class Array2Object
19
{
20
21
    /**
22
     * createObject
23
     *
24
     * @param string $class class to create object or instance
25
     * @param array  $data  array of data
26
     *
27
     * @return mixed
28
     *
29
     * @throws \InvalidArgumentException
30
     */
31
    static public function createObject($class, array $data)
32
    {
33
        if (is_string($class) && class_exists($class)) {
34
            $object = new $class;
35
        } else {
36
            $object = new $class;
37
        }
38
39
        self::populate($object, $data);
40
41
        return $object;
42
    }
43
44
    /**
45
     * @param object $object object instance to populate
46
     * @param array  $data   array of data to apply
47
     *
48
     * @throws \InvalidArgumentException
49
     */
50
    static public function populate($object, array $data)
51
    {
52
        if (!is_object($object)) {
53
            throw new \InvalidArgumentException('The first param should be a object.');
54
        }
55
56
        $propertyAccessor = new PropertyAccessor();
57
58
        $reflClass = new \ReflectionClass($object);
59
60
        foreach (self::getClassProperties($reflClass) as $property) {
61
            foreach ($data as $key => $value) {
62
                if ($propertyAccessor->isWritable($object, $key) && self::isSameProperty($property->getName(), $key)) {
63
                    $types = self::getPropertyTypes($property);
64
                    $value = self::parseValue($value, $types, new \ReflectionClass($property->class));
65
                    $propertyAccessor->setValue($object, $key, $value);
66
                }
67
            }
68
        }
69
    }
70
71
    /**
72
     * Get array of class properties including parents private properties
73
     *
74
     * @param \ReflectionClass $refClass
75
     *
76
     * @return array|\ReflectionProperty[]
77
     */
78
    static private function getClassProperties(\ReflectionClass $refClass)
79
    {
80
        $props = $refClass->getProperties();
81
        $props_arr = [];
82
        foreach ($props as $prop) {
83
            $f = $prop->getName();
84
85
            $props_arr[$f] = $prop;
86
        }
87
        if ($parentClass = $refClass->getParentClass()) {
88
            $parent_props_arr = self::getClassProperties($parentClass);//RECURSION
89
            if (count($parent_props_arr) > 0) {
90
                $props_arr = array_merge($parent_props_arr, $props_arr);
91
            }
92
        }
93
94
        return $props_arr;
95
    }
96
97
    /**
98
     * @param $propertyName
99
     * @param $givenName
100
     *
101
     * @return bool
102
     */
103
    static private function isSameProperty($propertyName, $givenName)
104
    {
105
        if ($propertyName === $givenName
106
            || $propertyName === self::camelize($givenName) //ErrorCode = error_code
107
            || $propertyName === lcfirst(self::camelize($givenName)) // errorCode => error_code
108
            || $propertyName === strtolower(self::camelize($givenName)) // errorcode => error_code
109
            || strtolower($propertyName) === $givenName // errorCode => errorcode
110
        ) {
111
            return true;
112
        }
113
114
        return false;
115
    }
116
117
    /**
118
     * Parse a value using given types
119
     *
120
     * @param mixed            $value
121
     * @param array            $types
122
     * @param \ReflectionClass $context
123
     *
124
     * @return array|bool|float|int|string
125
     */
126
    static private function parseValue($value, $types, \ReflectionClass $context)
127
    {
128
        foreach ($types as $type) {
129
130
            switch ($type) {
131
                case 'string':
132
                    $value = (string)$value;
133
                    break;
134
                case 'integer':
135
                case 'int':
136
                    $value = (integer)$value;
137
                    break;
138
                case 'float':
139
                case 'double':
140
                    $value = (float)$value;
141
                    break;
142
                case 'bool':
143
                case 'boolean':
144
                    if (is_string($value) && strtolower($value) === 'false') {
145
                        $value = false;
146
                    } elseif (is_string($value) && strtolower($value) === 'true') {
147
                        $value = true;
148
                    } else {
149
                        $value = (boolean)$value;
150
                    }
151
                    break;
152
                case '\DateTime':
153
                case 'DateTime':
154
                    $value = new $type($value);
155
                    break;
156
                case 'array':
157
                    if (is_array($value)) {
158
                        foreach ($value as $key => $arrayValue) {
159
                            $arrayType = [];
160
                            if (array_key_exists(1, $types) && $types[1] !== 'array') {
161
                                $arrayType = str_replace('[]', null, $types[1]);
162
                            }
163
                            $value[$key] = self::parseValue($arrayValue, [$arrayType], $context);
164
                        }
165
                    }
166
                    break;
167
                default:
168
                    $value = self::valueToObject($value, $type, $context);
169
170
            }
171
        }
172
173
        return $value;
174
    }
175
176
    /**
177
     * Convert a array value into a object or array of objects
178
     *
179
     * @param  mixed           $value
180
     * @param  string          $type
181
     * @param \ReflectionClass $context
182
     *
183
     * @return array
184
     */
185
    static private function valueToObject($value, $type, \ReflectionClass $context)
186
    {
187
        $isArrayOfObjects = (strpos($type, '[]') !== false);
188
        $type = str_replace('[]', null, $type);
189
        $className = null;
190
191
        //use the type as class
192
        if (class_exists($type)) {
193
            $className = $type;
194
        }
195
196
        //try to get the class from use statements in the class file
197
        if ($className === null) {
198
            $classContent = file_get_contents($context->getFileName());
199
            preg_match("/use\s+([\w\\\]+$type);/", $classContent, $matches);
200
            if (isset($matches[1]) && class_exists($matches[1])) {
201
                $className = $matches[1];
202
            }
203
        }
204
205
        //use the same namespace as class container
206
        if ($className === null && class_exists($context->getNamespaceName() . "\\" . $type)) {
207
            $className = $context->getNamespaceName() . "\\" . $type;
208
        }
209
210
        if (is_array($value) && $className !== null && class_exists($className)) {
211
            //array of objects
212
            if ($isArrayOfObjects) {
213
                $newValue = [];
214
                foreach ($value as $key => $item) {
215
                    if (is_array($item)) {
216
                        $newValue[$key] = self::createObject($className, $value);;
217
                    } else {
218
                        $newValue[$key] = $item;
219
                    }
220
                }
221
            } else { //simple object
222
                $newValue = self::createObject($className, $value);
223
            }
224
        }
225
226
        if (isset($newValue)) {
227
            $value = $newValue;
228
        }
229
230
        return $value;
231
    }
232
233
    /**
234
     * @param $name
235
     *
236
     * @return string
237
     */
238
    static private function camelize($name)
239
    {
240
        return strtr(ucwords(strtr($name, ['_' => ' '])), [' ' => '']);
241
    }
242
243
    /**
244
     * @param \ReflectionProperty $property
245
     *
246
     * @return array
247
     */
248
    static private function getPropertyTypes(\ReflectionProperty $property)
249
    {
250
        $doc = $property->getDocComment();
251
        preg_match('/@var\s([\w\\\|\[\]]+)/', $doc, $matches);
252
        $types = [];
253
        if (isset($matches[1])) {
254
            $types = explode('|', $matches[1]);
255
        }
256
257
        return $types;
258
    }
259
}