Completed
Push — master ( d54351...6ce5d4 )
by Rafael
03:34
created

Array2Object::isSameProperty()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 13
rs 8.8571
cc 6
eloc 8
nc 2
nop 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 Rafrsr\LibArray2Object\PropertyMatcher\CamelizeMatcher;
20
use Rafrsr\LibArray2Object\PropertyMatcher\PropertyMatcherInterface;
21
use Symfony\Component\PropertyAccess\PropertyAccessor;
22
23
/**
24
 * Using the property names and the common property annotations
25
 * populate a object instance with the values of the array recursively
26
 */
27
class Array2Object
28
{
29
    /**
30
     * @var array|ValueParserInterface
31
     */
32
    private static $parsers;
33
34
    /**
35
     * @var PropertyMatcherInterface
36
     */
37
    private static $propertyMatcher;
38
39
    /**
40
     * registerParser
41
     *
42
     * @param ValueParserInterface|array $parsers
43
     */
44
    static public function registerParser($parsers)
45
    {
46
        if (is_array($parsers)) {
47
            foreach ($parsers as $parser) {
48
                self::$parsers[] = $parser;
49
            }
50
        } else {
51
            self::$parsers[] = $parsers;
52
        }
53
    }
54
55
    /**
56
     * @param PropertyMatcherInterface $propertyMatcher
57
     *
58
     * @return $this
59
     */
60
    public static function setPropertyMatcher(PropertyMatcherInterface $propertyMatcher)
61
    {
62
        self::$propertyMatcher = $propertyMatcher;
63
    }
64
65
    /**
66
     * createObject
67
     *
68
     * @param string $class class to create object or instance
69
     * @param array  $data  array of data
70
     *
71
     * @return mixed
72
     *
73
     * @throws \InvalidArgumentException
74
     */
75
    static public function createObject($class, array $data)
76
    {
77
        if (is_string($class) && class_exists($class)) {
78
            $object = new $class;
79
        } else {
80
            throw new \InvalidArgumentException('The first argument should be a valid class, can use ::populate with objects');
81
        }
82
83
        self::populate($object, $data);
84
85
        return $object;
86
    }
87
88
    /**
89
     * @param object $object object instance to populate
90
     * @param array  $data   array of data to apply
91
     *
92
     * @throws \InvalidArgumentException
93
     */
94
    static public function populate($object, array $data)
95
    {
96
        self::setup();//register parsers
97
98
        if (!is_object($object)) {
99
            throw new \InvalidArgumentException('The first param should be a object.');
100
        }
101
102
        $propertyAccessor = new PropertyAccessor();
103
104
        $reflClass = new \ReflectionClass($object);
105
106
        foreach (self::getClassProperties($reflClass) as $property) {
107
            foreach ($data as $key => $value) {
108
                if ($propertyAccessor->isWritable($object, $property->getName()) && self::$propertyMatcher->match($property, $key)) {
109
                    $types = self::getPropertyTypes($property);
110
                    $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...
111
                    $propertyAccessor->setValue($object, $property->getName(), $value);
112
                }
113
            }
114
        }
115
    }
116
117
    /**
118
     * setup
119
     */
120
    static private function setup()
121
    {
122
        if (!self::$propertyMatcher) {
123
            self::$propertyMatcher = new CamelizeMatcher();
124
        }
125
126
        self::registerParser(
127
            [
128
                new StringParser(),
129
                new BooleanParser(),
130
                new IntegerParser(),
131
                new FloatParser(),
132
                new DateTimeParser(),
133
                new ObjectParser()
134
            ]
135
        );
136
    }
137
138
    /**
139
     * Get array of class properties including parents private properties
140
     *
141
     * @param \ReflectionClass $refClass
142
     *
143
     * @return array|\ReflectionProperty[]
144
     */
145
    static private function getClassProperties(\ReflectionClass $refClass)
146
    {
147
        $props = $refClass->getProperties();
148
        $props_arr = [];
149
        foreach ($props as $prop) {
150
            $f = $prop->getName();
151
152
            $props_arr[$f] = $prop;
153
        }
154
        if ($parentClass = $refClass->getParentClass()) {
155
            $parent_props_arr = self::getClassProperties($parentClass);//RECURSION
156
            if (count($parent_props_arr) > 0) {
157
                $props_arr = array_merge($parent_props_arr, $props_arr);
158
            }
159
        }
160
161
        return $props_arr;
162
    }
163
164
    /**
165
     * Parse a value using given types
166
     *
167
     * @param mixed               $value
168
     * @param array               $types
169
     * @param \ReflectionProperty $property
170
     * @param object              $object
171
     *
172
     * @return array|bool|float|int|string
173
     */
174
    static private function parseValue($value, $types, \ReflectionProperty $property, $object)
175
    {
176
        foreach ($types as $type) {
177
178
            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...
179
                if ($parser instanceof ValueParserInterface) {
180
                    if (is_array($value) && strpos($type, '[]') !== false) {
181
                        foreach ($value as $key => &$arrayValue) {
182
                            $arrayValue = $parser->parseValue($arrayValue, str_replace('[]', null, $type), $property, $object);
183
                        }
184
                    } else {
185
                        $value = $parser->parseValue($value, $type, $property, $object);
186
                    }
187
                } else {
188
                    throw new \InvalidArgumentException(sprintf("%s is not a valid parser.", get_class($parser)));
189
                }
190
            }
191
        }
192
193
        return $value;
194
    }
195
196
    /**
197
     * @param \ReflectionProperty $property
198
     *
199
     * @return array
200
     */
201
    static private function getPropertyTypes(\ReflectionProperty $property)
202
    {
203
        $doc = $property->getDocComment();
204
        preg_match('/@var\s([\w\\\|\[\]]+)/', $doc, $matches);
205
        $types = [];
206
        if (isset($matches[1])) {
207
            $types = explode('|', $matches[1]);
208
        }
209
210
        return $types;
211
    }
212
}