Completed
Push — master ( 06117a...d54351 )
by Rafael
03:01
created

Array2Object   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 197
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 7
Bugs 1 Features 0
Metric Value
wmc 33
c 7
b 1
f 0
lcom 1
cbo 8
dl 0
loc 197
rs 9.3999

9 Methods

Rating   Name   Duplication   Size   Complexity  
A registerParser() 0 10 3
A createObject() 0 12 3
B populate() 0 22 6
A setup() 0 13 1
A getClassProperties() 0 18 4
B isSameProperty() 0 13 6
B parseValue() 0 21 7
A camelize() 0 4 1
A getPropertyTypes() 0 11 2
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 Rafrsr\LibArray2Object\Parser\BooleanParser;
13
use Rafrsr\LibArray2Object\Parser\DateTimeParser;
14
use Rafrsr\LibArray2Object\Parser\FloatParser;
15
use Rafrsr\LibArray2Object\Parser\IntegerParser;
16
use Rafrsr\LibArray2Object\Parser\ObjectParser;
17
use Rafrsr\LibArray2Object\Parser\StringParser;
18
use Rafrsr\LibArray2Object\Parser\ValueParserInterface;
19
use Symfony\Component\PropertyAccess\PropertyAccessor;
20
21
/**
22
 * Using the property names and the common property annotations
23
 * populate a object instance with the values of the array recursively
24
 */
25
class Array2Object
26
{
27
    /**
28
     * @var array|ValueParserInterface
29
     */
30
    private static $parsers;
31
32
    /**
33
     * registerParser
34
     *
35
     * @param ValueParserInterface|array $parsers
36
     */
37
    static public function registerParser($parsers)
38
    {
39
        if (is_array($parsers)) {
40
            foreach ($parsers as $parser) {
41
                self::$parsers[] = $parser;
42
            }
43
        } else {
44
            self::$parsers[] = $parsers;
45
        }
46
    }
47
48
    /**
49
     * createObject
50
     *
51
     * @param string $class class to create object or instance
52
     * @param array  $data  array of data
53
     *
54
     * @return mixed
55
     *
56
     * @throws \InvalidArgumentException
57
     */
58
    static public function createObject($class, array $data)
59
    {
60
        if (is_string($class) && class_exists($class)) {
61
            $object = new $class;
62
        } else {
63
            $object = new $class;
64
        }
65
66
        self::populate($object, $data);
67
68
        return $object;
69
    }
70
71
    /**
72
     * @param object $object object instance to populate
73
     * @param array  $data   array of data to apply
74
     *
75
     * @throws \InvalidArgumentException
76
     */
77
    static public function populate($object, array $data)
78
    {
79
        self::setup();//register parsers
80
81
        if (!is_object($object)) {
82
            throw new \InvalidArgumentException('The first param should be a object.');
83
        }
84
85
        $propertyAccessor = new PropertyAccessor();
86
87
        $reflClass = new \ReflectionClass($object);
88
89
        foreach (self::getClassProperties($reflClass) as $property) {
90
            foreach ($data as $key => $value) {
91
                if ($propertyAccessor->isWritable($object, $key) && self::isSameProperty($property->getName(), $key)) {
92
                    $types = self::getPropertyTypes($property);
93
                    $value = self::parseValue($value, $types, $property, $object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type array; however, Rafrsr\LibArray2Object\Array2Object::parseValue() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
94
                    $propertyAccessor->setValue($object, $key, $value);
95
                }
96
            }
97
        }
98
    }
99
100
    /**
101
     * setup
102
     */
103
    static private function setup()
104
    {
105
        self::registerParser(
106
            [
107
                new StringParser(),
108
                new BooleanParser(),
109
                new IntegerParser(),
110
                new FloatParser(),
111
                new DateTimeParser(),
112
                new ObjectParser()
113
            ]
114
        );
115
    }
116
117
    /**
118
     * Get array of class properties including parents private properties
119
     *
120
     * @param \ReflectionClass $refClass
121
     *
122
     * @return array|\ReflectionProperty[]
123
     */
124
    static private function getClassProperties(\ReflectionClass $refClass)
125
    {
126
        $props = $refClass->getProperties();
127
        $props_arr = [];
128
        foreach ($props as $prop) {
129
            $f = $prop->getName();
130
131
            $props_arr[$f] = $prop;
132
        }
133
        if ($parentClass = $refClass->getParentClass()) {
134
            $parent_props_arr = self::getClassProperties($parentClass);//RECURSION
135
            if (count($parent_props_arr) > 0) {
136
                $props_arr = array_merge($parent_props_arr, $props_arr);
137
            }
138
        }
139
140
        return $props_arr;
141
    }
142
143
    /**
144
     * @param $propertyName
145
     * @param $givenName
146
     *
147
     * @return bool
148
     */
149
    static private function isSameProperty($propertyName, $givenName)
150
    {
151
        if ($propertyName === $givenName
152
            || $propertyName === self::camelize($givenName) //ErrorCode = error_code
153
            || $propertyName === lcfirst(self::camelize($givenName)) // errorCode => error_code
154
            || $propertyName === strtolower(self::camelize($givenName)) // errorcode => error_code
155
            || strtolower($propertyName) === $givenName // errorCode => errorcode
156
        ) {
157
            return true;
158
        }
159
160
        return false;
161
    }
162
163
    /**
164
     * Parse a value using given types
165
     *
166
     * @param mixed               $value
167
     * @param array               $types
168
     * @param \ReflectionProperty $property
169
     * @param object              $object
170
     *
171
     * @return array|bool|float|int|string
172
     */
173
    static private function parseValue($value, $types, \ReflectionProperty $property, $object)
174
    {
175
        foreach ($types as $type) {
176
177
            foreach (self::$parsers as $parser) {
0 ignored issues
show
Bug introduced by
The expression self::$parsers of type array|object<Rafrsr\LibA...r\ValueParserInterface> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
178
                if ($parser instanceof ValueParserInterface) {
179
                    if (is_array($value) && strpos($type, '[]') !== false) {
180
                        foreach ($value as $key => &$arrayValue) {
181
                            $arrayValue = $parser->parseValue($arrayValue, str_replace('[]', null, $type), $property, $object);
182
                        }
183
                    } else {
184
                        $value = $parser->parseValue($value, $type, $property, $object);
185
                    }
186
                } else {
187
                    throw new \InvalidArgumentException(sprintf("%s is not a valid parser.", get_class($parser)));
188
                }
189
            }
190
        }
191
192
        return $value;
193
    }
194
195
    /**
196
     * @param $name
197
     *
198
     * @return string
199
     */
200
    static private function camelize($name)
201
    {
202
        return strtr(ucwords(strtr($name, ['_' => ' '])), [' ' => '']);
203
    }
204
205
    /**
206
     * @param \ReflectionProperty $property
207
     *
208
     * @return array
209
     */
210
    static private function getPropertyTypes(\ReflectionProperty $property)
211
    {
212
        $doc = $property->getDocComment();
213
        preg_match('/@var\s([\w\\\|\[\]]+)/', $doc, $matches);
214
        $types = [];
215
        if (isset($matches[1])) {
216
            $types = explode('|', $matches[1]);
217
        }
218
219
        return $types;
220
    }
221
}