Completed
Push — performance-improvements-v2 ( 8879cc...332ef3 )
by Nate
02:35
created

ArrayTypeAdapter   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 92
dl 0
loc 215
ccs 88
cts 88
cp 1
rs 9.92
c 0
b 0
f 0
wmc 31

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
C read() 0 85 17
A isArrayObject() 0 7 2
B write() 0 54 11
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\Internal\TypeAdapter;
10
11
use LogicException;
12
use Tebru\Gson\Exception\JsonSyntaxException;
13
use Tebru\Gson\JsonWritable;
14
use Tebru\Gson\Internal\TypeAdapterProvider;
15
use Tebru\Gson\JsonReadable;
16
use Tebru\Gson\JsonToken;
17
use Tebru\Gson\TypeAdapter;
18
use Tebru\PhpType\TypeToken;
19
20
/**
21
 * Class ArrayTypeAdapter
22
 *
23
 * @author Nate Brunette <[email protected]>
24
 */
25
final class ArrayTypeAdapter extends TypeAdapter
26
{
27
    /**
28
     * @var TypeAdapterProvider
29
     */
30
    private $typeAdapterProvider;
31
32
    /**
33
     * @var null|TypeToken
34
     */
35
    private $keyType;
36
37
    /**
38
     * @var null|TypeAdapter
39
     */
40
    private $valueTypeAdapter;
41
42
    /**
43
     * @var int
44
     */
45
    private $numberOfGenerics;
46
47
    /**
48
     * Constructor
49
     *
50
     * @param TypeAdapterProvider $typeAdapterProvider
51
     * @param null|TypeToken $keyType
52
     * @param null|TypeAdapter $valueTypeAdapter
53
     * @param int $numberOfGenerics
54
     */
55 23
    public function __construct(
56
        TypeAdapterProvider $typeAdapterProvider,
57
        ?TypeToken $keyType,
58
        ?TypeAdapter $valueTypeAdapter,
59
        int $numberOfGenerics
60
    ) {
61 23
        $this->typeAdapterProvider = $typeAdapterProvider;
62 23
        $this->keyType = $keyType;
63 23
        $this->valueTypeAdapter = $valueTypeAdapter;
64 23
        $this->numberOfGenerics = $numberOfGenerics;
65 23
    }
66
67
    /**
68
     * Read the next value, convert it to its type and return it
69
     *
70
     * @param JsonReadable $reader
71
     * @return array|null
72
     * @throws \LogicException
73
     * @throws \Tebru\Gson\Exception\JsonSyntaxException If trying to read from non object/array
74
     */
75 14
    public function read(JsonReadable $reader): ?array
76
    {
77 14
        $token = $reader->peek();
78 14
        if ($token === JsonToken::NULL) {
79 1
            $reader->nextNull();
80 1
            return null;
81
        }
82
83 13
        $array = [];
84
85 13
        if ($this->numberOfGenerics > 2) {
86 1
            throw new LogicException(\sprintf('Array may not have more than 2 generic types at "%s"', $reader->getPath()));
87
        }
88
89
        switch ($token) {
90 12
            case JsonToken::BEGIN_OBJECT:
91 7
                $reader->beginObject();
92
93 7
                while ($reader->hasNext()) {
94 7
                    $name = $reader->nextName();
95
96 7
                    switch ($this->numberOfGenerics) {
97
                        // no generics specified
98 7
                        case 0:
99
                            // By now we know that we're deserializing a json object to an array.
100
                            // If there is a nested object, continue deserializing to an array,
101
                            // otherwise guess the type using the wildcard
102 3
                            $type = $reader->peek() === JsonToken::BEGIN_OBJECT
103 1
                                ? TypeToken::create(TypeToken::HASH)
104 3
                                : TypeToken::create(TypeToken::WILDCARD);
105
106 3
                            $adapter = $this->typeAdapterProvider->getAdapter($type);
107 3
                            $array[$name] = $adapter->read($reader);
108 3
                            break;
109
                        // generic for value specified
110 5
                        case 1:
111 1
                            $array[$name] = $this->valueTypeAdapter->read($reader);
0 ignored issues
show
Bug introduced by
The method read() does not exist on null. ( Ignorable by Annotation )

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

111
                            /** @scrutinizer ignore-call */ 
112
                            $array[$name] = $this->valueTypeAdapter->read($reader);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
112 1
                            break;
113
                        // generic for key and value specified
114 4
                        case 2:
115 4
                            if (!$this->keyType->isString() && !$this->keyType->isInteger()) {
0 ignored issues
show
Bug introduced by
The method isString() does not exist on null. ( Ignorable by Annotation )

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

115
                            if (!$this->keyType->/** @scrutinizer ignore-call */ isString() && !$this->keyType->isInteger()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
116 1
                                throw new LogicException(\sprintf('Array keys must be strings or integers at "%s"', $reader->getPath()));
117
                            }
118
119 3
                            if ($this->keyType->isInteger()) {
120 2
                                if (!\ctype_digit($name)) {
121 1
                                    throw new JsonSyntaxException(\sprintf('Expected integer, but found string for key at "%s"', $reader->getPath()));
122
                                }
123
124 1
                                $name = (int)$name;
125
                            }
126
127 2
                            $array[$name] = $this->valueTypeAdapter->read($reader);
128
129 2
                            break;
130
                    }
131
                }
132
133 5
                $reader->endObject();
134
135 5
                break;
136 5
            case JsonToken::BEGIN_ARRAY:
137 4
                $reader->beginArray();
138
139 4
                while ($reader->hasNext()) {
140 4
                    switch ($this->numberOfGenerics) {
141
                        // no generics specified
142 4
                        case 0:
143 3
                        case 1:
144 3
                            $array[] = $this->valueTypeAdapter->read($reader);
145
146 3
                            break;
147
                        default:
148 1
                            throw new LogicException(\sprintf('An array may only specify a generic type for the value at "%s"', $reader->getPath()));
149
                    }
150
                }
151
152 3
                $reader->endArray();
153
154 3
                break;
155
            default:
156 1
                throw new JsonSyntaxException(\sprintf('Could not parse json, expected array or object but found "%s" at "%s"', $token, $reader->getPath()));
157
        }
158
159 8
        return $array;
160
    }
161
162
    /**
163
     * Write the value to the writer for the type
164
     *
165
     * @param JsonWritable $writer
166
     * @param array $value
167
     * @return void
168
     * @throws \LogicException
169
     */
170 9
    public function write(JsonWritable $writer, $value): void
171
    {
172 9
        if (null === $value) {
0 ignored issues
show
introduced by
The condition null === $value is always false.
Loading history...
173 1
            $writer->writeNull();
174
175 1
            return;
176
        }
177
178 8
        if ($this->numberOfGenerics > 2) {
179 1
            throw new LogicException('Array may not have more than 2 generic types');
180
        }
181
182 7
        $arrayIsObject = $this->isArrayObject($value, $this->numberOfGenerics);
183
184 7
        if ($arrayIsObject) {
185 3
            $writer->beginObject();
186
        } else {
187 4
            $writer->beginArray();
188
        }
189
190 7
        foreach ($value as $key => $item) {
191 7
            switch ($this->numberOfGenerics) {
192
                // no generics specified
193 7
                case 0:
194 4
                    if ($arrayIsObject) {
195 1
                        $writer->name((string)$key);
196
                    }
197
198 4
                    $adapter = $this->typeAdapterProvider->getAdapter(TypeToken::createFromVariable($item));
199 4
                    $adapter->write($writer, $item);
200
201 4
                    break;
202
                // generic for value specified
203 3
                case 1:
204 2
                    if ($arrayIsObject) {
205 1
                        $writer->name((string)$key);
206
                    }
207
208 2
                    $this->valueTypeAdapter->write($writer, $item);
209
210 2
                    break;
211
                // generic for key and value specified
212 1
                case 2:
213 1
                    $writer->name($key);
214 1
                    $this->valueTypeAdapter->write($writer, $item);
215
216 7
                    break;
217
            }
218
        }
219
220 7
        if ($arrayIsObject) {
221 3
            $writer->endObject();
222
        } else {
223 4
            $writer->endArray();
224
        }
225 7
    }
226
227
    /**
228
     * Returns true if the array is acting like an object
229
     * @param array $array
230
     * @param int $numberOfGenerics
231
     * @return bool
232
     */
233 7
    private function isArrayObject(array $array, int $numberOfGenerics): bool
234
    {
235 7
        if (2 === $numberOfGenerics) {
236 1
            return true;
237
        }
238
239 6
        return \is_string(\key($array));
240
    }
241
}
242