Completed
Pull Request — master (#44)
by Nate
05:11 queued 02:28
created

ArrayTypeAdapter::write()   B

Complexity

Conditions 11
Paths 30

Size

Total Lines 54
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 11

Importance

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

240
        return \is_string(\key(/** @scrutinizer ignore-type */ $array));
Loading history...
241
    }
242
}
243