Passed
Push — master ( f6fd28...c9771d )
by Gareth
02:20
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 44
    public static function convertValueToExpectedType($object, $value, string $propertyName)
20
    {
21 44
        if (!($value instanceof \stdClass || (is_array($value) && current($value) instanceof \stdClass))) {
22 39
            return $value;
23
        }
24
25 8
        $type = self::getSetterType($object, $propertyName);
26 8
        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 8
    public static function convertToType($value, string $type)
37
    {
38 8
        if (!class_exists($type)) {
39
            return $value;
40
        }
41
42 8
        if (is_array($value)) {
43 4
            return array_map(fn($v) => self::convertToType($v, $type), $value);
44
        }
45
46 8
        if (!($value instanceof \stdClass)) {
47
            return $value;
48
        }
49
50 8
        $object = new $type();
51 8
        foreach (get_object_vars($value) as $prop => $val) {
52 8
            self::setProperty($object, $prop, $val);
53
        }
54
55 8
        return $object;
56
    }
57
58
    /**
59
     * Get the setter method type for a property
60
     *
61
     * @param object $object The object to inspect
62
     * @param string $property The property name
63
     * @return string|null The expected type or null if no setter exists
64
     */
65 8
    private static function getSetterType($object, string $property): ?string
66
    {
67 8
        $method = 'set' . ucfirst($property);
68 8
        if (!method_exists($object, $method)) {
69
            return null;
70
        }
71
72 8
        $type = (new \ReflectionMethod($object, $method))
73 8
            ->getParameters()[0]?->getType();
74
75 8
        if ($type instanceof \ReflectionUnionType) {
76 4
            foreach ($type->getTypes() as $t) {
77 4
                if (!$t->isBuiltin() && $t->getName() !== 'array') {
78 4
                    return $t->getName();
79
                }
80
            }
81
        }
82
83 4
        return ($type instanceof \ReflectionNamedType && !$type->isBuiltin())
84 4
            ? $type->getName()
85 4
            : null;
86
    }
87
88
    /**
89
     * Set property on target object using setter method or direct assignment
90
     *
91
     * @param object $instance Target object
92
     * @param string $property Property name
93
     * @param mixed $value Property value
94
     */
95 8
    private static function setProperty(object $instance, string $property, $value): void
96
    {
97 8
        $setter = 'set' . ucfirst($property);
98 8
        if (!method_exists($instance, $setter)) {
99 5
            if (property_exists($instance, $property)) {
100 5
                $instance->$property = $value;
101
            }
102 5
            return;
103
        }
104
105 8
        $type = (new \ReflectionMethod($instance, $setter))->getParameters()[0]?->getType();
106 8
        $value = self::convertIfStdClass($value, $type);
107 8
        $instance->$setter($value);
108
    }
109
110 8
    private static function convertIfStdClass($value, ?\ReflectionType $type)
111
    {
112 8
        if (!($value instanceof \stdClass) || !$type) {
113 8
            return $value;
114
        }
115
116 2
        if ($type instanceof \ReflectionUnionType) {
117 1
            foreach ($type->getTypes() as $t) {
118 1
                $name = $t->getName();
119 1
                if (!$t->isBuiltin() && class_exists($name)) {
120 1
                    return self::convertToType($value, $name);
121
                }
122
                if ($name === 'array') {
123
                    $props = get_object_vars($value);
124
                    $arr = count($props) === 1 ? current($props) : (array)$value;
125
                    return is_array($arr) ? $arr : [$arr];
126
                }
127
            }
128
        }
129
130 2
        if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
131 2
            return self::convertToType($value, $type->getName());
132
        }
133
134
        return $value;
135
    }
136
}
137