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

tylernathanreed / jira-client-php / 13977866104

20 Mar 2025 07:34PM UTC coverage: 75.543% (+3.2%) from 72.327%
13977866104

push

github

tylernathanreed
~ Fixed and validated nested DTOs

1 of 1 new or added line in 1 file covered. (100.0%)

71 existing lines in 35 files now uncovered.

5186 of 6865 relevant lines covered (75.54%)

9.51 hits per line

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

88.06
/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

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

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

28
            return $values;
214✔
29
        }
30

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

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

38
    /**
39
     * @phpstan-template T of Dto
40
     * @param class-string<T> $class
41
     * @param array<string,mixed> $data
42
     * @return T
43
     */
44
    public function from(string $class, array $data): Dto
397✔
45
    {
46
        $reflector = new ReflectionClass($class);
397✔
47

48
        $parameters = $reflector->getConstructor()?->getParameters() ?? [];
397✔
49

50
        $args = [];
397✔
51

52
        foreach ($parameters as $parameter) {
397✔
53
            $name = $parameter->getName();
397✔
54
            $key = str_starts_with($name, '_')
397✔
55
                ? substr($name, 1)
49✔
56
                : $name;
397✔
57

58
            $property = $reflector->getProperty($name);
397✔
59

60
            $value = array_key_exists($key, $data)
397✔
61
                ? $data[$key]
395✔
62
                : (
397✔
63
                    $parameter->isDefaultValueAvailable()
251✔
64
                    ? $parameter->getDefaultValue()
251✔
65
                    : null
251✔
66
                );
397✔
67

68
            $type = $parameter->getType();
397✔
69

70
            if (is_null($value)) {
397✔
71
                $args[] = $value;
251✔
72
            } elseif (is_null($type)) {
395✔
73
                $args[] = $value;
×
74
            } elseif (! $type instanceof ReflectionNamedType) {
395✔
75
                $args[] = $value;
×
76
            } elseif ($type->getName() === 'array' && is_array($value)) {
395✔
77
                $args[] = $this->fromArray($class, $property, $value);
248✔
78
            } elseif ($type->getName() === DateTimeImmutable::class && is_string($value)) {
371✔
79
                $args[] = new DateTimeImmutable($value);
31✔
80
            } elseif ($type->getName() === DateTimeImmutable::class && is_int($value)) {
371✔
81
                $args[] = (new DateTimeImmutable)->setTimestamp($value);
4✔
82
            } elseif (! $type->isBuiltin() && is_subclass_of($type->getName(), Dto::class) && is_array($value)) {
371✔
83
                // @phpstan-ignore argument.type
84
                $args[] = $this->from($type->getName(), $value);
140✔
85
            } else {
86
                $args[] = $value;
369✔
87
            }
88
        }
89

90
        return $reflector->newInstanceArgs($args);
397✔
91
    }
92

93
    /**
94
     * @param class-string<Dto> $class
95
     * @param array<mixed,mixed> $array
96
     * @return array<mixed,mixed>
97
     */
98
    protected function fromArray(string $class, ReflectionProperty $property, array $array): array
248✔
99
    {
100
        $doc = $property->getDocComment();
248✔
101

102
        if (! $doc) {
248✔
103
            return $array;
×
104
        }
105

106
        $var = preg_match('/@var ?\?([^ ]+)(?:\n| \*)/', $doc, $matches)
248✔
107
            ? $matches[1]
198✔
108
            : null;
103✔
109

110
        if (! $var) {
248✔
111
            return $array;
103✔
112
        }
113

114
        if (str_starts_with($var, 'list')) {
198✔
115
            $type = substr($var, strlen('list<'), -strlen('>'));
198✔
116
        } else {
UNCOV
117
            if (! preg_match('/^array<(?<key>[^>]+), ?(?<type>.+)>$/', $var, $matches)) {
×
UNCOV
118
                return $array;
×
119
            }
120

UNCOV
121
            $type = $matches['type'];
×
122
        }
123

124
        if ($type === 'mixed' || str_starts_with($type, 'list')) {
198✔
UNCOV
125
            return $array;
×
126
        }
127

128
        if (in_array($type, ['int', 'float', 'string', 'boolean'])) {
198✔
129
            return array_map(function ($v) use ($type) {
54✔
130
                settype($v, $type);
48✔
131
                return $v;
48✔
132
            }, $array);
54✔
133
        }
134

135
        if (class_exists($subclass = 'Jira\\Client\\Schema\\' . $type) && is_subclass_of($subclass, Dto::class)) {
179✔
136
            // @phpstan-ignore argument.type
137
            return $this->deserialize($array, $subclass, array: true);
179✔
138
        }
139

140
        throw new DeserializationException("Unknown class [{$subclass}] when deserializing [{$class}].");
×
141
    }
142
}
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