ARecord::reloadClass()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
nc 4
nop 1
dl 0
loc 13
ccs 8
cts 8
cp 1
crap 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace kalanis\kw_mapper\Records;
4
5
6
use ArrayAccess;
7
use Iterator;
8
use kalanis\kw_mapper\Interfaces\ICanFill;
9
use kalanis\kw_mapper\Interfaces\IEntryType;
10
use kalanis\kw_mapper\MapperException;
11
use ReflectionClass;
12
use ReflectionException;
13
14
15
/**
16
 * Class ARecord
17
 * @package kalanis\kw_mapper\Records
18
 * Class to map entries to their respective values
19
 * The level of "obstruction" to accessing properties is necessary
20
 * or it could not be possible to guarantee content values.
21
 * The children must stay too simple to avoid some usual problems which came with multilevel extending
22
 */
23
abstract class ARecord implements ArrayAccess, Iterator
24
{
25
    use TMapper;
26
27
    /** @var string|int|null */
28
    private $key = null;
29
    /** @var Entry[] */
30
    protected array $entries = [];
31
32
    /** @var int[] */
33
    protected static array $types = [IEntryType::TYPE_BOOLEAN,
34
                               IEntryType::TYPE_INTEGER, IEntryType::TYPE_FLOAT,
35
                               IEntryType::TYPE_STRING,
36
                               IEntryType::TYPE_SET, IEntryType::TYPE_ARRAY, IEntryType::TYPE_OBJECT];
37
38
    /**
39
     * Mapper constructor.
40
     * @throws MapperException
41
     */
42 178
    public function __construct()
43
    {
44 178
        $this->addEntries();
45
    }
46
47
    /**
48
     * @throws MapperException
49
     */
50
    abstract protected function addEntries(): void;
51
52
    /**
53
     * @param string $name
54
     * @param int $type
55
     * @param string|int|array<string|int, string|int>|null $defaultParam
56
     * @throws MapperException
57
     */
58 177
    final protected function addEntry($name, int $type, $defaultParam = null): void
59
    {
60 177
        $this->checkDefault($type, $defaultParam);
61 172
        $this->entries[$name] = Entry::getInstance()->setType($type)->setParams($defaultParam);
62
    }
63
64
    /**
65
     * @param iterable<string|int, string|int|float|object> $data
66
     */
67 1
    public function loadWithData(iterable $data): void
68
    {
69 1
        foreach ($data as $key => $value) {
70 1
            if ($this->offsetExists($key)) {
71 1
                $this->offsetSet($key, $value);
72
            }
73
        }
74
    }
75
76
    /**
77
     * @param string|int $name
78
     * @throws MapperException
79
     * @return Entry
80
     */
81 92
    final public function getEntry($name): Entry
82
    {
83 92
        $this->offsetCheck($name);
84 86
        return $this->entries[$name];
85
    }
86
87 26
    final public function __clone()
88
    {
89 26
        $entries = [];
90 26
        foreach ($this->entries as $key => $entry) {
91 26
            $entries[$key] = clone $entry;
92
        }
93 26
        $this->entries = $entries;
94
    }
95
96
    /**
97
     * @param string|int $name
98
     * @param mixed $value
99
     */
100 71
    final public function __set($name, $value): void
101
    {
102 71
        $this->offsetSet($name, $value);
103
    }
104
105
    /**
106
     * @param string|int $name
107
     * @throws MapperException
108
     * @return int|ICanFill|string|null
109
     */
110
    #[\ReturnTypeWillChange]
111 23
    final public function __get($name)
112
    {
113 23
        return $this->offsetGet($name);
114
    }
115
116
    /**
117
     * @throws MapperException
118
     * @return int|ICanFill|string|null
119
     */
120
    #[\ReturnTypeWillChange]
121 70
    final public function current()
122
    {
123 70
        return $this->valid() ? $this->offsetGet($this->key) : null ;
124
    }
125
126 70
    final public function next(): void
127
    {
128 70
        next($this->entries);
129 70
        $this->key = key($this->entries);
130
    }
131
132
    #[\ReturnTypeWillChange]
133 70
    final public function key()
134
    {
135 70
        return $this->key;
136
    }
137
138 70
    final public function valid(): bool
139
    {
140 70
        return $this->offsetExists($this->key);
141
    }
142
143 70
    final public function rewind(): void
144
    {
145 70
        reset($this->entries);
146 70
        $this->key = key($this->entries);
147
    }
148
149 123
    final public function offsetExists($offset): bool
150
    {
151 123
        return isset($this->entries[$offset]);
152
    }
153
154
    /**
155
     * @param mixed $offset
156
     * @throws MapperException
157
     * @return int|ICanFill|mixed|string|null
158
     */
159
    #[\ReturnTypeWillChange]
160 94
    final public function offsetGet($offset)
161
    {
162 94
        $this->offsetCheck($offset);
163 94
        $data = & $this->entries[$offset];
164
165 94
        switch ($data->getType()) {
166 94
            case IEntryType::TYPE_BOOLEAN:
167 94
            case IEntryType::TYPE_INTEGER:
168 92
            case IEntryType::TYPE_FLOAT:
169 92
            case IEntryType::TYPE_STRING:
170 7
            case IEntryType::TYPE_SET:
171 7
            case IEntryType::TYPE_ARRAY:
172 94
                return $data->getData();
173 3
            case IEntryType::TYPE_OBJECT:
174 3
                if (empty($data->getData())) {
175 3
                    return null;
176
                }
177
                /** @var ICanFill $class */
178 2
                $class = $data->getData();
179 2
                return $class->dumpData();
180
            default:
181
                // @codeCoverageIgnoreStart
182
                // happens only when someone is evil enough and change type directly on entry
183
                throw new MapperException(sprintf('Unknown type *%d*', $data->getType()));
184
                // @codeCoverageIgnoreEnd
185
        }
186
    }
187
188
    /**
189
     * @param mixed $offset
190
     * @throws MapperException
191
     */
192 1
    final public function offsetUnset($offset): void
193
    {
194 1
        throw new MapperException(sprintf('Key %s removal denied', $offset));
195
    }
196
197
    /**
198
     * @param mixed $offset
199
     * @throws MapperException
200
     */
201 123
    final protected function offsetCheck($offset): void
202
    {
203 123
        if (!$this->offsetExists($offset)) {
204 8
            throw new MapperException(sprintf('Unknown key *%s*', $offset));
205
        }
206
    }
207
208
    /**
209
     * @param Entry $data
210
     * @throws MapperException
211
     */
212 3
    final protected function reloadClass(Entry $data): void
213
    {
214 3
        if (empty($data->getData())) {
215 3
            $dataClass = $data->getParams();
216
            try {
217
                /** @var class-string $dataClass */
218 3
                $reflect = new ReflectionClass($dataClass);
0 ignored issues
show
Bug introduced by
It seems like $dataClass can also be of type array<integer|string,integer|string>; however, parameter $objectOrClass of ReflectionClass::__construct() does only seem to accept object|string, 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

218
                $reflect = new ReflectionClass(/** @scrutinizer ignore-type */ $dataClass);
Loading history...
219 2
                $classInstance = $reflect->newInstance();
220 1
            } catch (ReflectionException $ex) {
221 1
                throw new MapperException($ex->getMessage(), $ex->getCode(), $ex);
222
            }
223
            /** @var ICanFill $classInstance */
224 2
            $data->setData($classInstance);
225
        }
226
    }
227
228
    /**
229
     * @param int $type
230
     * @param string|int|float|object|array<string|int|float|object> $default
231
     * @throws MapperException
232
     */
233 177
    private function checkDefault(int $type, $default): void
234
    {
235
        switch ($type) {
236 177
            case IEntryType::TYPE_INTEGER:
237 176
            case IEntryType::TYPE_FLOAT:
238 176
            case IEntryType::TYPE_STRING:
239 171
                $this->checkLengthNumeric($default, $type);
240 170
                return;
241 69
            case IEntryType::TYPE_BOOLEAN:
242 38
            case IEntryType::TYPE_ARRAY:
243 17
            case IEntryType::TYPE_SET:
244 65
                return;
245 16
            case IEntryType::TYPE_OBJECT:
246 15
                $this->checkObjectString($default, $type);
247 14
                $this->checkObjectInstance($default, $type);
248 12
                return;
249
            default:
250 1
                throw new MapperException(sprintf('Unknown type *%d*', $type));
251
        }
252
    }
253
254
    /**
255
     * @param mixed $value
256
     * @param int $type
257
     * @throws MapperException
258
     */
259 171
    private function checkLengthNumeric($value, int $type): void
260
    {
261 171
        if (!is_numeric($value)) {
262 1
            throw new MapperException(sprintf('You must set length as number for type *%d*', $type));
263
        }
264
    }
265
266
    /**
267
     * @param mixed $value
268
     * @param int $type
269
     * @throws MapperException
270
     */
271 15
    private function checkObjectString($value, int $type): void
272
    {
273 15
        if (!is_string($value)) {
274 1
            throw new MapperException(sprintf('You must set available string representing object for type *%d*', $type));
275
        }
276
    }
277
278
    /**
279
     * @param mixed $value
280
     * @param int $type
281
     * @throws MapperException
282
     */
283 14
    private function checkObjectInstance($value, int $type): void
284
    {
285
        try {
286
            /** @var class-string $value */
287 14
            $reflect = new ReflectionClass($value);
288 13
            $classForTest = $reflect->newInstance();
289 1
        } catch (ReflectionException $ex) {
290 1
            throw new MapperException($ex->getMessage(), $ex->getCode(), $ex);
291
        }
292 13
        if (!$classForTest instanceof ICanFill) {
293 1
            throw new MapperException(sprintf('When you set string representing object for type *%d*, it must be stdClass or have ICanFill interface', $type));
294
        }
295
    }
296
297
    /**
298
     * From trait TMapper - map this record as one to processing
299
     * @return ARecord
300
     */
301 40
    final protected function getSelf(): ARecord
302
    {
303 40
        return $this;
304
    }
305
}
306