Completed
Push — v0.7 ( 5c4e91...21297d )
by Nate
02:10
created

ArrayTypeAdapter   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 171
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 33
eloc 75
dl 0
loc 171
ccs 67
cts 67
cp 1
rs 9.76
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
C write() 0 44 13
D read() 0 62 19
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\Gson\TypeAdapter;
10
11
use LogicException;
12
use stdClass;
13
use Tebru\Gson\Exception\JsonSyntaxException;
14
use Tebru\Gson\Internal\TypeAdapterProvider;
15
use Tebru\Gson\ReaderContext;
16
use Tebru\Gson\TypeAdapter;
17
use Tebru\Gson\WriterContext;
18
use Tebru\PhpType\TypeToken;
19
20
/**
21
 * Class ArrayTypeAdapter
22
 *
23
 * @author Nate Brunette <[email protected]>
24
 */
25
class ArrayTypeAdapter extends TypeAdapter
26
{
27
    /**
28
     * @var TypeAdapterProvider
29
     */
30
    private $typeAdapterProvider;
31
32
    /**
33
     * @var TypeToken
34
     */
35
    private $keyType;
36
37
    /**
38
     * @var TypeAdapter
39
     */
40
    private $valueTypeAdapter;
41
42
    /**
43
     * @var int
44
     */
45
    private $numberOfGenerics;
46
47
    /**
48
     * A TypeAdapter cache keyed by raw type
49
     *
50
     * @var TypeAdapter[]
51
     */
52
    private $adapters = [];
53
54
    /**
55
     * Constructor
56 24
     *
57
     * @param TypeAdapterProvider $typeAdapterProvider
58
     * @param TypeToken $keyType
59
     * @param TypeAdapter $valueTypeAdapter
60
     * @param int $numberOfGenerics
61
     */
62 24
    public function __construct(
63 24
        TypeAdapterProvider $typeAdapterProvider,
64 24
        TypeToken $keyType,
65 24
        TypeAdapter $valueTypeAdapter,
66 24
        int $numberOfGenerics
67
    ) {
68
        $this->typeAdapterProvider = $typeAdapterProvider;
69
        $this->keyType = $keyType;
70
        $this->valueTypeAdapter = $valueTypeAdapter;
71
        $this->numberOfGenerics = $numberOfGenerics;
72
    }
73
74
    /**
75
     * Read the next value, convert it to its type and return it
76 14
     *
77
     * @param array|null $value
78 14
     * @param ReaderContext $context
79 14
     * @return array|null
80 1
     */
81 1
    public function read($value, ReaderContext $context): ?array
82
    {
83
        if ($value === null) {
84 13
            return null;
85
        }
86 13
87 1
        $result = [];
88
89
        if ($this->numberOfGenerics > 2) {
90
            throw new LogicException('Array may not have more than 2 generic types');
91 12
        }
92 7
93
        $arrayIsObject = $this->numberOfGenerics === 2 || is_string(key($value));
94 7
        $enableScalarAdapters = $context->enableScalarAdapters();
95 7
96
        foreach ($value as $key => $item) {
97 7
            if (!$enableScalarAdapters && is_scalar($item)) {
98
                $result[$arrayIsObject ? (string)$key : (int)$key] = $item;
99 7
                continue;
100
            }
101
102
            $itemValue = null;
103 3
            switch ($this->numberOfGenerics) {
104 1
                case 0:
105 3
                    if (!$arrayIsObject) {
106
                        $itemValue = $this->valueTypeAdapter->read($item, $context);
107 3
                        break;
108 3
                    }
109 3
110
                    if (is_array($item)) {
111 5
                        $itemValue = $this->read($item, $context);
112 1
                        break;
113 1
                    }
114
115 4
                    $type = TypeToken::createFromVariable($item);
116 4
                    $adapter = $this->adapters[$type->rawType] ?? $this->adapters[$type->rawType] = $this->typeAdapterProvider->getAdapter($type);
117 1
                    $itemValue = $adapter->read($item, $context);
118
                    break;
119
                case 1:
120 3
                    $itemValue = $this->valueTypeAdapter->read($item, $context);
121 2
                    break;
122 1
                case 2:
123
                    if (!$arrayIsObject) {
124
                        throw new LogicException('An array may only specify a generic type for the value');
125 1
                    }
126
127
                    if ($this->keyType->phpType !== TypeToken::STRING && $this->keyType->phpType !== TypeToken::INTEGER) {
128 2
                        throw new LogicException('Array keys must be strings or integers');
129
                    }
130 2
131
                    if (($this->keyType->phpType === TypeToken::INTEGER) && !ctype_digit($key)) {
132
                        throw new JsonSyntaxException('Expected integer, but found string for key');
133
                    }
134 5
135
                    $itemValue = $this->valueTypeAdapter->read($item, $context);
136 5
                    break;
137 5
            }
138 4
139
            $result[$arrayIsObject ? (string)$key : (int)$key] = $itemValue;
140 4
        }
141 4
142
        return $result;
143 4
    }
144 3
145 3
    /**
146
     * Write the value to the writer for the type
147 3
     *
148
     * @param array|stdClass|null $value
149 1
     * @param WriterContext $context
150
     * @return array|null
151
     */
152
    public function write($value, WriterContext $context): ?array
153 3
    {
154
        if ($value === null) {
155 3
            return null;
156
        }
157 1
158
        if ($this->numberOfGenerics > 2) {
159
            throw new LogicException('Array may not have more than 2 generic types');
160 8
        }
161
162
        $arrayIsObject = $this->numberOfGenerics === 2 || is_string(key($value));
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type stdClass; however, parameter $array of key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
        $arrayIsObject = $this->numberOfGenerics === 2 || is_string(key(/** @scrutinizer ignore-type */ $value));
Loading history...
163
        $enableScalarAdapters = $context->enableScalarAdapters();
164
        $result = [];
165
166
        foreach ($value as $key => $item) {
167
            if (!$enableScalarAdapters && is_scalar($item)) {
168
                $result[$arrayIsObject ? (string)$key : (int)$key] = $item;
169
                continue;
170
            }
171 10
172
            $itemValue = null;
173 10
            switch ($this->numberOfGenerics) {
174 1
                // no generics specified
175
                case 0:
176 1
                    if (is_array($item)) {
177
                        $itemValue = $this->write($item, $context);
178
                        break;
179 9
                    }
180 1
181
                    $type = TypeToken::createFromVariable($item);
182
                    $adapter = $this->adapters[$type->rawType] ?? $this->adapters[$type->rawType] = $this->typeAdapterProvider->getAdapter($type);
183 8
                    $itemValue = $adapter->write($item, $context);
184
                    break;
185 8
                // generic for value specified
186 4
                case 1:
187
                case 2:
188 4
                    $itemValue = $this->valueTypeAdapter->write($item, $context);
189
                    break;
190
            }
191 8
192 8
            $result[$arrayIsObject ? (string)$key : (int)$key] = $itemValue;
193
        }
194 8
195 5
        return $result;
196 2
    }
197
}
198