• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Data / 7796885268

28 Jan 2024 08:47AM UTC coverage: 98.958%. Remained the same
7796885268

push

github

web-flow
Add PHP 8.3 support and update dependencies in composer.json (#31)

190 of 192 relevant lines covered (98.96%)

38.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.9
/src/AbstractData.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Data.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Data
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Data;
18

19
use JBZoo\Utils\Arr;
20
use JBZoo\Utils\Filter;
21

22
/**
23
 * @psalm-suppress MissingTemplateParam
24
 */
25
abstract class AbstractData extends \ArrayObject
26
{
27
    use AliasesTrait;
28

29
    public const LE = "\n";
30

31
    /**
32
     * Utility Method to unserialize the given data.
33
     */
34
    abstract protected function decode(string $string): mixed;
35

36
    /**
37
     * Utility Method to serialize the given data.
38
     * @param  string[] $data The data to serialize
39
     * @return string   The serialized data
40
     */
41
    abstract protected function encode(array $data): string;
42

43
    /**
44
     * @param null|array|false|string $data The data array
45
     * @suppress PhanPartialTypeMismatchArgumentInternal
46
     */
47
    public function __construct($data = [])
48
    {
49
        $this->setFlags(\ArrayObject::ARRAY_AS_PROPS);
318✔
50

51
        if (\is_string($data) && $data !== '' && \file_exists($data)) {
318✔
52
            $data = self::readFile($data);
108✔
53
        }
54

55
        if (\is_string($data)) {
318✔
56
            $data = $this->decode($data);
150✔
57
        }
58

59
        /** @psalm-suppress PossiblyInvalidArgument */
60
        parent::__construct(($data !== false && $data !== null) && \count($data) > 0 ? (array)$data : []);
318✔
61
    }
62

63
    /**
64
     * Magic method to convert the data to a string
65
     * Returns a serialized version of the data contained in
66
     * the data object using serialize().
67
     */
68
    public function __toString(): string
69
    {
70
        return $this->write();
66✔
71
    }
72

73
    /**
74
     * Checks if the given key is present.
75
     * @param string $name The key to check
76
     */
77
    public function has(string $name): bool
78
    {
79
        return $this->offsetExists($name);
174✔
80
    }
81

82
    /**
83
     * Get a value from the data given its key.
84
     * @param string     $key     The key used to fetch the data
85
     * @param null|mixed $default The default value
86
     * @param null|mixed $filter  Filter returned value
87
     */
88
    public function get(string $key, mixed $default = null, mixed $filter = null): mixed
89
    {
90
        self::checkDeprecatedFilter('get', $filter);
168✔
91

92
        $result = $default;
168✔
93
        if ($this->has($key)) {
168✔
94
            $result = $this->offsetGet($key);
108✔
95
        }
96

97
        return self::filter($result, $filter);
168✔
98
    }
99

100
    /**
101
     * Set a value in the data.
102
     * @param string $pathKey   The key used to set the value
103
     * @param mixed  $value     The value to set
104
     * @param string $separator The separator to use when searching for sub keys. Default is '.'
105
     *
106
     * @psalm-suppress UnsafeInstantiation
107
     */
108
    public function set(string $pathKey, mixed $value, string $separator = '.'): self
109
    {
110
        if (\str_contains($pathKey, $separator) && $separator !== '') {
12✔
111
            $keys = \explode($separator, $pathKey);
6✔
112
        } else {
113
            $keys = [$pathKey];
12✔
114
        }
115

116
        $arrayCopy = $this->getArrayCopy();
12✔
117
        self::setNestedValue($arrayCopy, $keys, $value);
12✔
118

119
        // @phpstan-ignore-next-line
120
        return new static($arrayCopy);
12✔
121
    }
122

123
    /**
124
     * Remove a value from the data.
125
     * @param string $name The key of the data to remove
126
     */
127
    public function remove(string $name): static
128
    {
129
        if ($this->has($name)) {
6✔
130
            $this->offsetUnset($name);
6✔
131
        }
132

133
        return $this;
6✔
134
    }
135

136
    /**
137
     * Encode an array or an object in INI format.
138
     */
139
    public function write(): string
140
    {
141
        return $this->encode($this->getArrayCopy());
66✔
142
    }
143

144
    /**
145
     * Find a key in the data recursively
146
     * This method finds the given key, searching also in any array or
147
     * object that's nested under the current data object.
148
     * Example: $data->find('parent-key.sub-key.sub-sub-key');.
149
     * @param string     $key       The key to search for. Can be composed using $separator as the key/su-bkey separator
150
     * @param null|mixed $default   The default value
151
     * @param null|mixed $filter    Filter returned value
152
     * @param string     $separator The separator to use when searching for sub keys. Default is '.'
153
     */
154
    public function find(string $key, mixed $default = null, mixed $filter = null, string $separator = '.'): mixed
155
    {
156
        self::checkDeprecatedFilter('find', $filter);
132✔
157

158
        $value = $this->get($key);
132✔
159

160
        // check if key exists in array
161
        if ($value !== null) {
132✔
162
            return self::filter($value, $filter);
60✔
163
        }
164

165
        // explode search key and init search data
166
        if ($separator === '') {
90✔
167
            throw new Exception("Separator can't be empty");
6✔
168
        }
169

170
        $parts = \explode($separator, $key);
84✔
171
        $data  = $this;
84✔
172

173
        foreach ($parts as $part) {
84✔
174
            // handle ArrayObject and Array
175
            if ($data instanceof \ArrayObject && $data[$part] !== null) {
84✔
176
                $data = $data[$part];
78✔
177
                continue;
78✔
178
            }
179

180
            if (\is_array($data) && isset($data[$part])) {
84✔
181
                $data = $data[$part];
72✔
182
                continue;
72✔
183
            }
184

185
            // Handle object
186
            if (\is_object($data) && \property_exists($data, $part)) {
18✔
187
                /** @phpstan-ignore-next-line */
188
                $data = $data->{$part};
6✔
189
                continue;
6✔
190
            }
191

192
            return self::filter($default, $filter);
18✔
193
        }
194

195
        // return existing value
196
        return self::filter($data, $filter);
72✔
197
    }
198

199
    /**
200
     * Find a value also in nested arrays/objects.
201
     * @param mixed $needle The value to search for
202
     */
203
    public function search(mixed $needle): null|bool|float|int|string
204
    {
205
        $aIterator = new \RecursiveArrayIterator($this->getArrayCopy());
6✔
206
        $iterator  = new \RecursiveIteratorIterator($aIterator);
6✔
207

208
        while ($iterator->valid()) {
6✔
209
            $iterator->current();
6✔
210

211
            if ($iterator->current() === $needle) {
6✔
212
                return $aIterator->key();
6✔
213
            }
214

215
            $iterator->next();
6✔
216
        }
217

218
        return false;
6✔
219
    }
220

221
    /**
222
     * Return flattened array copy. Keys are <b>NOT</b> preserved.
223
     */
224
    public function flattenRecursive(): array
225
    {
226
        $flat = [];
6✔
227

228
        foreach (new \RecursiveIteratorIterator(new \RecursiveArrayIterator($this->getArrayCopy())) as $value) {
6✔
229
            $flat[] = $value;
6✔
230
        }
231

232
        return $flat;
6✔
233
    }
234

235
    /**
236
     * @param  int|string $key
237
     * @return null|mixed
238
     * @phpstan-ignore-next-line
239
     */
240
    public function offsetGet($key): mixed
241
    {
242
        if (!\property_exists($this, (string)$key)) {
168✔
243
            return null;
12✔
244
        }
245

246
        return parent::offsetGet($key);
162✔
247
    }
248

249
    /**
250
     * Compare value by key with something.
251
     * @SuppressWarnings(PHPMD.ShortMethodName)
252
     */
253
    public function is(string $key, mixed $compareWith = true, bool $strictMode = false): bool
254
    {
255
        if (!\str_contains($key, '.')) {
6✔
256
            $value = $this->get($key);
6✔
257
        } else {
258
            $value = $this->find($key);
6✔
259
        }
260

261
        if ($strictMode) {
6✔
262
            return $value === $compareWith;
6✔
263
        }
264

265
        /** @noinspection TypeUnsafeComparisonInspection */
266
        return $value == $compareWith;
6✔
267
    }
268

269
    public function getSchema(): array
270
    {
271
        return Arr::getSchema($this->getArrayCopy());
6✔
272
    }
273

274
    /**
275
     * Filter value before return.
276
     */
277
    protected static function filter(mixed $value, mixed $filter): mixed
278
    {
279
        if ($filter !== null) {
168✔
280
            $value = Filter::_($value, $filter);
12✔
281
        }
282

283
        return $value;
168✔
284
    }
285

286
    protected static function readFile(string $filePath): bool|string
287
    {
288
        $contents = false;
108✔
289

290
        $realPath = \realpath($filePath);
108✔
291
        if ($realPath !== false) {
108✔
292
            $contents = \file_get_contents($realPath);
108✔
293
        }
294

295
        return $contents;
108✔
296
    }
297

298
    /**
299
     * Check is array is nested.
300
     */
301
    protected static function isMulti(array $array): bool
302
    {
303
        $arrayCount = \array_filter($array, '\is_array');
12✔
304

305
        return \count($arrayCount) > 0;
12✔
306
    }
307

308
    private static function setNestedValue(array &$array, array $keys, mixed $value): void
309
    {
310
        $key = \array_shift($keys);
12✔
311

312
        if (\count($keys) === 0) {
12✔
313
            $array[$key] = $value;
12✔
314
        } else {
315
            if (!isset($array[$key]) || !\is_array($array[$key])) {
6✔
316
                $array[$key] = [];
6✔
317
            }
318

319
            self::setNestedValue($array[$key], $keys, $value);
6✔
320
        }
321
    }
322

323
    private static function checkDeprecatedFilter(string $prefix, mixed $filter): void
324
    {
325
        if (!\is_string($filter)) {
168✔
326
            return;
168✔
327
        }
328

329
        if (\in_array($filter, ['bool', 'int', 'float', 'string', 'array', 'arr'], true)) {
12✔
330
            if ($filter === 'arr') {
12✔
331
                $filter = 'array';
×
332
            }
333

334
            /** @psalm-suppress RedundantFunctionCall */
335
            $methodName = $prefix . \ucfirst(\strtolower($filter));
12✔
336
            @\trigger_error(
12✔
337
                "Instead of filter=\"{$filter}\", please use `\$data->{$methodName}(\$key, \$default)` method",
12✔
338
                \E_USER_DEPRECATED,
12✔
339
            );
12✔
340
        }
341
    }
342
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc