Completed
Push — master ( 3e1989...d15db6 )
by Rafael
11:38
created

Array2Object::populate()   D

Complexity

Conditions 9
Paths 13

Size

Total Lines 38
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 4.909
c 0
b 0
f 0
cc 9
eloc 22
nc 13
nop 2
1
<?php
2
3
/*
4
 * This file is part of the rafrsr/lib-array2object package.
5
 *
6
 * (c) Rafael SR <https://github.com/rafrsr>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
namespace Rafrsr\LibArray2Object;
12
13
use Rafrsr\LibArray2Object\Parser\ValueParserInterface;
14
15
/**
16
 * Using the property names and the common property annotations
17
 * populate a object instance with the values of the array recursively.
18
 */
19
class Array2Object
20
{
21
    /**
22
     * @var Array2ObjectContext
23
     */
24
    private $context;
25
26
    /**
27
     * @var array
28
     */
29
    private static $classProperties = [];
0 ignored issues
show
Unused Code introduced by
The property $classProperties is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
30
31
    /**
32
     * @param Array2ObjectContext $context
33
     */
34
    public function __construct(Array2ObjectContext $context)
35
    {
36
        $this->context = $context;
37
    }
38
39
    /**
40
     * @return Array2ObjectContext
41
     */
42
    public function getContext()
43
    {
44
        return $this->context;
45
    }
46
47
    /**
48
     * @param Array2ObjectContext $context
49
     *
50
     * @return $this
51
     */
52
    public function setContext(Array2ObjectContext $context)
53
    {
54
        $this->context = $context;
55
56
        return $this;
57
    }
58
59
    /**
60
     * createObject.
61
     *
62
     * @param string $class class to create object or instance
63
     * @param array  $data  array of data
64
     *
65
     * @throws \InvalidArgumentException
66
     *
67
     * @return mixed
68
     */
69
    public function createObject($class, array $data)
70
    {
71
        if (is_string($class) && class_exists($class)) {
72
            $object = new $class();
73
        } else {
74
            throw new \InvalidArgumentException('The first argument should be a valid class, can use ::populate with objects');
75
        }
76
77
        $this->populate($object, $data);
78
79
        return $object;
80
    }
81
82
    /**
83
     * @param object $object object instance to populate
84
     * @param array  $data   array of data to apply
85
     *
86
     * @throws \InvalidArgumentException
87
     */
88
    public function populate($object, array $data)
89
    {
90
        if (!is_object($object)) {
91
            throw new \InvalidArgumentException('The first param should be a object.');
92
        }
93
94
        if ($object instanceof Array2ObjectInterface) {
95
            $object->__populate($data);
96
        } else {
97
            //this static cache is helpful when populate
98
            //many objects of the same type in a loop
99
            static $matcherCache = [];
100
            static $writerCache = [];
101
102
            $objectClass = get_class($object);
103
            $properties = Utils::getClassProperties($objectClass);
104
            foreach ($properties as $property) {
105
                //save in cache if the property is writable
106
                $writableCacheHash = $objectClass.$property->getName();
107
                if (!isset($writerCache[$writableCacheHash])) {
108
                    $writerCache[$writableCacheHash] = $this->context->getWriter()->isWritable($object, $property->getName());
109
                }
110
                foreach ($data as $key => $value) {
111
                    //save in cache if the property name match with key
112
                    $propHash = $objectClass.$property->getName().$key;
113
                    if (!isset($matcherCache[$propHash])) {
114
                        $matcherCache[$propHash] = $this->context->getMatcher()->match($property, $key);
115
                    }
116
117
                    if ($writerCache[$writableCacheHash] && $matcherCache[$propHash]) {
118
                        $types = Utils::getPropertyTypes($property);
119
                        $value = $this->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...
120
                        $this->context->getWriter()->setValue($object, $property->getName(), $value);
121
                    }
122
                }
123
            }
124
        }
125
    }
126
127
    /**
128
     * Parse a value using given types.
129
     *
130
     * @param mixed               $value
131
     * @param array               $types
132
     * @param \ReflectionProperty $property
133
     * @param object              $object
134
     *
135
     * @return array|bool|float|int|string
136
     */
137
    private function parseValue($value, $types, \ReflectionProperty $property, $object)
138
    {
139
        foreach ($types as $type) {
140
            foreach ($this->context->getParsers() as $parser) {
141
                if ($parser instanceof ValueParserInterface) {
142
                    if (is_array($value) && strpos($type, '[]') !== false) {
143
144
                        //support for nesting children
145
                        //https://github.com/rafrsr/lib-array2object/issues/1
146
                        if (count($value) === 1 && is_array(current($value))) {
147
                            if (array_key_exists(0, current($value))) {
148
                                $value = current($value);
149
                            }
150
                        }
151
152
                        $tmpArray = [];
153
                        foreach ($value as $key => $arrayValue) {
154
                            $parsedValue = $parser->toObjectValue($arrayValue, str_replace('[]', null, $type), $property, $object);
155
                            //the annotation [] is used alone to ignore array keys
156
                            if (in_array('[]', $types, true)) {
157
                                $tmpArray[] = $parsedValue;
158
                            } else {
159
                                $tmpArray[$key] = $parsedValue;
160
                            }
161
                        }
162
                        $value = $tmpArray;
163
                    } else {
164
                        $value = $parser->toObjectValue($value, str_replace('[]', null, $type), $property, $object);
165
                    }
166
                } else {
167
                    throw new \InvalidArgumentException(sprintf('%s is not a valid parser.', get_class($parser)));
168
                }
169
            }
170
        }
171
172
        return $value;
173
    }
174
}
175