• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tylernathanreed / jira-client-php / 14063772259

25 Mar 2025 03:34PM UTC coverage: 79.552% (+0.9%) from 78.694%
14063772259

push

github

tylernathanreed
~ Fixed all example/type mismatch tests

5466 of 6871 relevant lines covered (79.55%)

10.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.05
/src/Deserializer.php
1
<?php
2

3
namespace Jira\Client;
4

5
use DateTimeImmutable;
6
use Jira\Client\Exceptions\DeserializationException;
7
use ReflectionClass;
8
use ReflectionNamedType;
9
use ReflectionProperty;
10
use TypeError;
11

12
class Deserializer
13
{
14
    /**
15
     * @phpstan-template TDto of Dto
16
     * @param ($array is true ? list<array<string,mixed>> : array<string,mixed>) $data
17
     * @param class-string<TDto> $class
18
     * @return ($array is true ? (TDto is PolymorphicDto ? list<Dto> : list<TDto>) : (TDto is PolymorphicDto ? Dto : TDto))
19
     */
20
    public function deserialize(array $data, string $class, bool $array = false)
413✔
21
    {
22
        if ($array) {
413✔
23
            $values = [];
235✔
24

25
            foreach ($data as $value) {
235✔
26
                $values[] = self::deserialize($value, $class);
235✔
27
            }
28

29
            return $values;
235✔
30
        }
31

32
        if (is_subclass_of($class, PolymorphicDto::class)) {
413✔
33
            $class = $class::discriminateFromData($data);
1✔
34
        }
35

36
        return $this->from($class, $data);
413✔
37
    }
38

39
    /**
40
     * @phpstan-template T of Dto
41
     * @param class-string<T> $class
42
     * @param array<string,mixed> $data
43
     * @return T
44
     */
45
    public function from(string $class, array $data): Dto
413✔
46
    {
47
        try {
48
            return $this->resolve($class, $data);
413✔
49
        } catch (TypeError $e) {
×
50
            throw new DeserializationException(sprintf(
×
51
                'Failed to deserialize [%s]: %s (Data: %s)',
×
52
                $class,
×
53
                substr($e->getMessage(), strlen($class . '::__construct(): ')),
×
54
                json_encode($data),
×
55
            ), previous: $e);
×
56
        }
57
    }
58

59
    /**
60
     * @phpstan-template T of Dto
61
     * @param class-string<T> $class
62
     * @param array<string,mixed> $data
63
     * @return T
64
     */
65
    protected function resolve(string $class, array $data): Dto
413✔
66
    {
67
        $reflector = new ReflectionClass($class);
413✔
68

69
        $parameters = $reflector->getConstructor()?->getParameters() ?? [];
413✔
70

71
        $args = [];
413✔
72

73
        foreach ($parameters as $parameter) {
413✔
74
            $name = $parameter->getName();
413✔
75
            $key = str_starts_with($name, '_')
413✔
76
                ? substr($name, 1)
50✔
77
                : $name;
413✔
78

79
            $property = $reflector->getProperty($name);
413✔
80

81
            $value = array_key_exists($key, $data)
413✔
82
                ? $data[$key]
411✔
83
                : (
413✔
84
                    $parameter->isDefaultValueAvailable()
269✔
85
                    ? $parameter->getDefaultValue()
269✔
86
                    : null
269✔
87
                );
413✔
88

89
            $type = $parameter->getType();
413✔
90

91
            if (is_null($value)) {
413✔
92
                $args[] = $value;
269✔
93
            } elseif (is_null($type)) {
411✔
94
                $args[] = $value;
×
95
            } elseif (! $type instanceof ReflectionNamedType) {
411✔
96
                $args[] = $value;
×
97
            } elseif (is_array($value) && empty($value) && $type->allowsNull()) {
411✔
98
                $args[] = null;
50✔
99
            } elseif ($type->getName() === 'array' && is_array($value)) {
411✔
100
                $args[] = $this->fromArray($class, $property, $value);
256✔
101
            } elseif ($type->getName() === DateTimeImmutable::class && is_string($value)) {
401✔
102
                $args[] = new DateTimeImmutable($value);
33✔
103
            } elseif ($type->getName() === DateTimeImmutable::class && is_int($value)) {
400✔
104
                $args[] = (new DateTimeImmutable)->setTimestamp($value);
4✔
105
            } elseif (! $type->isBuiltin() && is_subclass_of($type->getName(), Dto::class) && is_array($value)) {
400✔
106
                // @phpstan-ignore argument.type
107
                $args[] = $this->from($type->getName(), $value);
153✔
108
            } else {
109
                $args[] = $value;
400✔
110
            }
111
        }
112

113
        return $reflector->newInstanceArgs($args);
413✔
114
    }
115

116
    /**
117
     * @param class-string<Dto> $class
118
     * @param array<mixed,mixed> $array
119
     * @return array<mixed,mixed>
120
     */
121
    protected function fromArray(string $class, ReflectionProperty $property, array $array): array
256✔
122
    {
123
        $doc = $property->getDocComment();
256✔
124

125
        if (! $doc) {
256✔
126
            return $array;
×
127
        }
128

129
        $var = preg_match('/@var \??([^ ]+)(?:\n| \*)/', $doc, $matches)
256✔
130
            ? $matches[1]
256✔
131
            : null;
×
132

133
        if (! $var) {
256✔
134
            return $array;
×
135
        }
136

137
        if (str_starts_with($var, 'list')) {
256✔
138
            $type = substr($var, strlen('list<'), -strlen('>'));
232✔
139
        } else {
140
            if (! preg_match('/^array<(?<key>[^>]+), ?(?<type>.+)>$/', $var, $matches)) {
59✔
141
                return $array;
×
142
            }
143

144
            $type = $matches['type'];
59✔
145
        }
146

147
        if ($type === 'mixed' || str_starts_with($type, 'list')) {
256✔
148
            return $array;
24✔
149
        }
150

151
        if (in_array($type, ['int', 'float', 'string', 'boolean', 'bool'])) {
254✔
152
            return array_map(function ($v) use ($type) {
119✔
153
                settype($v, $type);
116✔
154
                return $v;
116✔
155
            }, $array);
119✔
156
        }
157

158
        if (class_exists($subclass = 'Jira\\Client\\Schema\\' . $type) && is_subclass_of($subclass, Dto::class)) {
199✔
159
            // @phpstan-ignore argument.type
160
            return $this->deserialize($array, $subclass, array: true);
199✔
161
        }
162

163
        throw new DeserializationException("Unknown class [{$subclass}] when deserializing [{$class}].");
×
164
    }
165
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc