Passed
Push — master ( 56f7f0...182f6a )
by Gareth
02:15
created

TypeConverter::convertIfStdClass()   C

Complexity

Conditions 12
Paths 10

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 17.3317

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 14
c 1
b 0
f 0
nc 10
nop 2
dl 0
loc 25
ccs 10
cts 15
cp 0.6667
crap 17.3317
rs 6.9666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace garethp\ews\API;
4
5
/**
6
 * Type converter for handling SOAP stdClass objects
7
 * Converts stdClass objects to proper EWS type objects
8
 */
9
class TypeConverter
10
{
11
    /**
12
     * Convert stdClass objects to proper EWS type objects
13
     *
14
     * @param object $object The object containing the property
15
     * @param mixed $value The value to convert
16
     * @param string $propertyName The property name
17
     * @return mixed Converted value
18
     */
19 46
    public static function convertValueToExpectedType($object, $value, string $propertyName)
20
    {
21 46
        if (!($value instanceof \stdClass || (is_array($value) && current($value) instanceof \stdClass))) {
22 41
            return $value;
23
        }
24
25 9
        $type = self::getSetterType($object, $propertyName);
26 9
        return $type ? self::convertToType($value, $type) : $value;
27
    }
28
29
    /**
30
     * Convert stdClass objects to proper EWS types using reflection
31
     *
32
     * @param mixed $value The value to convert
33
     * @param string $targetType The expected EWS type class name
34
     * @return mixed Converted value or original if no conversion needed
35
     */
36 9
    public static function convertToType($value, string $type)
37
    {
38 9
        if (!class_exists($type)) {
39
            return $value;
40
        }
41
42 9
        if (is_array($value)) {
43 5
            return array_map(fn($v) => self::convertToType($v, $type), $value);
44
        }
45
46 9
        if (!($value instanceof \stdClass)) {
47 1
            return $value;
48
        }
49
50
        // We'll sometimes get objects where they only hold a single key that, rather than matching the type that it
51
        // goes into it'll be containing the items that the type expects.
52 9
        $valueProperties = get_object_vars($value);
53 9
        if (count($valueProperties) === 1 && !property_exists($type, lcfirst(key($valueProperties)))) {
54 1
            return self::convertToType($value->{key($valueProperties)}, $type);
55
        }
56
57 9
        $object = new $type();
58 9
        foreach ($valueProperties as $prop => $val) {
59 8
            self::setProperty($object, $prop, $val);
60
        }
61
62 9
        return $object;
63
    }
64
65
    /**
66
     * Get the setter method type for a property
67
     *
68
     * @param object $object The object to inspect
69
     * @param string $property The property name
70
     * @return string|null The expected type or null if no setter exists
71
     */
72 9
    private static function getSetterType($object, string $property): ?string
73
    {
74 9
        $method = 'set' . ucfirst($property);
75 9
        if (!method_exists($object, $method)) {
76
            return null;
77
        }
78
79 9
        $type = (new \ReflectionMethod($object, $method))
80 9
            ->getParameters()[0]?->getType();
81
82 9
        if ($type instanceof \ReflectionUnionType) {
83 5
            foreach ($type->getTypes() as $t) {
84 5
                if (!$t->isBuiltin() && $t->getName() !== 'array') {
85 5
                    return $t->getName();
86
                }
87
            }
88
        }
89
90 4
        return ($type instanceof \ReflectionNamedType && !$type->isBuiltin())
91 4
            ? $type->getName()
92 4
            : null;
93
    }
94
95
    /**
96
     * Set property on target object using setter method or direct assignment
97
     *
98
     * @param object $instance Target object
99
     * @param string $property Property name
100
     * @param mixed $value Property value
101
     */
102 8
    private static function setProperty(object $instance, string $property, $value): void
103
    {
104 8
        $setter = 'set' . ucfirst($property);
105 8
        if (!method_exists($instance, $setter)) {
106 5
            if (property_exists($instance, $property)) {
107 5
                $instance->$property = $value;
108
            }
109 5
            return;
110
        }
111
112 8
        $type = (new \ReflectionMethod($instance, $setter))->getParameters()[0]?->getType();
113 8
        $value = self::convertIfStdClass($value, $type);
114 8
        $instance->$setter($value);
115
    }
116
117 8
    private static function convertIfStdClass($value, ?\ReflectionType $type)
118
    {
119 8
        if (!($value instanceof \stdClass) || !$type) {
120 8
            return $value;
121
        }
122
123 2
        if ($type instanceof \ReflectionUnionType) {
124 1
            foreach ($type->getTypes() as $t) {
125 1
                $name = $t->getName();
126 1
                if (!$t->isBuiltin() && class_exists($name)) {
127 1
                    return self::convertToType($value, $name);
128
                }
129
                if ($name === 'array') {
130
                    $props = get_object_vars($value);
131
                    $arr = count($props) === 1 ? current($props) : (array)$value;
132
                    return is_array($arr) ? $arr : [$arr];
133
                }
134
            }
135
        }
136
137 2
        if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
138 2
            return self::convertToType($value, $type->getName());
139
        }
140
141
        return $value;
142
    }
143
}
144