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

ArrayTypeAdapter   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 181
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 37
eloc 82
c 0
b 0
f 0
dl 0
loc 181
ccs 80
cts 80
cp 1
rs 9.44

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
C write() 0 49 15
D read() 0 67 21
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\Context\ReaderContext;
16
use Tebru\Gson\TypeAdapter;
17
use Tebru\Gson\Context\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
    protected $typeAdapterProvider;
31
32
    /**
33
     * @var TypeToken
34
     */
35
    protected $keyType;
36
37
    /**
38
     * @var TypeAdapter
39
     */
40
    protected $valueTypeAdapter;
41
42
    /**
43
     * @var int
44
     */
45
    protected $numberOfGenerics;
46
47
    /**
48
     * A TypeAdapter cache keyed by raw type
49
     *
50
     * @var TypeAdapter[]
51
     */
52
    protected $adapters = [];
53
54
    /**
55
     * Constructor
56
     *
57
     * @param TypeAdapterProvider $typeAdapterProvider
58
     * @param TypeToken $keyType
59
     * @param TypeAdapter $valueTypeAdapter
60
     * @param int $numberOfGenerics
61
     */
62 24
    public function __construct(
63
        TypeAdapterProvider $typeAdapterProvider,
64
        TypeToken $keyType,
65
        TypeAdapter $valueTypeAdapter,
66
        int $numberOfGenerics
67
    ) {
68 24
        $this->typeAdapterProvider = $typeAdapterProvider;
69 24
        $this->keyType = $keyType;
70 24
        $this->valueTypeAdapter = $valueTypeAdapter;
71 24
        $this->numberOfGenerics = $numberOfGenerics;
72 24
    }
73
74
    /**
75
     * Read the next value, convert it to its type and return it
76
     *
77
     * @param array|null $value
78
     * @param ReaderContext $context
79
     * @return array|null
80
     */
81 14
    public function read($value, ReaderContext $context): ?array
82
    {
83 14
        if ($value === null) {
84 1
            return null;
85
        }
86
87 13
        if (!is_array($value)) {
88 1
            throw new JsonSyntaxException(sprintf('Could not parse json, expected array or object but found "%s"', gettype($value)));
89
        }
90
91 12
        $result = [];
92
93 12
        if ($this->numberOfGenerics > 2) {
94 1
            throw new LogicException('Array may not have more than 2 generic types');
95
        }
96
97 11
        if ($this->keyType->phpType !== TypeToken::WILDCARD
98 11
            && $this->keyType->phpType !== TypeToken::STRING
99 11
            && $this->keyType->phpType !== TypeToken::INTEGER
100
        ) {
101 1
            throw new LogicException('Array keys must be strings or integers');
102
        }
103
104 10
        $arrayIsObject = $this->numberOfGenerics === 2 || is_string(key($value));
105 10
        $enableScalarAdapters = $context->enableScalarAdapters();
106
107 10
        foreach ($value as $key => $item) {
108 10
            $itemValue = null;
109 10
            switch ($this->numberOfGenerics) {
110 10
                case 0:
111 5
                    if (!$enableScalarAdapters && is_scalar($item)) {
112 1
                        $itemValue = $item;
113 1
                        break;
114
                    }
115
116 4
                    if (!$arrayIsObject) {
117 1
                        $itemValue = $this->valueTypeAdapter->read($item, $context);
118 1
                        break;
119
                    }
120
121 3
                    if (is_array($item)) {
122 1
                        $itemValue = $this->read($item, $context);
123 1
                        break;
124
                    }
125
126 3
                    $type = TypeToken::createFromVariable($item);
127 3
                    $adapter = $this->adapters[$type->rawType] ?? $this->adapters[$type->rawType] = $this->typeAdapterProvider->getAdapter($type);
128 3
                    $itemValue = $adapter->read($item, $context);
129 3
                    break;
130 6
                case 1:
131 2
                    $itemValue = $this->valueTypeAdapter->read($item, $context);
132 2
                    break;
133 4
                case 2:
134 4
                    if (($this->keyType->phpType === TypeToken::INTEGER) && !ctype_digit((string)$key)) {
135 1
                        throw new JsonSyntaxException('Expected integer, but found string for key');
136
                    }
137
138 3
                    $itemValue = (!$enableScalarAdapters && is_scalar($item))
139 1
                        ? $item
140 3
                        : $this->valueTypeAdapter->read($item, $context);
141 3
                    break;
142
            }
143
144 9
            $result[$arrayIsObject ? (string)$key : (int)$key] = $itemValue;
145
        }
146
147 9
        return $result;
148
    }
149
150
    /**
151
     * Write the value to the writer for the type
152
     *
153
     * @param array|null $value
154
     * @param WriterContext $context
155
     * @return array|null
156
     */
157 10
    public function write($value, WriterContext $context): ?array
158
    {
159 10
        if ($value === null) {
160 1
            return null;
161
        }
162
163 9
        if ($this->numberOfGenerics > 2) {
164 1
            throw new LogicException('Array may not have more than 2 generic types');
165
        }
166
167 8
        $arrayIsObject = $this->numberOfGenerics === 2 || is_string(key($value));
168 8
        $enableScalarAdapters = $context->enableScalarAdapters();
169 8
        $serializeNull = $context->serializeNull();
170 8
        $result = [];
171
172 8
        foreach ($value as $key => $item) {
173 8
            if ($item === null && !$serializeNull) {
174 1
                continue;
175
            }
176
177 8
            if (!$enableScalarAdapters && is_scalar($item)) {
178 2
                $result[$arrayIsObject ? (string)$key : (int)$key] = $item;
179 2
                continue;
180
            }
181
182 7
            $itemValue = null;
183 7
            switch ($this->numberOfGenerics) {
184
                // no generics specified
185 7
                case 0:
186 6
                    if (is_array($item)) {
187 1
                        $itemValue = $this->write($item, $context);
188 1
                        break;
189
                    }
190
191 5
                    $type = TypeToken::createFromVariable($item);
192 5
                    $adapter = $this->adapters[$type->rawType] ?? $this->adapters[$type->rawType] = $this->typeAdapterProvider->getAdapter($type);
193 5
                    $itemValue = $adapter->write($item, $context);
194 5
                    break;
195
                // generic for value specified
196 1
                case 1:
197 1
                case 2:
198 1
                    $itemValue = $this->valueTypeAdapter->write($item, $context);
199 1
                    break;
200
            }
201
202 7
            $result[$arrayIsObject ? (string)$key : (int)$key] = $itemValue;
203
        }
204
205 8
        return $result;
206
    }
207
}
208