Completed
Pull Request — master (#44)
by Nate
04:08 queued 02:15
created

ArrayTypeAdapter::read()   C

Complexity

Conditions 17
Paths 16

Size

Total Lines 85
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 17

Importance

Changes 0
Metric Value
cc 17
eloc 49
nc 16
nop 1
dl 0
loc 85
ccs 45
cts 45
cp 1
crap 17
rs 5.2166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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;
10
11
use LogicException;
12
use stdClass;
13
use Tebru\Gson\Exception\JsonSyntaxException;
14
use Tebru\Gson\Internal\TypeAdapterProvider;
15
use Tebru\PhpType\TypeToken;
16
17
/**
18
 * Class ArrayTypeAdapter
19
 *
20
 * @author Nate Brunette <[email protected]>
21
 */
22
class ArrayTypeAdapter extends TypeAdapter
23
{
24
    /**
25
     * @var TypeAdapterProvider
26
     */
27
    private $typeAdapterProvider;
28
29
    /**
30
     * @var TypeToken
31
     */
32
    private $keyType;
33
34
    /**
35
     * @var TypeAdapter
36
     */
37
    private $valueTypeAdapter;
38
39
    /**
40
     * @var int
41
     */
42
    private $numberOfGenerics;
43
44
    /**
45
     * Constructor
46
     *
47
     * @param TypeAdapterProvider $typeAdapterProvider
48
     * @param TypeToken $keyType
49
     * @param TypeAdapter $valueTypeAdapter
50
     * @param int $numberOfGenerics
51
     */
52
    public function __construct(
53
        TypeAdapterProvider $typeAdapterProvider,
54
        TypeToken $keyType,
55
        TypeAdapter $valueTypeAdapter,
56 24
        int $numberOfGenerics
57
    ) {
58
        $this->typeAdapterProvider = $typeAdapterProvider;
59
        $this->keyType = $keyType;
60
        $this->valueTypeAdapter = $valueTypeAdapter;
61
        $this->numberOfGenerics = $numberOfGenerics;
62 24
    }
63 24
64 24
    /**
65 24
     * Read the next value, convert it to its type and return it
66 24
     *
67
     * @param JsonReadable $reader
68
     * @return array|null
69
     * @throws \LogicException
70
     * @throws \Tebru\Gson\Exception\JsonSyntaxException If trying to read from non object/array
71
     */
72
    public function read(JsonReadable $reader): ?array
73
    {
74
        $token = $reader->peek();
75
        if ($token === JsonToken::NULL) {
76 14
            $reader->nextNull();
77
            return null;
78 14
        }
79 14
80 1
        $array = [];
81 1
82
        if ($this->numberOfGenerics > 2) {
83
            throw new LogicException(\sprintf('Array may not have more than 2 generic types at "%s"', $reader->getPath()));
84 13
        }
85
86 13
        switch ($token) {
87 1
            case JsonToken::BEGIN_OBJECT:
88
                $reader->beginObject();
89
90
                while ($reader->hasNext()) {
91 12
                    $name = $reader->nextName();
92 7
93
                    switch ($this->numberOfGenerics) {
94 7
                        // no generics specified
95 7
                        case 0:
96
                            // By now we know that we're deserializing a json object to an array.
97 7
                            // If there is a nested object, continue deserializing to an array,
98
                            // otherwise guess the type using the wildcard
99 7
                            $type = $reader->peek() === JsonToken::BEGIN_OBJECT
100
                                ? TypeToken::create(TypeToken::HASH)
101
                                : TypeToken::create(TypeToken::WILDCARD);
102
103 3
                            $adapter = $this->typeAdapterProvider->getAdapter($type);
104 1
                            $array[$name] = $adapter->read($reader);
105 3
                            break;
106
                        // generic for value specified
107 3
                        case 1:
108 3
                            $array[$name] = $this->valueTypeAdapter->read($reader);
109 3
                            break;
110
                        // generic for key and value specified
111 5
                        case 2:
112 1
                            if (!$this->keyType->isString() && !$this->keyType->isInteger()) {
113 1
                                throw new LogicException(\sprintf('Array keys must be strings or integers at "%s"', $reader->getPath()));
114
                            }
115 4
116 4
                            if ($this->keyType->isInteger()) {
117 1
                                if (!\ctype_digit($name)) {
118
                                    throw new JsonSyntaxException(\sprintf('Expected integer, but found string for key at "%s"', $reader->getPath()));
119
                                }
120 3
121 2
                                $name = (int)$name;
122 1
                            }
123
124
                            $array[$name] = $this->valueTypeAdapter->read($reader);
125 1
126
                            break;
127
                    }
128 2
                }
129
130 2
                $reader->endObject();
131
132
                break;
133
            case JsonToken::BEGIN_ARRAY:
134 5
                $reader->beginArray();
135
136 5
                while ($reader->hasNext()) {
137 5
                    switch ($this->numberOfGenerics) {
138 4
                        // no generics specified
139
                        case 0:
140 4
                        case 1:
141 4
                            $array[] = $this->valueTypeAdapter->read($reader);
142
143 4
                            break;
144 3
                        default:
145 3
                            throw new LogicException(\sprintf('An array may only specify a generic type for the value at "%s"', $reader->getPath()));
146
                    }
147 3
                }
148
149 1
                $reader->endArray();
150
151
                break;
152
            default:
153 3
                throw new JsonSyntaxException(\sprintf('Could not parse json, expected array or object but found "%s" at "%s"', $token, $reader->getPath()));
154
        }
155 3
156
        return $array;
157 1
    }
158
159
    /**
160 8
     * Write the value to the writer for the type
161
     *
162
     * @param JsonWritable $writer
163
     * @param array|stdClass|null $value
164
     * @return void
165
     * @throws \LogicException
166
     */
167
    public function write(JsonWritable $writer, $value): void
168
    {
169
        if (null === $value) {
170
            $writer->writeNull();
171 10
172
            return;
173 10
        }
174 1
175
        if ($this->numberOfGenerics > 2) {
176 1
            throw new LogicException(\sprintf('Array may not have more than 2 generic types at "%s"', $writer->getPath()));
177
        }
178
179 9
        $arrayIsObject = $this->isArrayObject($value, $this->numberOfGenerics);
180 1
181
        if ($arrayIsObject) {
182
            $writer->beginObject();
183 8
        } else {
184
            $writer->beginArray();
185 8
        }
186 4
187
        foreach ($value as $key => $item) {
188 4
            switch ($this->numberOfGenerics) {
189
                // no generics specified
190
                case 0:
191 8
                    if ($arrayIsObject) {
192 8
                        $writer->name((string)$key);
193
                    }
194 8
195 5
                    $adapter = $this->typeAdapterProvider->getAdapter(TypeToken::createFromVariable($item));
196 2
                    $adapter->write($writer, $item);
197
198
                    break;
199 5
                // generic for value specified
200 5
                case 1:
201
                    if ($arrayIsObject) {
202 5
                        $writer->name((string)$key);
203
                    }
204 3
205 2
                    $this->valueTypeAdapter->write($writer, $item);
206 1
207
                    break;
208
                // generic for key and value specified
209 2
                case 2:
210
                    $writer->name($key);
211 2
                    $this->valueTypeAdapter->write($writer, $item);
212
213 1
                    break;
214 1
            }
215 1
        }
216
217 8
        if ($arrayIsObject) {
218
            $writer->endObject();
219
        } else {
220
            $writer->endArray();
221 8
        }
222 4
    }
223
224 4
    /**
225
     * Returns true if the array is acting like an object
226 8
     * @param array|stdClass $array
227
     * @param int $numberOfGenerics
228
     * @return bool
229
     */
230
    private function isArrayObject($array, int $numberOfGenerics): bool
231
    {
232
        if (2 === $numberOfGenerics) {
233
            return true;
234 8
        }
235
236 8
        return \is_string(\key($array));
0 ignored issues
show
Bug introduced by
It seems like $array 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

236
        return \is_string(\key(/** @scrutinizer ignore-type */ $array));
Loading history...
237 1
    }
238
}
239