Passed
Push — master ( f6fd28...c9771d )
by Gareth
02:20
created

TypeConverter::setProperty()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 3
nop 3
dl 0
loc 13
ccs 9
cts 9
cp 1
crap 3
rs 10
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