├── 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 | [![CI](https://github.com/JBZoo/Data/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/JBZoo/Data/actions/workflows/main.yml?query=branch%3Amaster) [![Coverage Status](https://coveralls.io/repos/github/JBZoo/Data/badge.svg?branch=master)](https://coveralls.io/github/JBZoo/Data?branch=master) [![Psalm Coverage](https://shepherd.dev/github/JBZoo/Data/coverage.svg)](https://shepherd.dev/github/JBZoo/Data) [![Psalm Level](https://shepherd.dev/github/JBZoo/Data/level.svg)](https://shepherd.dev/github/JBZoo/Data) [![CodeFactor](https://www.codefactor.io/repository/github/jbzoo/data/badge)](https://www.codefactor.io/repository/github/jbzoo/data/issues) 4 | [![Stable Version](https://poser.pugx.org/jbzoo/data/version)](https://packagist.org/packages/jbzoo/data/) [![Total Downloads](https://poser.pugx.org/jbzoo/data/downloads)](https://packagist.org/packages/jbzoo/data/stats) [![Dependents](https://poser.pugx.org/jbzoo/data/dependents)](https://packagist.org/packages/jbzoo/data/dependents?order_by=downloads) [![GitHub License](https://img.shields.io/github/license/jbzoo/data)](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 | --------------------------------------------------------------------------------