├── src
├── Exception.php
├── Data.php
├── Yml.php
├── JSON.php
├── PhpArray.php
├── Ini.php
├── functions.php
├── AliasesTrait.php
└── AbstractData.php
├── LICENSE
├── composer.json
├── CLAUDE.md
└── README.md
/src/Exception.php:
--------------------------------------------------------------------------------
1 | decode($data);
28 | }
29 |
30 | if ($data === false || $data === null) {
31 | $data = [];
32 | }
33 |
34 | parent::__construct($data);
35 | }
36 |
37 | protected function decode(string $string): mixed
38 | {
39 | if (\file_exists($string)) {
40 | return include $string;
41 | }
42 |
43 | return [];
44 | }
45 |
46 | protected function encode(array $data): string
47 | {
48 | $data = [
49 | 'render($data);
29 | }
30 |
31 | protected function render(array $data = [], array $parent = []): string
32 | {
33 | $result = [];
34 |
35 | foreach ($data as $dataKey => $dataValue) {
36 | if (\is_array($dataValue)) {
37 | if (self::isMulti($dataValue)) {
38 | $sections = \array_merge($parent, (array)$dataKey);
39 | $result[] = '';
40 | $result[] = '[' . \implode('.', $sections) . ']';
41 | $result[] = $this->render($dataValue, $sections);
42 | } else {
43 | foreach ($dataValue as $key => $value) {
44 | $result[] = $dataKey . '[' . $key . '] = "' . \str_replace('"', '\"', (string)$value) . '"';
45 | }
46 | }
47 | } else {
48 | $result[] = $dataKey . ' = "' . \str_replace('"', '\"', (string)$dataValue) . '"';
49 | }
50 | }
51 |
52 | return \implode(Data::LE, $result);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/functions.php:
--------------------------------------------------------------------------------
1 | =7.3.3",
38 |
39 | "symfony/polyfill-ctype" : ">=1.33.0",
40 | "symfony/polyfill-mbstring" : ">=1.33.0",
41 | "symfony/polyfill-php73" : ">=1.33.0",
42 | "symfony/polyfill-php80" : ">=1.33.0",
43 | "symfony/polyfill-php81" : ">=1.33.0"
44 | },
45 |
46 | "suggest" : {
47 | "symfony/yaml" : ">=7.3",
48 | "jbzoo/utils" : ">=7.2"
49 | },
50 |
51 | "autoload" : {
52 | "psr-4" : {"JBZoo\\Data\\" : "src"},
53 | "files" : [
54 | "src/functions.php"
55 | ]
56 |
57 | },
58 |
59 | "autoload-dev" : {
60 | "psr-4" : {"JBZoo\\PHPUnit\\" : "tests"}
61 | },
62 |
63 | "config" : {
64 | "optimize-autoloader" : true,
65 | "allow-plugins" : {"composer/package-versions-deprecated" : true}
66 | },
67 |
68 | "extra" : {
69 | "branch-alias" : {
70 | "dev-master" : "7.x-dev"
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
1 | # CLAUDE.md
2 |
3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4 |
5 | ## Project Overview
6 |
7 | JBZoo Data is a PHP library that provides an extended version of ArrayObject for working with data arrays and various formats (JSON, YAML, INI, PHP arrays). It offers a fluent API for data manipulation with built-in filtering, nested access, and format conversion capabilities.
8 |
9 | ## Core Architecture
10 |
11 | ### Abstract Base Class Pattern
12 | - `AbstractData` - Base class extending PHP's ArrayObject with common functionality
13 | - `AliasesTrait` - Provides multiple access methods (array, object, method-based)
14 | - Concrete implementations: `Data`, `JSON`, `Yml`, `Ini`, `PhpArray`
15 |
16 | ### Function-Based Factory Pattern
17 | The `src/functions.php` file provides factory functions for creating instances:
18 | - `data()` - Creates Data objects
19 | - `json()` - Creates JSON objects
20 | - `yml()` - Creates Yml objects
21 | - `ini()` - Creates Ini objects
22 | - `phpArray()` - Creates PhpArray objects
23 |
24 | ### Data Access Patterns
25 | Multiple ways to access data with graceful undefined handling:
26 | - Array access: `$data['key']`
27 | - Object access: `$data->key`
28 | - Method access: `$data->get('key', $default)`
29 | - Nested access: `$data->find('deep.nested.key', $default)`
30 |
31 | ### Format Support
32 | Each class handles specific data formats:
33 | - `JSON` - JSON strings and files
34 | - `Yml` - YAML format (requires symfony/yaml)
35 | - `Ini` - INI configuration files
36 | - `PhpArray` - PHP files returning arrays
37 | - `Data` - Generic/serialized data
38 |
39 | ## Common Commands
40 |
41 | ### Development Setup
42 | ```bash
43 | make update # Install/update all dependencies via Composer
44 | make autoload # Dump optimized autoloader
45 | ```
46 |
47 | ### Testing
48 | ```bash
49 | make test # Run PHPUnit tests
50 | make test-all # Run all tests and code style checks at once
51 | make codestyle # Run all linters and style checks
52 | ```
53 |
54 | ### Individual Quality Assurance Tools
55 | ```bash
56 | make test-phpstan # Static analysis with PHPStan
57 | make test-psalm # Static analysis with Psalm
58 | make test-phpcs # PHP CodeSniffer (PSR-12 + compatibility)
59 | make test-phpcsfixer # PHP-CS-Fixer style check
60 | make test-phpcsfixer-fix # Auto-fix code style issues
61 | make test-phpmd # PHP Mess Detector
62 | make test-phan # Phan static analyzer
63 | make test-performance # Run benchmark tests
64 | ```
65 |
66 | ### Reports and Analysis
67 | ```bash
68 | make report-all # Generate all analysis reports
69 | make report-phpmetrics # PHP Metrics report
70 | make report-pdepend # PHP Depend analysis
71 | make report-phploc # Lines of code statistics
72 | ```
73 |
74 | ## Development Standards
75 |
76 | ### PHP Requirements
77 | - PHP 8.2+ required
78 | - Strict types enabled (`declare(strict_types=1)`)
79 | - PSR-12 coding standard
80 | - Full type hints required
81 |
82 | ### Code Patterns
83 | When extending the library:
84 | 1. New format classes should extend `AbstractData`
85 | 2. Implement `decode()` and `encode()` abstract methods
86 | 3. Add factory function to `functions.php`
87 | 4. Follow existing naming conventions (`Yml` not `Yaml`, `PhpArray` not `PHPArray`)
88 |
89 | ### Testing Structure
90 | - Tests in `tests/` directory follow naming pattern `Data{Format}Test.php`
91 | - Benchmark tests in `tests/phpbench/` for performance monitoring
92 | - Test fixtures in `tests/resource/`
93 | - Use `tests/Fixtures.php` for common test data
94 |
95 | ### Performance Considerations
96 | The library includes comprehensive benchmarks comparing:
97 | - Native PHP arrays vs ArrayObject performance
98 | - Different access methods (array, object, method calls)
99 | - Data retrieval patterns for defined vs undefined values
100 |
101 | ## Key Dependencies
102 |
103 | ### Required
104 | - `php: ^8.2`
105 | - `ext-json: *`
106 |
107 | ### Development/Optional
108 | - `jbzoo/toolbox-dev: ^7.2` - Development tooling
109 | - `jbzoo/utils: ^7.2.2` - Utility functions for filtering
110 | - `symfony/yaml: >=7.3.3` - YAML parsing support
111 |
112 | ## Test Data and Fixtures
113 |
114 | The library uses shared test fixtures across format tests:
115 | - `tests/Fixtures.php` - Common test data arrays
116 | - `tests/resource/` - Sample data files in various formats
117 | - Consistent test data ensures format conversion accuracy
118 |
119 | ## Filter Integration
120 |
121 | When JBZoo/Utils is available, the library supports data filtering:
122 | - `$data->get('key', $default, 'int')` - Type conversion
123 | - `$data->find('key', $default, 'email')` - Email validation
124 | - Chain filters: `'strip,trim'`
125 | - Custom callbacks supported
126 |
127 | ## Build System Integration
128 |
129 | The project uses JBZoo's standardized Makefile system via:
130 | ```makefile
131 | ifneq (, $(wildcard ./vendor/jbzoo/codestyle/src/init.Makefile))
132 | include ./vendor/jbzoo/codestyle/src/init.Makefile
133 | endif
134 | ```
--------------------------------------------------------------------------------
/src/AliasesTrait.php:
--------------------------------------------------------------------------------
1 | get($key, $default));
31 | }
32 |
33 | public function getIntNull(string $key, ?int $default = null): ?int
34 | {
35 | if (!$this->has($key)) {
36 | return $default;
37 | }
38 |
39 | return $this->getInt($key, $default ?? 0);
40 | }
41 |
42 | public function getFloat(string $key, float $default = 0.0): float
43 | {
44 | return float($this->get($key, $default));
45 | }
46 |
47 | public function getFloatNull(string $key, ?float $default = null): ?float
48 | {
49 | if (!$this->has($key)) {
50 | return $default;
51 | }
52 |
53 | return $this->getFloat($key, $default ?? 0.0);
54 | }
55 |
56 | public function getString(string $key, string $default = ''): string
57 | {
58 | return (string)$this->get($key, $default);
59 | }
60 |
61 | public function getStringNull(string $key, ?string $default = null): ?string
62 | {
63 | if (!$this->has($key)) {
64 | return $default;
65 | }
66 |
67 | return $this->getString($key, $default ?? '');
68 | }
69 |
70 | public function getArray(string $key, array $default = []): array
71 | {
72 | return (array)$this->get($key, $default);
73 | }
74 |
75 | public function getArrayNull(string $key, ?array $default = null): ?array
76 | {
77 | if (!$this->has($key)) {
78 | return $default;
79 | }
80 |
81 | return $this->getArray($key, $default ?? []);
82 | }
83 |
84 | /**
85 | * @SuppressWarnings(PHPMD.BooleanGetMethodName)
86 | */
87 | public function getBool(string $key, bool $default = false): bool
88 | {
89 | return bool($this->get($key, $default));
90 | }
91 |
92 | /**
93 | * @SuppressWarnings(PHPMD.BooleanGetMethodName)
94 | */
95 | public function getBoolNull(string $key, ?bool $default = null): ?bool
96 | {
97 | if (!$this->has($key)) {
98 | return $default;
99 | }
100 |
101 | return $this->getBool($key, $default ?? false);
102 | }
103 |
104 | /**
105 | * @psalm-suppress UnsafeInstantiation
106 | * @psalm-suppress ImplicitToStringCast
107 | */
108 | public function getSelf(string $key, array|self $default = []): self
109 | {
110 | if ($this->has($key) && $this->get($key) !== null) {
111 | // @phpstan-ignore-next-line
112 | return new static((array)$this->get($key, $default));
113 | }
114 |
115 | // @phpstan-ignore-next-line
116 | return new static($default);
117 | }
118 |
119 | /**
120 | * @psalm-suppress UnsafeInstantiation
121 | * @psalm-suppress ImplicitToStringCast
122 | */
123 | public function getSelfNull(string $key, array|self|null $default = null): ?self
124 | {
125 | if (!$this->has($key)) {
126 | // @phpstan-ignore-next-line
127 | return $default !== null ? new static($default) : null;
128 | }
129 |
130 | return $this->getSelf($key, $default ?? []);
131 | }
132 |
133 | public function findInt(string $key, int $default = 0): int
134 | {
135 | return int($this->find($key, $default));
136 | }
137 |
138 | public function findFloat(string $key, float $default = 0.0): float
139 | {
140 | return float($this->find($key, $default));
141 | }
142 |
143 | public function findString(string $key, string $default = ''): string
144 | {
145 | return (string)$this->find($key, $default);
146 | }
147 |
148 | public function findArray(string $key, array $default = []): array
149 | {
150 | return (array)$this->find($key, $default);
151 | }
152 |
153 | public function findBool(string $key, bool $default = false): bool
154 | {
155 | return bool($this->find($key, $default));
156 | }
157 |
158 | /**
159 | * @psalm-suppress UnsafeInstantiation
160 | */
161 | public function findSelf(string $key, array $default = []): self
162 | {
163 | if ($this->has($key) && $this->get($key) !== null) {
164 | // @phpstan-ignore-next-line
165 | return new static((array)$this->find($key, $default));
166 | }
167 |
168 | // @phpstan-ignore-next-line
169 | return new static($default);
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/AbstractData.php:
--------------------------------------------------------------------------------
1 | setFlags(\ArrayObject::ARRAY_AS_PROPS);
51 |
52 | if (\is_string($data) && $data !== '' && \file_exists($data)) {
53 | $data = self::readFile($data);
54 | }
55 |
56 | if (\is_string($data)) {
57 | $data = $this->decode($data);
58 | }
59 |
60 | /** @psalm-suppress PossiblyInvalidArgument */
61 | parent::__construct(($data !== false && $data !== null) && \count($data) > 0 ? (array)$data : []);
62 | }
63 |
64 | /**
65 | * Magic method to convert the data to a string
66 | * Returns a serialized version of the data contained in
67 | * the data object using serialize().
68 | */
69 | public function __toString(): string
70 | {
71 | return $this->write();
72 | }
73 |
74 | /**
75 | * Checks if the given key is present.
76 | * @param string $name The key to check
77 | */
78 | public function has(string $name): bool
79 | {
80 | return $this->offsetExists($name);
81 | }
82 |
83 | /**
84 | * Get a value from the data given its key.
85 | * @param string $key The key used to fetch the data
86 | * @param null|mixed $default The default value
87 | * @param null|mixed $filter Filter returned value
88 | */
89 | public function get(string $key, mixed $default = null, mixed $filter = null): mixed
90 | {
91 | self::checkDeprecatedFilter('get', $filter);
92 |
93 | $result = $default;
94 | if ($this->has($key)) {
95 | $result = $this->offsetGet($key);
96 | }
97 |
98 | return self::filter($result, $filter);
99 | }
100 |
101 | /**
102 | * Set a value in the data.
103 | * @param string $pathKey The key used to set the value
104 | * @param mixed $value The value to set
105 | * @param string $separator The separator to use when searching for sub keys. Default is '.'
106 | *
107 | * @psalm-suppress UnsafeInstantiation
108 | */
109 | public function set(string $pathKey, mixed $value, string $separator = '.'): self
110 | {
111 | if (\str_contains($pathKey, $separator) && $separator !== '') {
112 | $keys = \explode($separator, $pathKey);
113 | } else {
114 | $keys = [$pathKey];
115 | }
116 |
117 | $arrayCopy = $this->getArrayCopy();
118 | self::setNestedValue($arrayCopy, $keys, $value);
119 |
120 | // @phpstan-ignore-next-line
121 | return new static($arrayCopy);
122 | }
123 |
124 | /**
125 | * Remove a value from the data.
126 | * @param string $name The key of the data to remove
127 | */
128 | public function remove(string $name): static
129 | {
130 | if ($this->has($name)) {
131 | $this->offsetUnset($name);
132 | }
133 |
134 | return $this;
135 | }
136 |
137 | /**
138 | * Encode an array or an object in INI format.
139 | */
140 | public function write(): string
141 | {
142 | return $this->encode($this->getArrayCopy());
143 | }
144 |
145 | /**
146 | * Find a key in the data recursively
147 | * This method finds the given key, searching also in any array or
148 | * object that's nested under the current data object.
149 | * Example: $data->find('parent-key.sub-key.sub-sub-key');.
150 | * @param string $key The key to search for. Can be composed using $separator as the key/su-bkey separator
151 | * @param null|mixed $default The default value
152 | * @param null|mixed $filter Filter returned value
153 | * @param string $separator The separator to use when searching for sub keys. Default is '.'
154 | */
155 | public function find(string $key, mixed $default = null, mixed $filter = null, string $separator = '.'): mixed
156 | {
157 | self::checkDeprecatedFilter('find', $filter);
158 |
159 | $value = $this->get($key);
160 |
161 | // check if key exists in array
162 | if ($value !== null) {
163 | return self::filter($value, $filter);
164 | }
165 |
166 | // explode search key and init search data
167 | if ($separator === '') {
168 | throw new Exception("Separator can't be empty");
169 | }
170 |
171 | $parts = \explode($separator, $key);
172 | $data = $this;
173 |
174 | foreach ($parts as $part) {
175 | // handle ArrayObject and Array
176 | if ($data instanceof \ArrayObject && $data[$part] !== null) {
177 | $data = $data[$part];
178 | continue;
179 | }
180 |
181 | if (\is_array($data) && isset($data[$part])) {
182 | $data = $data[$part];
183 | continue;
184 | }
185 |
186 | // Handle object
187 | if (\is_object($data) && \property_exists($data, $part)) {
188 | /** @phpstan-ignore-next-line */
189 | $data = $data->{$part};
190 | continue;
191 | }
192 |
193 | return self::filter($default, $filter);
194 | }
195 |
196 | // return existing value
197 | return self::filter($data, $filter);
198 | }
199 |
200 | /**
201 | * Find a value also in nested arrays/objects.
202 | * @param mixed $needle The value to search for
203 | */
204 | public function search(mixed $needle): bool|float|int|string|null
205 | {
206 | $aIterator = new \RecursiveArrayIterator($this->getArrayCopy());
207 | $iterator = new \RecursiveIteratorIterator($aIterator);
208 |
209 | while ($iterator->valid()) {
210 | $iterator->current();
211 |
212 | if ($iterator->current() === $needle) {
213 | return $aIterator->key();
214 | }
215 |
216 | $iterator->next();
217 | }
218 |
219 | return false;
220 | }
221 |
222 | /**
223 | * Return flattened array copy. Keys are NOT preserved.
224 | */
225 | public function flattenRecursive(): array
226 | {
227 | $flat = [];
228 |
229 | foreach (new \RecursiveIteratorIterator(new \RecursiveArrayIterator($this->getArrayCopy())) as $value) {
230 | $flat[] = $value;
231 | }
232 |
233 | return $flat;
234 | }
235 |
236 | /**
237 | * @param int|string $key
238 | * @return null|mixed
239 | * @phpstan-ignore-next-line
240 | */
241 | public function offsetGet($key): mixed
242 | {
243 | if (!\property_exists($this, (string)$key)) {
244 | return null;
245 | }
246 |
247 | return parent::offsetGet($key);
248 | }
249 |
250 | /**
251 | * Compare value by key with something.
252 | * @SuppressWarnings(PHPMD.ShortMethodName)
253 | */
254 | public function is(string $key, mixed $compareWith = true, bool $strictMode = false): bool
255 | {
256 | if (!\str_contains($key, '.')) {
257 | $value = $this->get($key);
258 | } else {
259 | $value = $this->find($key);
260 | }
261 |
262 | if ($strictMode) {
263 | return $value === $compareWith;
264 | }
265 |
266 | /** @noinspection TypeUnsafeComparisonInspection */
267 | /** @phpstan-ignore equal.notAllowed */
268 | return $value == $compareWith;
269 | }
270 |
271 | public function getSchema(): array
272 | {
273 | return Arr::getSchema($this->getArrayCopy());
274 | }
275 |
276 | /**
277 | * Filter value before return.
278 | */
279 | protected static function filter(mixed $value, mixed $filter): mixed
280 | {
281 | if ($filter !== null) {
282 | $value = Filter::_($value, $filter);
283 | }
284 |
285 | return $value;
286 | }
287 |
288 | protected static function readFile(string $filePath): bool|string
289 | {
290 | $contents = false;
291 |
292 | $realPath = \realpath($filePath);
293 | if ($realPath !== false) {
294 | $contents = \file_get_contents($realPath);
295 | }
296 |
297 | return $contents;
298 | }
299 |
300 | /**
301 | * Check is array is nested.
302 | */
303 | protected static function isMulti(array $array): bool
304 | {
305 | $arrayCount = \array_filter($array, '\is_array');
306 |
307 | return \count($arrayCount) > 0;
308 | }
309 |
310 | /**
311 | * @psalm-suppress PossiblyNullArrayOffset
312 | */
313 | private static function setNestedValue(array &$array, array $keys, mixed $value): void
314 | {
315 | $key = \array_shift($keys);
316 |
317 | if (\count($keys) === 0) {
318 | $array[$key] = $value;
319 | } else {
320 | if (!isset($array[$key]) || !\is_array($array[$key])) {
321 | $array[$key] = [];
322 | }
323 |
324 | self::setNestedValue($array[$key], $keys, $value);
325 | }
326 | }
327 |
328 | private static function checkDeprecatedFilter(string $prefix, mixed $filter): void
329 | {
330 | if (!\is_string($filter)) {
331 | return;
332 | }
333 |
334 | if (\in_array($filter, ['bool', 'int', 'float', 'string', 'array', 'arr'], true)) {
335 | if ($filter === 'arr') {
336 | $filter = 'array';
337 | }
338 |
339 | /** @psalm-suppress RedundantFunctionCall */
340 | $methodName = $prefix . \ucfirst(\strtolower($filter));
341 | @\trigger_error(
342 | "Instead of filter=\"{$filter}\", please use `\$data->{$methodName}(\$key, \$default)` method",
343 | \E_USER_DEPRECATED,
344 | );
345 | }
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JBZoo / Data
2 |
3 | [](https://github.com/JBZoo/Data/actions/workflows/main.yml?query=branch%3Amaster) [](https://coveralls.io/github/JBZoo/Data?branch=master) [](https://shepherd.dev/github/JBZoo/Data) [](https://shepherd.dev/github/JBZoo/Data) [](https://www.codefactor.io/repository/github/jbzoo/data/issues)
4 | [](https://packagist.org/packages/jbzoo/data/) [](https://packagist.org/packages/jbzoo/data/stats) [](https://packagist.org/packages/jbzoo/data/dependents?order_by=downloads) [](https://github.com/JBZoo/Data/blob/master/LICENSE)
5 |
6 |
7 | An extended version of the [ArrayObject](http://php.net/manual/en/class.arrayobject.php) object for working with system settings or just for working with data arrays.
8 |
9 | It provides a short syntax for daily routine, eliminates common mistakes. Allows you to work with various line and file formats - JSON, Yml, Ini, PHP arrays and simple objects.
10 |
11 |
12 | * [Installation](#installation)
13 | * [Usage](#usage)
14 | * [Comparison with pure PHP](#comparison-with-pure-php)
15 | * [Know your data](#know-your-data)
16 | * [Methods](#methods)
17 | * [Filter values (required JBZoo/Utils)](#filter-values-required-jbzooutils)
18 | * [Utility methods](#utility-methods)
19 | * [Export to pretty-print format](#export-to-pretty-print-format)
20 | * [Summary benchmark info (execution time) PHP v8.2+](#summary-benchmark-info-execution-time-php-v82)
21 | * [Unit tests and check code style](#unit-tests-and-check-code-style)
22 | * [License](#license)
23 | * [See Also](#see-also)
24 |
25 |
26 |
27 | ## Installation
28 | ```sh
29 | composer require jbzoo/data
30 | ```
31 |
32 | ## Usage
33 |
34 | ### Comparison with pure PHP
35 |
36 | --------------------------------------------------------------------------------
37 | | Action | JBZoo/Data | Pure PHP way |
38 | |:-------------------------------------|:------------------------------------------------|:-----------------------------------------|
39 | | Create | `$d = data($someData)` | `$ar = [/* ... */];` |
40 | | Supported formats | Array, Object, ArrayObject, JSON, INI, Yml | Array |
41 | | Load form file | *.php, *.ini, *.yml, *.json, serialized | - |
42 | | Get value or default | `$d->get('key', 42)` | `$ar['key'] ?? 42` |
43 | | Get undefined #1 | `$d->get('undefined')` (no any notice) | `$ar['undefined'] ?? null` |
44 | | Get undefined #2 | `$d->find('undefined')` | `$ar['und'] ?? null` |
45 | | Get undefined #3 | `$d->undefined === null` (no any notice) | - |
46 | | Get undefined #4 | `$d['undefined'] === null` (no any notice) | - |
47 | | Get undefined #5 | `$d['undef']['undef'] === null` (no any notice) | - |
48 | | Comparing #1 | `$d->get('key') === $someVar` | `$ar['key'] === $someVar` |
49 | | Comparing #2 | `$d->is('key', $someVar)` | - |
50 | | Comparing #3 | `$d->is('key', $someVar, true)` (strict) | - |
51 | | Like array | `$d['key']` | `$ar['key']` |
52 | | Like object #1 | `$d->key` | - |
53 | | Like object #2 | `$d->get('key')` | - |
54 | | Like object #3 | `$d->find('key')` | - |
55 | | Like object #4 | `$d->offsetGet('key')` | - |
56 | | Isset #1 | `isset($d['key'])` | `isset($ar['key'])` |
57 | | Isset #2 | `isset($d->key)` | `array_key_exists('key', $ar)` |
58 | | Isset #3 | `$d->has('key')` | - |
59 | | Nested key #1 | `$d->find('inner.inner.prop', $default)` | `$ar['inner']['inner']['prop']` (error?) |
60 | | Nested key #2 | `$d->inner['inner']['prop']` | - |
61 | | Nested key #3 | `$d['inner']['inner']['prop']` | - |
62 | | Export to Serialized | `echo data([/* ... */])` | `echo serialize([/* ... */])` |
63 | | Export to JSON | `echo (json([/* ... */]))` (readable) | `echo json_encode([/* ... */])` |
64 | | Export to Yml | `echo yml([/* ... */])` (readable) | - |
65 | | Export to Ini | `echo ini([/* ... */])` (readable) | - |
66 | | Export to PHP Code | `echo phpArray([/* ... */])` (readable) | - |
67 | | JSON | **+** | - |
68 | | Filters | **+** | - |
69 | | Search | **+** | - |
70 | | Flatten Recursive | **+** | - |
71 | | Set Value | `$d['value'] = 42` | $ar['value'] = 42 |
72 | | Set Nested Value | `$d->set('q.w.e.r.t.y') = 42` | $ar['q']['w']['e']['r']['t']['y'] = 42 |
73 | | Set Nested Value (if it's undefined) | `$d->set('q.w.e.r.t.y') = 42` | PHP Notice errors... |
74 |
75 |
76 | ### Know your data
77 |
78 | ```php
79 | $json = json('{ "some": "thing", "number": 42 }');
80 | dump($json->getSchema();
81 | // [
82 | // "some" => "string",
83 | // "number" => "int"
84 | // ]
85 |
86 | ```
87 |
88 |
89 | #### Methods
90 |
91 | ```php
92 | use function JBZoo\Data\data;
93 | use function JBZoo\Data\ini;
94 | use function JBZoo\Data\json;
95 | use function JBZoo\Data\phpArray;
96 | use function JBZoo\Data\yml;
97 |
98 | $config = data([/* Assoc Array */]); // Any PHP-array or simple object, serialized data
99 | $config = ini('./configs/some.ini'); // Load configs from ini file (or string, or simple array)
100 | $config = yml('./configs/some.yml'); // Yml (or string, or simple array). Parsed with Symfony/Yaml Component.
101 | $config = json('./configs/some.json'); // JSON File (or string, or simple array)
102 | $config = phpArray('./configs/some.php'); // PHP-file that must return array
103 |
104 | // Read
105 | $config->get('key', 42); // Returns value if it exists oR returns default value
106 | $config['key']; // As regular array
107 | $config->key; // As regular object
108 |
109 | // Read nested values without PHP errors
110 | $config->find('deep.config.key', 42); // Gets `$config['very']['deep']['config']['key']` OR returns default value
111 |
112 | // Write
113 | $config->set('key', 42);
114 | $config['key'] = 42;
115 | $config->key = 42;
116 |
117 | // Isset
118 | $config->has('key');
119 | isset($config['key']);
120 | isset($config->key);
121 |
122 | // Unset
123 | $config->remove('key');
124 | unset($config['key']);
125 | unset($config->key);
126 |
127 | ```
128 |
129 |
130 | #### Filter values (required JBZoo/Utils)
131 |
132 | List of filters - [JBZoo/Utils/Filter](https://github.com/JBZoo/Utils/blob/master/src/Filter.php)
133 | * `bool` - Converts many english words that equate to true or false to boolean.
134 | * `int` - Smart converting to integer
135 | * `float` - Smart converting to float
136 | * `digits` - Leaves only "0-9"
137 | * `alpha` - Leaves only "a-zA-Z"
138 | * `alphanum` - Combination of `digits` and `alpha`
139 | * `base64` - Returns only chars which are compatible with base64
140 | * `path` - Clean FS path
141 | * `trim` - Extend trim
142 | * `arr` - Converting to array
143 | * `cmd` - Cleanup system command (CLI)
144 | * `email` - Returns cleaned up email or null
145 | * `strip` - Strip tags
146 | * `alias` - Sluggify
147 | * `low` - String to lower (uses mbstring or symfony polyfill)
148 | * `up` - String to upper (uses mbstring or symfony polyfill)
149 | * `clean` - Returns safe string
150 | * `html` - HTML escaping
151 | * `xml` - XML escaping
152 | * `esc` - Escape chars for UTF-8
153 | * `function($value) { return $value; }` - Your custom callback function
154 |
155 |
156 | ```php
157 | $config->get('key', 42, 'int'); // Smart converting to integer
158 | $config->find('key', 42, 'float'); // To float
159 | $config->find('no', 'yes', 'bool'); // Smart converting popular word to boolean value
160 | $config->get('key', 42, 'strip, trim'); // Chain of filters
161 |
162 | // Your custom handler
163 | $config->get('key', 42, function($value) {
164 | return (float)str_replace(',', '.', $value);
165 | });
166 | ```
167 |
168 |
169 | #### Utility methods
170 | ```php
171 | $config->search($needle); // Find a value also in nested arrays/objects
172 | $config->flattenRecursive(); // Return flattened array copy. Keys are NOT preserved.
173 | ```
174 |
175 | #### Export to pretty-print format
176 | ```php
177 | echo $config;
178 |
179 | $result = '' . $config;
180 | $result = (string)$config;
181 | $result = $config->__toString();
182 | ```
183 |
184 | Example of serializing the `JSON` object
185 | ```json
186 | {
187 | "empty": "",
188 | "zero": "0",
189 | "string": " ",
190 | "tag": "Google.com<\/a>",
191 | "array1": {
192 | "0": "1",
193 | "1": "2"
194 | },
195 | "section": {
196 | "array2": {
197 | "0": "1",
198 | "12": "2",
199 | "3": "3"
200 | }
201 | },
202 | "section.nested": {
203 | "array3": {
204 | "00": "0",
205 | "01": "1"
206 | }
207 | }
208 | }
209 | ```
210 |
211 | Example of serializing the `PHPArray` object
212 | ```php
213 | '',
217 | 'zero' => '0',
218 | 'string' => ' ',
219 | 'tag' => 'Google.com',
220 | 'array1' => array(
221 | 0 => '1',
222 | 1 => '2',
223 | ),
224 | 'section' => array(
225 | 'array2' => array(
226 | 0 => '1',
227 | 12 => '2',
228 | 3 => '3',
229 | ),
230 | ),
231 | 'section.nested' => array(
232 | 'array3' => array(
233 | '00' => '0',
234 | '01' => '1',
235 | ),
236 | ),
237 | );
238 | ```
239 |
240 | Example of serializing the `Yml` object
241 | ```yml
242 | empty: ''
243 | zero: '0'
244 | string: ' '
245 | tag: 'Google.com'
246 | array1:
247 | - '1'
248 | - '2'
249 | section:
250 | array2: { 0: '1', 12: '2', 3: '3' }
251 | section.nested:
252 | array3: ['0', '1']
253 | ```
254 |
255 | Example of serializing the `Ini` object
256 | ```ini
257 | empty = ""
258 | zero = "0"
259 | string = " "
260 | tag = "Google.com"
261 | array1[0] = "1"
262 | array1[1] = "2"
263 |
264 | [section]
265 | array2[0] = "1"
266 | array2[12] = "2"
267 | array2[3] = "3"
268 |
269 | [section.nested]
270 | array3[00] = "0"
271 | array3[01] = "1"
272 | ```
273 |
274 | Example of serializing the `Data` object
275 | ```
276 | a:7:{s:5:"empty";s:0:"";s:4:"zero";s:1:"0";s:6:"string";s:1:" ";s:3:"tag";s:42:"Google.com";s:6:"array1";a:2:{i:0;s:1:"1";i:1;s:1:"2";}s:7:"section";a:1:{s:6:"array2";a:3:{i:0;s:1:"1";i:12;s:1:"2";i:3;s:1:"3";}}s:14:"section.nested";a:1:{s:6:"array3";a:2:{s:2:"00";s:1:"0";s:2:"01";s:1:"1";}}}
277 | ```
278 |
279 | ## Summary benchmark info (execution time) PHP v8.2+
280 | All benchmark tests are executing without xdebug and with a huge random array and 100.000 iterations.
281 |
282 | Benchmark tests based on the tool [phpbench/phpbench](https://github.com/phpbench/phpbench). See details [here](tests/phpbench).
283 |
284 | Please, pay attention - `1μs = 1/1.000.000 of second!`
285 |
286 | **benchmark: CreateObject**
287 | subject | groups | its | revs | mean | stdev | rstdev | mem_real | diff
288 | --- | --- | --- | --- | --- | --- | --- | --- | ---
289 | benchArrayObjectOrig | Native,ArrayObject | 3 | 100000 | 7.30μs | 0.01μs | 0.18% | 8,388,608b | 1.00x
290 | benchArrayObjectExtOrig | Native,ArrayObject,Extended | 3 | 100000 | 7.43μs | 0.05μs | 0.66% | 8,388,608b | 1.02x
291 | benchJson | JSON | 3 | 100000 | 7.55μs | 0.01μs | 0.15% | 8,388,608b | 1.03x
292 | benchIni | Ini | 3 | 100000 | 7.55μs | 0.01μs | 0.15% | 8,388,608b | 1.03x
293 | benchData | Data | 3 | 100000 | 7.57μs | 0.03μs | 0.41% | 8,388,608b | 1.04x
294 | benchIniFunc | Ini,Func | 3 | 100000 | 7.62μs | 0.01μs | 0.10% | 8,388,608b | 1.04x
295 | benchDataFunc | Data,Func | 3 | 100000 | 7.63μs | 0.01μs | 0.19% | 8,388,608b | 1.05x
296 | benchYml | Yml | 3 | 100000 | 7.63μs | 0.10μs | 1.36% | 8,388,608b | 1.05x
297 | benchJsonFunc | JSON,Func | 3 | 100000 | 7.64μs | 0.01μs | 0.11% | 8,388,608b | 1.05x
298 | benchPhpArray | PhpArray | 3 | 100000 | 7.65μs | 0.03μs | 0.44% | 8,388,608b | 1.05x
299 | benchYmlFunc | Yml,Func | 3 | 100000 | 7.70μs | 0.05μs | 0.60% | 8,388,608b | 1.05x
300 | benchPhpArrayFunc | PhpArray,Func | 3 | 100000 | 7.75μs | 0.06μs | 0.72% | 8,388,608b | 1.06x
301 |
302 |
303 | **benchmark: GetUndefinedValue**
304 | subject | groups | its | revs | mean | stdev | rstdev | mem_real | diff
305 | --- | --- | --- | --- | --- | --- | --- | --- | ---
306 | benchArrayIsset | Native,Array,Undefined | 3 | 1000000 | 0.04μs | 0.00μs | 1.48% | 8,388,608b | 1.00x
307 | benchDataOffsetGet | Data,Undefined | 3 | 1000000 | 0.11μs | 0.00μs | 0.41% | 8,388,608b | 2.88x
308 | benchDataGet | Data,Undefined | 3 | 1000000 | 0.14μs | 0.00μs | 0.39% | 8,388,608b | 3.56x
309 | benchDataArray | Data,Undefined | 3 | 1000000 | 0.14μs | 0.00μs | 0.08% | 8,388,608b | 3.72x
310 | benchDataArrow | Data,Undefined | 3 | 1000000 | 0.15μs | 0.00μs | 0.34% | 8,388,608b | 3.86x
311 | benchArrayRegularMuted | Native,Array,Undefined | 3 | 1000000 | 0.19μs | 0.00μs | 0.04% | 8,388,608b | 4.99x
312 | benchDataFind | Data,Undefined | 3 | 1000000 | 0.37μs | 0.00μs | 0.11% | 8,388,608b | 9.69x
313 | benchDataFindInner | Data,Undefined | 3 | 1000000 | 0.41μs | 0.00μs | 0.14% | 8,388,608b | 10.86x
314 |
315 |
316 | **benchmark: GetValue**
317 | subject | groups | its | revs | mean | stdev | rstdev | mem_real | diff
318 | --- | --- | --- | --- | --- | --- | --- | --- | ---
319 | benchArrayRegular | Native,Array | 3 | 1000000 | 0.04μs | 0.00μs | 5.02% | 8,388,608b | 1.00x
320 | benchArrayRegularMuted | Native,Array | 3 | 1000000 | 0.04μs | 0.00μs | 1.40% | 8,388,608b | 1.06x
321 | benchArrayIsset | Native,Array | 3 | 1000000 | 0.04μs | 0.00μs | 2.04% | 8,388,608b | 1.07x
322 | benchArrayObjectArray | Native,ArrayObject | 3 | 1000000 | 0.05μs | 0.00μs | 1.07% | 8,388,608b | 1.14x
323 | benchArrayObjectArrayExt | Native,ArrayObject,Extended | 3 | 1000000 | 0.05μs | 0.00μs | 0.24% | 8,388,608b | 1.19x
324 | benchArrayObjectOffsetGet | Native,ArrayObject | 3 | 1000000 | 0.07μs | 0.00μs | 1.35% | 8,388,608b | 1.77x
325 | benchArrayObjectExtOffsetGet | Native,ArrayObject,Extended | 3 | 1000000 | 0.08μs | 0.00μs | 0.23% | 8,388,608b | 1.86x
326 | benchDataOffsetGet | Data | 3 | 1000000 | 0.16μs | 0.00μs | 0.28% | 8,388,608b | 4.01x
327 | benchDataArray | Data | 3 | 1000000 | 0.20μs | 0.00μs | 0.17% | 8,388,608b | 4.96x
328 | benchDataArrow | Data | 3 | 1000000 | 0.21μs | 0.00μs | 0.21% | 8,388,608b | 5.07x
329 | benchDataGet | Data | 3 | 1000000 | 0.28μs | 0.00μs | 0.21% | 8,388,608b | 6.95x
330 | benchDataFind | Data | 3 | 1000000 | 0.35μs | 0.00μs | 0.65% | 8,388,608b | 8.52x
331 |
332 | **benchmark: GetValueInner**
333 | subject | groups | its | revs | mean | stdev | rstdev | mem_real | diff
334 | --- | --- | --- | --- | --- | --- | --- | --- | ---
335 | benchArrayRegular | Native,Array | 3 | 1000000 | 0.05μs | 0.00μs | 0.23% | 8,388,608b | 1.00x
336 | benchArrayRegularMuted | Native,Array | 3 | 1000000 | 0.06μs | 0.00μs | 0.86% | 8,388,608b | 1.06x
337 | benchArrayIsset | Native,Array | 3 | 1000000 | 0.06μs | 0.00μs | 0.27% | 8,388,608b | 1.08x
338 | benchArrayObjectArrayExt | Native,ArrayObject,Extended | 3 | 1000000 | 0.06μs | 0.00μs | 0.76% | 8,388,608b | 1.14x
339 | benchArrayObjectArray | Native,ArrayObject | 3 | 1000000 | 0.07μs | 0.00μs | 1.39% | 8,388,608b | 1.22x
340 | benchDataFind | Data | 3 | 1000000 | 0.81μs | 0.01μs | 1.06% | 8,388,608b | 15.22x
341 |
342 |
343 | ## Unit tests and check code style
344 | ```sh
345 | make update
346 | make test-all
347 | ```
348 |
349 |
350 | ## License
351 |
352 | MIT
353 |
354 |
355 | ## See Also
356 |
357 | - [CI-Report-Converter](https://github.com/JBZoo/CI-Report-Converter) - Converting different error reports for deep compatibility with popular CI systems.
358 | - [Composer-Diff](https://github.com/JBZoo/Composer-Diff) - See what packages have changed after `composer update`.
359 | - [Composer-Graph](https://github.com/JBZoo/Composer-Graph) - Dependency graph visualization of composer.json based on mermaid-js.
360 | - [Mermaid-PHP](https://github.com/JBZoo/Mermaid-PHP) - Generate diagrams and flowcharts with the help of the mermaid script language.
361 | - [Utils](https://github.com/JBZoo/Utils) - Collection of useful PHP functions, mini-classes, and snippets for every day.
362 | - [Image](https://github.com/JBZoo/Image) - Package provides object-oriented way to manipulate with images as simple as possible.
363 | - [Retry](https://github.com/JBZoo/Retry) - Tiny PHP library providing retry/backoff functionality with multiple backoff strategies and jitter support.
364 | - [SimpleTypes](https://github.com/JBZoo/SimpleTypes) - Converting any values and measures - money, weight, exchange rates, length, ...
365 |
--------------------------------------------------------------------------------