Passed
Pull Request — master (#275)
by
unknown
09:01
created

TypeConverter::convertToType()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 10
c 2
b 0
f 0
nc 5
nop 2
dl 0
loc 20
rs 9.6111
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
    public static function convertValueToExpectedType($object, $value, string $propertyName)
20
    {
21
        if (!($value instanceof \stdClass || (is_array($value) && current($value) instanceof \stdClass))) {
22
            return $value;
23
        }
24
25
        $type = self::getSetterType($object, $propertyName);
26
        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
    public static function convertToType($value, string $type)
37
    {
38
        if (!class_exists($type)) {
39
            return $value;
40
        }
41
42
        if (is_array($value)) {
43
            return array_map(fn($v) => self::convertToType($v, $type), $value);
44
        }
45
46
        if (!($value instanceof \stdClass)) {
47
            return $value;
48
        }
49
50
        $object = new $type();
51
        foreach (get_object_vars($value) as $prop => $val) {
52
            self::setProperty($object, $prop, $val);
53
        }
54
55
        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
    private static function getSetterType($object, string $property): ?string
66
    {
67
        $method = 'set' . ucfirst($property);
68
        if (!method_exists($object, $method)) {
69
            return null;
70
        }
71
72
        $type = (new \ReflectionMethod($object, $method))
73
            ->getParameters()[0]?->getType();
74
75
        if ($type instanceof \ReflectionUnionType) {
76
            foreach ($type->getTypes() as $t) {
77
                if (!$t->isBuiltin() && $t->getName() !== 'array') {
78
                    return $t->getName();
79
                }
80
            }
81
        }
82
83
        return ($type instanceof \ReflectionNamedType && !$type->isBuiltin())
84
            ? $type->getName()
85
            : 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
    private static function setProperty(object $instance, string $property, $value): void
96
    {
97
        $setter = 'set' . ucfirst($property);
98
        if (!method_exists($instance, $setter)) {
99
            if (property_exists($instance, $property)) {
100
                $instance->$property = $value;
101
            }
102
            return;
103
        }
104
105
        $type = (new \ReflectionMethod($instance, $setter))->getParameters()[0]?->getType();
106
        $value = self::convertIfStdClass($value, $type);
107
        $instance->$setter($value);
108
    }
109
110
    private static function convertIfStdClass($value, ?\ReflectionType $type)
111
    {
112
        if (!($value instanceof \stdClass) || !$type) {
113
            return $value;
114
        }
115
116
        if ($type instanceof \ReflectionUnionType) {
117
            foreach ($type->getTypes() as $t) {
118
                $name = $t->getName();
119
                if (!$t->isBuiltin() && class_exists($name)) {
120
                    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
        if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
131
            return self::convertToType($value, $type->getName());
132
        }
133
134
        return $value;
135
    }
136
}
137