├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── composer.json └── src ├── Arrayy.php ├── ArrayyIterator.php ├── ArrayyMeta.php ├── ArrayyRewindableExtendedGenerator.php ├── ArrayyRewindableGenerator.php ├── ArrayyStrict.php ├── Collection ├── AbstractCollection.php ├── Collection.php └── CollectionInterface.php ├── Create.php ├── Mapper └── Json.php ├── StaticArrayy.php ├── Type ├── ArrayCollection.php ├── BoolArrayCollection.php ├── BoolCollection.php ├── CallableCollection.php ├── DetectFirstValueTypeCollection.php ├── FloatArrayCollection.php ├── FloatCollection.php ├── FloatIntArrayCollection.php ├── FloatIntCollection.php ├── InstanceCollection.php ├── InstancesCollection.php ├── IntArrayCollection.php ├── IntCollection.php ├── JsonSerializableCollection.php ├── MixedCollection.php ├── NonEmptyStringCollection.php ├── NumericCollection.php ├── NumericStringCollection.php ├── ObjectCollection.php ├── ResourceCollection.php ├── ScalarCollection.php ├── StdClassCollection.php ├── StringArrayCollection.php ├── StringCollection.php └── TypeInterface.php └── TypeCheck ├── AbstractTypeCheck.php ├── TypeCheckArray.php ├── TypeCheckCallback.php ├── TypeCheckInterface.php ├── TypeCheckPhpDoc.php └── TypeCheckSimple.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 7.9.6 (2022-12-27) 4 | 5 | - "Json"-Mapper: support objects in "Arrayy"-collections 6 | 7 | ### 7.9.5 (2022-10-13) 8 | 9 | - try to optimize autocompletion for PhpStorm 10 | 11 | ### 7.9.4 (2022-09-06) 12 | 13 | - try to fix phpstan (1.8.4) reported issues 14 | 15 | ### 7.9.3 (2022-07-06) 16 | 17 | - "Collection" -> fix phpstan check for offset 18 | 19 | ### 7.9.2 (2022-07-05) 20 | 21 | - optimize some phpdocs (phpstan) 22 | - simplify some code 23 | 24 | ### 7.9.1 (2022-03-08) 25 | 26 | - "To people of Russia": There is a war in Ukraine right now. The forces of the Russian Federation are attacking civilians. 27 | - fix phpdocs for "chunk()" 28 | - try to optimize support for PhpStorm auto-completion 29 | 30 | ### 7.9.0 (2022-02-15) 31 | 32 | - remove "gitbook" from "dev-dependencies" 33 | - add "containsOnly()" 34 | - add "getBackwardsGenerator()" 35 | - add "reverseKeepIndex()" 36 | 37 | ### 7.8.14 (2021-12-21) 38 | 39 | - add more support for Generics + phpstan checks 40 | - fix PHP 8.1 compatibility -> thanks @frenchcomp 41 | 42 | ### 7.8.13 (2021-10-23) 43 | 44 | - add "NonEmptyString"-Collections (accepts "non-empty-string") 45 | - add "Numeric"-Collections (accepts "numeric" e.g.: 1, '1', 1.1, '1.1') 46 | - add "NumericString"-Collections (accepts "numeric" e.g.: '1', '1.1') 47 | 48 | ### 7.8.12 (2021-10-18) 49 | 50 | - fix "Float"-Collections (only accepts "float" now) 51 | - add "FloatInt"-Collections (accepts "float" and "int") 52 | 53 | ### 7.8.11 (2021-08-08) 54 | 55 | - fix fatal error from "ArrayyRewindableExtendedGenerator" 56 | 57 | ### 7.8.10 (2021-06-19) 58 | 59 | - add more support for Generics + phpstan checks 60 | 61 | ### 7.8.9 (2021-03-29) 62 | 63 | - fix code style 64 | - use Github Actions 65 | 66 | ### 7.8.8 (2021-03-09) 67 | 68 | - add more support for Generics + phpstan checks 69 | 70 | ### 7.8.7 (20201-01-30) 71 | 72 | - optimize Json Mapper with nested Arrayy elements 73 | 74 | ### 7.8.6 (2020-12-06) 75 | 76 | - add more support for Generics + phpstan checks 77 | 78 | ### 7.8.5 (2020-11-02) 79 | 80 | - use more Generators v3.1 81 | - add more support for Generics + phpstan checks 82 | - auto-generate the README 83 | 84 | ### 7.8.4 (2020-09-06) 85 | 86 | - fix some internal Generator usage 87 | - add more support for Generics + phpstan checks 88 | 89 | ### 7.8.3 (2020-08-22) 90 | 91 | - use more Generators v3 92 | - auto-generate the README 93 | - use property type check for "Arrayy->unshift(...)" 94 | 95 | ### 7.8.2 (2020-07-28) 96 | 97 | - use more Generators v2 98 | - auto-generate the README 99 | 100 | ### 7.8.1 (2020-07-15) 101 | 102 | - use more Generators 103 | - auto-generate the README 104 | 105 | ### 7.8.0 (2020-07-11) 106 | 107 | - search for PhpDoc @property in parent classes 108 | -> this is working for "Arrayy" classes and "ArrayyMeta" classes 109 | 110 | ### 7.7.0 (2020-07-05) 111 | 112 | - add "prependImmutable()" 113 | - add "appendImmutable()" 114 | - add prefix for "implode()" 115 | - use more Generators 116 | - add more tests 117 | 118 | ### 7.6.0 (2020-06-19) 119 | 120 | - fix "offsetGet()" usage 121 | -> now e.g. $a['foo'] will return a reference instead of a new variable, 122 | so that we can overwrite the key of an array like this $a['foo']['counter'] += 1 123 | 124 | ### 7.5.0 (2020-04-06) 125 | 126 | - add support for "A->get('*.key)" 127 | 128 | ### 7.4.0 (2020-03-22) 129 | 130 | - add support for JSON-Mappings 131 | 132 | ### 7.3.2 (2020-03-10) 133 | 134 | - add "JsonSerializable"-Collection 135 | 136 | ### 7.3.1 (2020-03-01) 137 | 138 | - add "Callable"-Collections 139 | - add "Object"-Collections 140 | - add "Resource"-Collections 141 | - add "Scalar"-Collections 142 | 143 | ### 7.3.0 (2020-02-23) 144 | 145 | - add & use "Arrayy->getGeneratorByReference()" 146 | - update "phpdocumentor/reflection-docblock" 147 | 148 | ### 7.2.0 (2020-01-31) 149 | 150 | - fix "Arrayy->offsetUnset()" -> unset of non array values + fix phpdoc 151 | - fix "Arrayy->merge*" -> now accepts also Arrayy elements in the array 152 | - update "Arrayy->has()" -> now you can check multiple keys 153 | - add "$key" for "Arrayy->clear()" 154 | - add "Arrayy->flatten()" 155 | - add "Arrayy->createFromArray()" 156 | 157 | ### 7.1.5 (2020-01-24) 158 | 159 | - fix check for "\Arrayy\collection()" 160 | 161 | ### 7.1.4 (2020-01-24) 162 | 163 | - use generics for "\Arrayy\array_first()" & "\Arrayy\array_last()" 164 | 165 | ### 7.1.3 (2020-01-06) 166 | 167 | - fix phpdoc from "toPermutation()" 168 | 169 | ### 7.1.2 (2020-01-04) 170 | 171 | - fix "@psalm-mutation-free" - via psalm 172 | 173 | ### 7.1.1 (2020-01-04) 174 | 175 | - fix generics support - via psalm 176 | 177 | ### 7.1.0 (2020-01-04) 178 | 179 | - more generics support - via psalm + phpstan 180 | - add some more "Immutable" versions of sort methods 181 | - fix "createFromObject()" -> will return static instead of self 182 | 183 | ### 7.0.3 (2019-12-30) 184 | 185 | - fix more phpdocs 186 | 187 | ### 7.0.2 (2019-12-30) 188 | 189 | - add "@psalm-mutation-free" - via psalm 190 | - fix more phpdocs 191 | 192 | ### 7.0.1 (2019-12-16) 193 | 194 | - more generics support - via phpstan (>= 0.12) 195 | 196 | ### 7.0.0 (2019-12-13) 197 | 198 | - normalize $closure (value => key instead of key => value) for "forAll()" 199 | - normalize $closure (value => key instead of key => value) for "partition()" 200 | - rename "forAll()" into "validate()" 201 | - use "iterator_count()" if possible 202 | - add "nth()" 203 | - add "toPermutation()" 204 | 205 | ### 6.1.1 206 | 207 | - fix phpstan (0.12) generic support 208 | 209 | ### 6.1.0 210 | 211 | - use phpstan (0.12) generic support 212 | - split "InstanceCollection" into "InstancesCollection" && "InstanceCollection" 213 | 214 | ### 6.0.0 215 | 216 | - instance of "InvalidArgumentException" we now use "TypeError" for type errors 217 | - add "Arrayy->checkPropertiesMismatch" 218 | - add pre-defined typified collections (TypeInterface) 219 | - merge type check from Arrayy & Collection 220 | - add "TypeCheckInterface" 221 | - fix type checks 222 | 223 | ### 5.15.0 224 | 225 | - fix serialization of "Arrayy" + "Property" (Property !== instance of ReflectionProperty anymore) 226 | - fix for php 7.4 (ArrayObject use __serialize + __unserialize) 227 | 228 | ### 5.14.2 (2019-10-06) 229 | 230 | - fix "Arrayy->keys()" -> use strict and non-strict comparision 231 | 232 | ### 5.14.1 (2019-09-16) 233 | 234 | - optimize "array_last"-polyfill (php < 7.3) 235 | 236 | ### 5.14.0 (2019-09-13) 237 | 238 | - add "Arrayy->diffKey" + "Arrayy->diffKeyAndValue" 239 | - add more test cases 240 | - update "phpdocumentor/reflection-docblock" 241 | 242 | ### 5.13.2 (2019-08-01) 243 | 244 | - fix "array_first()" + "array_last()" (move from global namespace into Arrayy) 245 | 246 | ### 5.13.1 (2019-07-19) 247 | 248 | - fix return type of "Arrayy->internalGetArray()" 249 | 250 | ### 5.13.0 (2019-07-19) 251 | 252 | - add Arrayy->moveElementToFirstPlace() 253 | - add Arrayy->moveElementToLastPlace() 254 | 255 | ### 5.12.1 (2019-07-03) 256 | 257 | - fix for php >= 7.3 258 | 259 | ### 5.12.0 (2019-07-03) 260 | 261 | - add new array key functions + most value functions 262 | 263 | -> "Arrayy->firstKey()", "Arrayy->lastKey()", "Arrayy->mostUsedValue()", "Arrayy->mostUsedValues()" 264 | 265 | ### 5.11.1 (2019-06-25) 266 | 267 | - Collection -> fix some string methods from the parent "Arrayy"-class 268 | 269 | ### 5.11.0 (2019-06-25) 270 | 271 | - AbstractCollection -> accept collections (self) as valid source + fix phpdoc 272 | 273 | ### 5.10.0 (2019-06-21) 274 | 275 | - add "keyExists()" / "delete()" / "pull()" 276 | 277 | ### 5.9.1 (2019-05-03) 278 | 279 | - "first()" / "last()" -> fix -> do not change the current array, if it's not needed 280 | 281 | ### 5.9.0 (2019-05-03) 282 | 283 | - "group()" / "sorter()" -> fix phpdoc 284 | - "keys()" / "values()" -> optimize generator usage 285 | - "replace()" -> fix immutable of the input 286 | - "sizeIs()" / "sizeIsLessThan()" / "sizeIsGreaterThan()" / "sizeIsBetween()" -> added 287 | - "invoke()" -> improve generator usage + fix phpdoc 288 | - "map()" -> allow to use the key, in the callable + additional parameter 289 | - "containsCaseInsensitive()" -> optimize for generator usage 290 | 291 | ### 5.8.1 (2019-04-30) 292 | - optimize performance from "Arrayy->unshift()" 293 | - optimize performance from "Arrayy->push()" 294 | 295 | ### 5.8.0 (2019-04-20) 296 | - add a simple "Collection" implementation + function alias \Arrayy\collection() 297 | - fix errors reported by phpstan (level 7) 298 | - improve performance (use "dot-notation" internally only if needed) 299 | - improve "dot-notation" handling for non "Arrayy" objects 300 | 301 | ### 5.7.1 (2019-04-18) 302 | - "AbstractCollection" -> optimize foreach usage 303 | - "AbstractCollection" -> fix merge && where methods 304 | 305 | ### 5.7.0 (2019-04-17) 306 | - optimize property check in the constructor 307 | - better support for PhpDoc @property checks 308 | - allow callable as input (Arrayy::createFromGeneratorFunction()) 309 | - add a abstract "Collection" implementation 310 | 311 | ### 5.6.3 (2019-01-11) 312 | - "ramsey/array_column" is not needed anymore 313 | - use autoloader also for the tests 314 | 315 | ### 5.6.2 (2019-01-02) 316 | - fix issue when requiring float types 317 | - update phpcs fixer config 318 | 319 | ### 5.6.1 (2018-12-20) 320 | - update "require-dev" 321 | - optimize the "constructor" 322 | - use the "JsonSerializable" interface 323 | - fix fallback for "this->shuffle()" 324 | 325 | ### 5.6.0 (2018-12-20) 326 | - use phpstan + fixes (level 5) 327 | - use phpcs fixer 328 | 329 | ### 5.5.0 (2018-12-07) 330 | - replace "UTF8" with "mbstring" 331 | -> Warning: is you need the "UTF8" class, please add it separately in you composer.json 332 | -> "voku/portable-utf8": "~5.0" 333 | 334 | ### 5.4.0 (2018-12-07) 335 | 336 | - fix "checkForMissingPropertiesInConstructor" with arrays 337 | -> new parameter in the constructor 338 | - fix internal "ArrayyIterator" handling 339 | 340 | ### 5.3.2 (2018-11-10) 341 | 342 | - use generators for for-each loops 343 | - add "Arrayy->getGenerator()" + tests 344 | 345 | ### 5.3.1 (2018-11-05) 346 | 347 | - test the tests via "infection" (Mutation Code Coverage: 91%) 348 | - optimize performance 349 | 350 | ### 5.3.0 (2018-11-03) 351 | 352 | - add "type checking for @property" 353 | 354 | ### 5.2.0 (2018-09-08) 355 | 356 | - add Arrayy->appendArrayValues() 357 | - fix usage of set() with nested dot-notation 358 | 359 | ### 5.1.0 (2018-06-08) 360 | 361 | - add Arrayy->fillWithDefaults() 362 | - fix usage of "count()" + COUNT_RECURSIVE if needed 363 | 364 | ### 5.0.0 (2017-12-23) 365 | 366 | - update "Portable UTF8" from v4 -> v5 367 | 368 | -> this is a breaking change without API-changes - but the requirement from 369 | "Portable UTF8" has been changed (it no longer requires all polyfills from Symfony) 370 | 371 | ### 4.0.0 (2017-11-14) 372 | 373 | - "php": ">=7.0" 374 | * drop support for PHP < 7.0 375 | * use "strict_types" 376 | 377 | ### 3.8.0 (2017-09-23) 378 | 379 | - add some pre- / append methods + tests 380 | 381 | ### 3.7.0 (2017-08-11) 382 | 383 | - add "Arrayy::createFromObjectVars()" 384 | - fix internal __toString() / Arrayy->implode() 385 | - fix in_array() usage for multidimensional array 386 | 387 | ### 3.6.0 (2017-05-09) 388 | 389 | - add flag-parameter for "Arrayy->filter()" + polyfill for old php versions (< 5.6 || HHVM) 390 | - add "Arrayy->countValues()"-method 391 | 392 | ### 3.5.1 (2017-04-11) 393 | 394 | - fix "offsetGet() must be compatible with that of ArrayAccess::offsetGet()" 395 | 396 | ### 3.5.0 (2017-04-10) 397 | 398 | - more information via "InvalidArgumentException" 399 | - re-use the "Arrayy->customSortKeys()"-method 400 | - add more "sort"-methods + tests 401 | 402 | ### 3.4.0 (2017-04-09) 403 | 404 | - overwrite "ArrayObject"-methods 405 | - dependency injection for the "Iterator" via __constructor 406 | - fix serialize() + unserialize() -> we will process the object now, not only the array in the object 407 | - add more tests 408 | 409 | ### 3.3.0 (2017-04-08) 410 | 411 | - add "Arrayy->changeKeyCase()" (with UTF-8 support) 412 | 413 | ### 3.2.1 (2017-04-07) 414 | 415 | - fix "StaticArrayy"-class -> return value from "repeat()" is always an instance of the "Arrayy"-class 416 | 417 | ### 3.2.0 (2017-04-01) 418 | 419 | - fix php-doc (for extended classes) 420 | - add "Array->uniqueKeepIndex()" 421 | - fix some more php-docs 422 | 423 | ### 3.1.2 424 | 425 | - fix "matches()" and "matchesAny()" with empty-arrays 426 | 427 | ### 3.1.1 428 | 429 | - fix usage of "isset() / array_key_exists()" and "array()$value / array($value)" 430 | 431 | ### 3.1.0 432 | 433 | - fix some bugs with the magic __set // __get 434 | - fix bug from Arrayy->get() 435 | 436 | ### 3.0.0 437 | 438 | - "Recursively return new Arrayy objects" | thx @brad-jones 439 | 440 | ### 2.2.9 (2016-12-16) 441 | 442 | - Apply fixes from StyleCI 443 | 444 | ### 2.2.8 (2016-12-16) 445 | 446 | - add "Arrayy->moveElement()" 447 | 448 | ### 2.2.6 (2016-12-11) 449 | 450 | - add "Arrayy->containsKeys()" 451 | - add "Arrayy->containsValues()" 452 | 453 | ### 2.2.4 (2016-11-05) 454 | 455 | - fix for PHP 5.3 456 | 457 | ### 2.2.3 (2016-11-04) 458 | 459 | - add Arrayy->divide() 460 | - add Arrayy->swap() 461 | - add Arrayy->stripEmpty() 462 | 463 | ### 2.2.2 (2016-08-12) 464 | 465 | - use new version of "portable-utf8" (3.0) 466 | 467 | ### 2.2.0 (2016-06-20) 468 | 469 | - add "containsCaseInsensitive()" 470 | - add "isEqual()" 471 | - add "isSequential()" 472 | 473 | ### 2.1.0 (2016-04-19) 474 | 475 | - add "Arrayy->diffRecursive()" 476 | 477 | ### 2.0.1 (2016-03-21) 478 | 479 | - use new "portable-utf8"-version 480 | 481 | ### 2.0.0 (2016-02-10) 482 | 483 | - fixed dot-notation 484 | - merged doublicate functions 485 | - use "Immutable & Mutable"-methods 486 | - use the "ArrayAccess"-Interface 487 | - try to fix for old php-versions 488 | 489 | ### 1.2.0 (2016-02-04) 490 | 491 | - add Arrayy->create() 492 | - add Arrayy->flip() 493 | - add Arrayy->reduce() | thx @formigone 494 | 495 | ### 1.1.1 (2016-01-31) 496 | 497 | - "Fixed Countable interface description" | thx @dvdmarchetti 498 | 499 | ### 1.1.0 (2016-01-31) 500 | 501 | - fixed Arrayy->mergePrependKeepIndex() 502 | - fixed Arrayy->mergeAppendKeepIndex() 503 | 504 | ### 1.0.5 (2016-01-30) 505 | 506 | - add Arrayy->getColumn() 507 | - use the "array_column()"-polyfill 508 | 509 | ### 1.0.4 (2016-01-30) 510 | 511 | - add Arrayy->randomWeighted() 512 | - add Arrayy->split() 513 | 514 | 515 | ### 1.0.3 (2016-01-29) 516 | 517 | - replace "self" with "static" 518 | 519 | ### 1.0.2 (2016-01-27) 520 | 521 | - add Arrayy->isMultiArray() 522 | - added some more documentation 523 | 524 | ### 1.0.1 (2016-01-27) 525 | 526 | - added some more doc's 527 | - fixed "Arrayy->random()" 528 | 529 | ### 1.0.0 (2016-01-26) 530 | 531 | - return a "Arrayy"-object 532 | - fixed "replaceValue()" 533 | - rename "replaceValue()" -> into "replaceOneValue()" 534 | - init 535 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 Lars Moelleken 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voku/arrayy", 3 | "description": "Array manipulation library for PHP, called Arrayy!", 4 | "keywords": [ 5 | "array", 6 | "Arrayy", 7 | "manipulation", 8 | "utility", 9 | "methods", 10 | "helpers", 11 | "utils" 12 | ], 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Lars Moelleken", 17 | "email": "lars@moelleken.org", 18 | "homepage": "https://www.moelleken.org/", 19 | "role": "Maintainer" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=7.0.0", 24 | "ext-json": "*", 25 | "symfony/polyfill-mbstring": "~1.0", 26 | "phpdocumentor/reflection-docblock": "~4.3 || ~5.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" 30 | }, 31 | "support": { 32 | "docs": "https://voku.github.io/Arrayy/", 33 | "issues": "https://github.com/voku/Arrayy/issues", 34 | "source": "https://github.com/voku/Arrayy" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Arrayy\\": "src/" 39 | }, 40 | "files": [ 41 | "src/Create.php" 42 | ] 43 | }, 44 | "autoload-dev": { 45 | "psr-4": { 46 | "Arrayy\\tests\\": "tests/" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ArrayyIterator.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ArrayyIterator extends \ArrayIterator 13 | { 14 | /** 15 | * @var string 16 | * 17 | * @phpstan-var string|class-string<\Arrayy\Arrayy> 18 | */ 19 | private $class; 20 | 21 | /** 22 | * @param array $array 23 | * @param int $flags 24 | * @param string $class 25 | * 26 | * @phpstan-param array $array 27 | */ 28 | public function __construct(array $array = [], int $flags = 0, string $class = '') 29 | { 30 | $this->class = $class; 31 | 32 | parent::__construct($array, $flags); 33 | } 34 | 35 | /** 36 | * @return Arrayy|mixed will return a "Arrayy"-object instead of an array 37 | */ 38 | #[\ReturnTypeWillChange] 39 | public function current() 40 | { 41 | $value = parent::current(); 42 | 43 | if (\is_array($value)) { 44 | $value = \call_user_func([$this->class, 'create'], $value, static::class, false); 45 | } 46 | 47 | return $value; 48 | } 49 | 50 | /** 51 | * @param string $offset 52 | * 53 | * @return Arrayy|mixed 54 | *

Will return a "Arrayy"-object instead of an array.

55 | * 56 | * @phpstan-param TKey $offset 57 | * @param-return Arrayy|mixed 58 | */ 59 | #[\ReturnTypeWillChange] 60 | public function offsetGet($offset) 61 | { 62 | $value = parent::offsetGet($offset); 63 | 64 | if (\is_array($value)) { 65 | $value = \call_user_func([$this->class, 'create'], $value, static::class, false); 66 | } 67 | 68 | return $value; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ArrayyMeta.php: -------------------------------------------------------------------------------- 1 | > $className 27 | */ 28 | public function getMetaObject(string $className): self 29 | { 30 | static $STATIC_CACHE = []; 31 | 32 | $cacheKey = $className; 33 | if (!empty($STATIC_CACHE[$cacheKey])) { 34 | return $STATIC_CACHE[$cacheKey]; 35 | } 36 | 37 | $reflector = new \ReflectionClass($className); 38 | $factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance(); 39 | $docComment = $reflector->getDocComment(); 40 | if ($docComment) { 41 | $docblock = $factory->create($docComment); 42 | /** @var \phpDocumentor\Reflection\DocBlock\Tags\Property $tag */ 43 | foreach ($docblock->getTagsByName('property') as $tag) { 44 | $PropertyName = $tag->getVariableName(); 45 | $this->{$PropertyName} = $PropertyName; 46 | } 47 | } 48 | 49 | /** @noinspection PhpAssignmentInConditionInspection */ 50 | while ($reflector = $reflector->getParentClass()) { 51 | $docComment = $reflector->getDocComment(); 52 | if ($docComment) { 53 | $docblock = $factory->create($docComment); 54 | /** @var \phpDocumentor\Reflection\DocBlock\Tags\Property $tag */ 55 | foreach ($docblock->getTagsByName('property') as $tag) { 56 | $PropertyName = $tag->getVariableName(); 57 | $this->{$PropertyName} = $PropertyName; 58 | } 59 | } 60 | } 61 | 62 | $STATIC_CACHE[$cacheKey] = $this; 63 | 64 | return $this; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ArrayyRewindableExtendedGenerator.php: -------------------------------------------------------------------------------- 1 | 11 | * 12 | * @internal 13 | */ 14 | class ArrayyRewindableExtendedGenerator extends ArrayyRewindableGenerator 15 | { 16 | public function __construct( 17 | callable $generatorConstructionFunction, 18 | callable $onRewind = null, 19 | string $class = '' 20 | ) { 21 | parent::__construct( 22 | $generatorConstructionFunction, 23 | $onRewind, 24 | $class 25 | ); 26 | } 27 | 28 | /** 29 | * Return the current element. 30 | * 31 | * @return mixed 32 | * 33 | * @see http://php.net/manual/en/iterator.current.php 34 | * @see Iterator::current 35 | * 36 | * @phpstan-return X 37 | */ 38 | #[\ReturnTypeWillChange] 39 | public function current() 40 | { 41 | $value = $this->generator->current(); 42 | 43 | if (\is_array($value)) { 44 | $value = \call_user_func([$this->class, 'create'], $value, static::class, false); 45 | } 46 | 47 | return $value; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ArrayyRewindableGenerator.php: -------------------------------------------------------------------------------- 1 | 11 | * 12 | * @internal 13 | */ 14 | class ArrayyRewindableGenerator extends \ArrayIterator 15 | { 16 | /** 17 | * @var string 18 | * 19 | * @phpstan-var string|class-string<\Arrayy\Arrayy> 20 | */ 21 | protected $class; 22 | 23 | /** 24 | * @var callable 25 | */ 26 | protected $generatorFunction; 27 | 28 | /** 29 | * @var \Generator 30 | * 31 | * @phpstan-var \Generator 32 | */ 33 | protected $generator; 34 | 35 | /** 36 | * @var callable|null 37 | */ 38 | protected $onRewind; 39 | 40 | /** 41 | * @param callable $generatorConstructionFunction 42 | *

A callable that should return a Generator.

43 | * @param callable|null $onRewind 44 | *

Callable that gets invoked with 0 arguments after the iterator 45 | * was rewinded.

46 | * @param string $class 47 | * 48 | * @throws \InvalidArgumentException 49 | */ 50 | public function __construct( 51 | callable $generatorConstructionFunction, 52 | callable $onRewind = null, 53 | string $class = '' 54 | ) { 55 | $this->class = $class; 56 | $this->generatorFunction = $generatorConstructionFunction; 57 | $this->onRewind = $onRewind; 58 | $this->generateGenerator(); 59 | } 60 | 61 | /** 62 | * Return the current element. 63 | * 64 | * @return mixed 65 | * 66 | * @see http://php.net/manual/en/iterator.current.php 67 | * @see Iterator::current 68 | * 69 | * @phpstan-return X 70 | */ 71 | #[\ReturnTypeWillChange] 72 | public function current() 73 | { 74 | return $this->generator->current(); 75 | } 76 | 77 | /** 78 | * Return the key of the current element. 79 | * 80 | * @return mixed scalar on success, or null on failure 81 | * 82 | * @see http://php.net/manual/en/iterator.key.php 83 | * @see Iterator::key 84 | * 85 | * @phpstan-return XKey 86 | */ 87 | #[\ReturnTypeWillChange] 88 | public function key() 89 | { 90 | return $this->generator->key(); 91 | } 92 | 93 | /** 94 | * Move forward to next element. 95 | * 96 | * @return void 97 | * 98 | * @see http://php.net/manual/en/iterator.next.php 99 | * @see Iterator::next 100 | */ 101 | #[\ReturnTypeWillChange] 102 | public function next() 103 | { 104 | $this->generator->next(); 105 | } 106 | 107 | /** 108 | * Rewind the Iterator to the first element. 109 | * 110 | * @return void 111 | * 112 | * @see http://php.net/manual/en/iterator.rewind.php 113 | * @see Iterator::rewind 114 | */ 115 | #[\ReturnTypeWillChange] 116 | public function rewind() 117 | { 118 | $this->generateGenerator(); 119 | 120 | if (\is_callable($this->onRewind)) { 121 | \call_user_func($this->onRewind); 122 | } 123 | } 124 | 125 | /** 126 | * Checks if current position is valid. 127 | * 128 | * @return bool 129 | * 130 | * @see http://php.net/manual/en/iterator.valid.php 131 | * @see Iterator::rewind 132 | */ 133 | public function valid(): bool 134 | { 135 | return $this->generator->valid(); 136 | } 137 | 138 | /** 139 | * @return void 140 | */ 141 | private function generateGenerator() 142 | { 143 | $this->generator = \call_user_func($this->generatorFunction); 144 | 145 | if (!($this->generator instanceof \Generator)) { 146 | throw new \InvalidArgumentException('The callable needs to return a Generator'); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/ArrayyStrict.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class ArrayyStrict extends Arrayy implements \Arrayy\Type\TypeInterface 18 | { 19 | /** 20 | * @var bool 21 | */ 22 | protected $checkPropertyTypes = true; 23 | 24 | /** 25 | * @var bool 26 | */ 27 | protected $checkPropertiesMismatch = false; 28 | 29 | /** 30 | * @var bool 31 | */ 32 | protected $checkForMissingPropertiesInConstructor = true; 33 | 34 | /** 35 | * @var bool 36 | */ 37 | protected $checkPropertiesMismatchInConstructor = false; 38 | } 39 | -------------------------------------------------------------------------------- /src/Collection/AbstractCollection.php: -------------------------------------------------------------------------------- 1 | 27 | * @implements CollectionInterface 28 | */ 29 | abstract class AbstractCollection extends Arrayy implements CollectionInterface 30 | { 31 | /** 32 | * @var bool 33 | */ 34 | protected $checkPropertyTypes = true; 35 | 36 | /** 37 | * @var bool 38 | */ 39 | protected $checkPropertiesMismatch = false; 40 | 41 | /** 42 | * @var bool 43 | */ 44 | protected $checkForMissingPropertiesInConstructor = true; 45 | 46 | /** 47 | * Constructs a collection object of the specified type, optionally with the 48 | * specified data. 49 | * 50 | * @param mixed $data 51 | *

52 | * The initial items to store in the collection. 53 | *

54 | * @param string $iteratorClass optional

55 | * You can overwrite the ArrayyIterator, but mostly you don't 56 | * need this option. 57 | *

58 | * @param bool $checkPropertiesInConstructor optional

59 | * You need to extend the "Arrayy"-class and you need to set 60 | * the $checkPropertiesMismatchInConstructor class property 61 | * to 62 | * true, otherwise this option didn't not work anyway. 63 | *

64 | * 65 | * @phpstan-param array|\Arrayy\Arrayy|\Closure():array|mixed $data 66 | * @phpstan-param class-string<\Arrayy\ArrayyIterator> $iteratorClass 67 | */ 68 | public function __construct( 69 | $data = [], 70 | string $iteratorClass = ArrayyIterator::class, 71 | bool $checkPropertiesInConstructor = true 72 | ) { 73 | $type = $this->getType(); 74 | 75 | $type = self::convertIntoTypeCheckArray($type); 76 | 77 | $this->properties = $type; 78 | 79 | // cast into array, if needed 80 | if ( 81 | !\is_array($data) 82 | && 83 | !($data instanceof \Traversable) 84 | && 85 | !($data instanceof \Closure) 86 | ) { 87 | $data = [$data]; 88 | } 89 | 90 | parent::__construct( 91 | $data, 92 | $iteratorClass, 93 | $checkPropertiesInConstructor 94 | ); 95 | } 96 | 97 | /** 98 | * Append a (key) + value to the current array. 99 | * 100 | * @param mixed $value 101 | * @param mixed $key 102 | * 103 | * @return $this 104 | *

(Mutable) Return this CollectionInterface object, with the appended values.

105 | * 106 | * @phpstan-param T|static $value 107 | * @phpstan-param TKey|null $key 108 | * @phpstan-return static 109 | */ 110 | public function append($value, $key = null): Arrayy 111 | { 112 | if ( 113 | $value instanceof self 114 | && 115 | !$value instanceof TypeInterface 116 | ) { 117 | foreach ($value as $valueTmp) { 118 | parent::append($valueTmp, $key); 119 | } 120 | 121 | return $this; 122 | } 123 | 124 | /* @phpstan-ignore-next-line | special? */ 125 | $return = parent::append($value, $key); 126 | $this->array = $return->array; 127 | $this->generator = null; 128 | 129 | return $this; 130 | } 131 | 132 | /** 133 | * Assigns a value to the specified offset + check the type. 134 | * 135 | * @param int|string|null $offset 136 | * @param mixed $value 137 | * 138 | * @return void 139 | * 140 | * @phpstan-param T $value 141 | */ 142 | #[\ReturnTypeWillChange] 143 | public function offsetSet($offset, $value) 144 | { 145 | if ( 146 | $value instanceof self 147 | && 148 | !$value instanceof TypeInterface 149 | ) { 150 | foreach ($value as $valueTmp) { 151 | parent::offsetSet($offset, $valueTmp); 152 | } 153 | 154 | return; 155 | } 156 | 157 | parent::offsetSet($offset, $value); 158 | } 159 | 160 | /** 161 | * Prepend a (key) + value to the current array. 162 | * 163 | * @param mixed $value 164 | * @param mixed $key 165 | * 166 | * @return $this 167 | *

(Mutable) Return this CollectionInterface object, with the prepended value.

168 | * 169 | * @phpstan-param T|static $value 170 | * @phpstan-param TKey|null $key 171 | * @phpstan-return static 172 | */ 173 | public function prepend($value, $key = null): Arrayy 174 | { 175 | if ( 176 | $value instanceof self 177 | && 178 | !$value instanceof TypeInterface 179 | ) { 180 | foreach ($value as $valueTmp) { 181 | parent::prepend($valueTmp, $key); 182 | } 183 | 184 | return $this; 185 | } 186 | 187 | /* @phpstan-ignore-next-line | special? */ 188 | $return = parent::prepend($value, $key); 189 | $this->array = $return->array; 190 | $this->generator = null; 191 | 192 | return $this; 193 | } 194 | 195 | /** 196 | * {@inheritdoc} 197 | */ 198 | public function column(string $keyOrPropertyOrMethod): array 199 | { 200 | // init 201 | $temp = []; 202 | 203 | foreach ($this->getGenerator() as $item) { 204 | $temp[] = $this->extractValue($item, $keyOrPropertyOrMethod); 205 | } 206 | 207 | return $temp; 208 | } 209 | 210 | /** 211 | * @return array 212 | * 213 | * @phpstan-return array 214 | */ 215 | public function getCollection(): array 216 | { 217 | return $this->getArray(); 218 | } 219 | 220 | /** 221 | * The type (FQCN) associated with this collection. 222 | * 223 | * @return string|string[]|TypeCheckArray|TypeCheckInterface[] 224 | * 225 | * @phpstan-return string|string[]|class-string|class-string[]|TypeCheckArray|TypeCheckInterface[] 226 | */ 227 | abstract public function getType(); 228 | 229 | /** 230 | * Merge current items and items of given collections into a new one. 231 | * 232 | * @param CollectionInterface|static ...$collections 233 | *

The collections to merge.

234 | * 235 | *@throws \InvalidArgumentException if any of the given collections are not of the same type 236 | * 237 | * @return $this 238 | * 239 | * @phpstan-param CollectionInterface ...$collections 240 | * @phpstan-return static 241 | */ 242 | public function merge(CollectionInterface ...$collections): self 243 | { 244 | foreach ($collections as $collection) { 245 | foreach ($collection as $item) { 246 | $this->append($item); 247 | } 248 | } 249 | 250 | return $this; 251 | } 252 | 253 | /** 254 | * Creates an CollectionInterface object. 255 | * 256 | * @param mixed $data 257 | * @param string $iteratorClass 258 | * @param bool $checkPropertiesInConstructor 259 | * 260 | * @return static 261 | *

(Immutable) Returns an new instance of the CollectionInterface object.

262 | * 263 | * @template TKeyCreate as TKey 264 | * @template TCreate as T 265 | * @phpstan-param array $data 266 | * @phpstan-param class-string<\Arrayy\ArrayyIterator> $iteratorClass 267 | * @phpstan-return static 268 | * 269 | * @psalm-mutation-free 270 | */ 271 | public static function create( 272 | $data = [], 273 | string $iteratorClass = ArrayyIterator::class, 274 | bool $checkPropertiesInConstructor = true 275 | ) { 276 | return new static( 277 | $data, 278 | $iteratorClass, 279 | $checkPropertiesInConstructor 280 | ); 281 | } 282 | 283 | /** 284 | * @param string $json 285 | * 286 | * @return static 287 | *

(Immutable) Returns an new instance of the CollectionInterface object.

288 | * 289 | * @phpstan-return static 290 | * 291 | * @psalm-mutation-free 292 | */ 293 | public static function createFromJsonMapper(string $json) 294 | { 295 | // init 296 | $return = static::create(); 297 | 298 | $jsonObject = \json_decode($json, false); 299 | 300 | $mapper = new \Arrayy\Mapper\Json(); 301 | $mapper->undefinedPropertyHandler = static function ($object, $key, $jsonValue) use ($return) { 302 | if ($return->checkForMissingPropertiesInConstructor) { 303 | throw new \TypeError('Property mismatch - input: ' . \print_r(['key' => $key, 'jsonValue' => $jsonValue], true) . ' for object: ' . \get_class($object)); 304 | } 305 | }; 306 | 307 | $type = $return->getType(); 308 | 309 | if ( 310 | \is_string($type) 311 | && 312 | \class_exists($type) 313 | ) { 314 | if (\is_array($jsonObject)) { 315 | foreach ($jsonObject as $jsonObjectSingle) { 316 | $collectionData = $mapper->map($jsonObjectSingle, $type); 317 | $return->add($collectionData); 318 | } 319 | } else { 320 | $collectionData = $mapper->map($jsonObject, $type); 321 | $return->add($collectionData); 322 | } 323 | } else { 324 | foreach ($jsonObject as $key => $jsonValue) { 325 | $return->add($jsonValue, $key); 326 | } 327 | } 328 | 329 | /** @phpstan-var static */ 330 | return $return; 331 | } 332 | 333 | /** 334 | * Internal mechanic of set method. 335 | * 336 | * @param int|string|null $key 337 | * @param mixed $value 338 | * @param bool $checkProperties 339 | * 340 | * @return bool 341 | */ 342 | protected function internalSet( 343 | $key, 344 | &$value, 345 | bool $checkProperties = true 346 | ): bool { 347 | if ( 348 | $value instanceof self 349 | && 350 | !$value instanceof TypeInterface 351 | ) { 352 | foreach ($value as $valueTmp) { 353 | parent::internalSet( 354 | $key, 355 | $valueTmp, 356 | $checkProperties 357 | ); 358 | } 359 | 360 | return true; 361 | } 362 | 363 | return parent::internalSet( 364 | $key, 365 | $value, 366 | $checkProperties 367 | ); 368 | } 369 | 370 | /** 371 | * @param string|string[]|TypeCheckArray|TypeCheckInterface[]|null $type 372 | * 373 | * @return TypeCheckArray 374 | * 375 | * @phpstan-param null|string|string[]|class-string|class-string[]|TypeCheckArray|array|mixed $type 376 | * @phpstan-return TypeCheckArray 377 | */ 378 | protected static function convertIntoTypeCheckArray($type): TypeCheckArray 379 | { 380 | $is_array = false; 381 | if ( 382 | \is_scalar($type) 383 | || 384 | $is_array = \is_array($type) 385 | ) { 386 | $type = TypeCheckArray::create( 387 | [ 388 | Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES => new TypeCheckSimple($is_array ? $type : (string) $type), 389 | ] 390 | ); 391 | } 392 | 393 | return $type; 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /src/Collection/Collection.php: -------------------------------------------------------------------------------- 1 | add(new \My\Foo()); 25 | * $collection->add(new \My\Foo()); 26 | * 27 | * foreach ($collection as $foo) { 28 | * if ($foo instanceof \My\FooInterface) { 29 | * // Do something with $foo 30 | * } 31 | * } 32 | * ``` 33 | * 34 | * It is preferable to subclass `AbstractCollection` to create your own typed 35 | * collections. For example: 36 | * 37 | * ``` php 38 | * namespace My; 39 | * /** 40 | * * @extends \Arrayy\Collection\AbstractCollection 41 | * *\/ 42 | * class FooCollection extends \Arrayy\Collection\AbstractCollection 43 | * { 44 | * public function getType() 45 | * { 46 | * return FooInterface::class; 47 | * } 48 | * } 49 | * ``` 50 | * 51 | * And then use it similarly to the earlier example: 52 | * 53 | * ``` php 54 | * namespace My; 55 | * 56 | * $fooCollection = new \My\FooCollection(); 57 | * $fooCollection->add(new \My\Foo()); 58 | * $fooCollection->add(new \My\Foo()); 59 | * 60 | * foreach ($fooCollection as $foo) { 61 | * if ($foo instanceof \My\FooInterface) { 62 | * // Do something with $foo 63 | * } 64 | * } 65 | * ``` 66 | * 67 | * INFO: this collection thingy is inspired by https://github.com/ramsey/collection/ 68 | * 69 | * @template TKey of array-key 70 | * @template T 71 | * @extends AbstractCollection 72 | */ 73 | class Collection extends AbstractCollection 74 | { 75 | /** 76 | * Constructs a collection object of the specified type, optionally with the 77 | * specified data. 78 | * 79 | * @param mixed $data 80 | *

81 | * The initial items to store in the 82 | * collection. 83 | *

84 | * @param string|null $iteratorClass optional

85 | * You can overwrite the 86 | * ArrayyIterator, but mostly you 87 | * don't need this option. 88 | *

89 | * @param bool $checkPropertiesInConstructor optional

90 | * You need to extend the 91 | * "Arrayy"-class and you need to set 92 | * the 93 | * $checkPropertiesMismatchInConstructor 94 | * class property to true, otherwise 95 | * this option didn't not work 96 | * anyway. 97 | *

98 | * @param TypeInterface|null $type 99 | * 100 | * @phpstan-param array|array|\Arrayy\Arrayy $data 101 | * @phpstan-param class-string<\Arrayy\ArrayyIterator> $iteratorClass 102 | */ 103 | public function __construct( 104 | $data = [], 105 | string $iteratorClass = null, 106 | bool $checkPropertiesInConstructor = null, 107 | TypeInterface $type = null 108 | ) { 109 | // fallback 110 | if ($iteratorClass === null) { 111 | $iteratorClass = ArrayyIterator::class; 112 | } 113 | if ($checkPropertiesInConstructor === null) { 114 | $checkPropertiesInConstructor = true; 115 | } 116 | 117 | if ($type !== null) { 118 | /* @phpstan-ignore-next-line - we use the "TypeInterface" only as base */ 119 | $this->properties = $type; 120 | } 121 | 122 | parent::__construct( 123 | $data, 124 | $iteratorClass, 125 | $checkPropertiesInConstructor 126 | ); 127 | } 128 | 129 | /** 130 | * @param string|TypeCheckArray|TypeCheckInterface[] $type 131 | * @param array $data 132 | * @param bool $checkPropertiesInConstructorAndType 133 | * 134 | * @return static 135 | * 136 | * @template TConstruct 137 | * @phpstan-param string|class-string|class-string|TypeInterface|TypeCheckArray|array $type 138 | * @phpstan-param array $data 139 | * @phpstan-return static 140 | */ 141 | public static function construct( 142 | $type, 143 | $data = [], 144 | bool $checkPropertiesInConstructorAndType = true 145 | ): self { 146 | $type = self::convertIntoTypeCheckArray($type); 147 | 148 | return new static( 149 | $data, 150 | ArrayyIterator::class, 151 | $checkPropertiesInConstructorAndType, 152 | $type 153 | ); 154 | } 155 | 156 | /** 157 | * Returns a new iterator, thus implementing the \Iterator interface. 158 | * 159 | * @return \Iterator 160 | *

An iterator for the values in the array.

161 | * 162 | * @phpstan-return \Iterator 163 | * 164 | * @noinspection SenselessProxyMethodInspection 165 | */ 166 | public function getIterator(): \Iterator 167 | { 168 | return parent::getIterator(); 169 | } 170 | 171 | /** 172 | * The type (FQCN) associated with this collection. 173 | * 174 | * @return string|TypeCheckArray|TypeCheckInterface[] 175 | * 176 | * @phpstan-return string|class-string|class-string|TypeInterface|TypeCheckArray|TypeCheckArray|array|array 177 | */ 178 | public function getType() 179 | { 180 | return $this->properties; 181 | } 182 | 183 | /** 184 | * Get a base Collection instance from this Collection. 185 | * 186 | * @return self 187 | * 188 | * @phpstan-return self 189 | */ 190 | public function toBase(): self 191 | { 192 | return self::construct( 193 | $this->getType(), 194 | $this->getArray() 195 | ); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/Collection/CollectionInterface.php: -------------------------------------------------------------------------------- 1 | 20 | * @extends \ArrayAccess 21 | */ 22 | interface CollectionInterface extends \IteratorAggregate, \ArrayAccess, \Serializable, \JsonSerializable, \Countable 23 | { 24 | /** 25 | * Assigns a value to the specified element. 26 | * 27 | * @param mixed $key 28 | * @param mixed $value 29 | * 30 | * @return void 31 | * 32 | * @phpstan-param TKey $key 33 | * @phpstan-param T $value 34 | */ 35 | public function __set($key, $value); 36 | 37 | /** 38 | * alias: for "CollectionInterface->append()" 39 | * 40 | * @param mixed $value 41 | * 42 | * @return CollectionInterface 43 | *

(Mutable) Return this CollectionInterface object, with the appended values.

44 | * 45 | * @see CollectionInterface::append() 46 | * 47 | * @phpstan-param T $value 48 | * @phpstan-return CollectionInterface 49 | */ 50 | public function add($value); 51 | 52 | /** 53 | * Append a (key) + value to the current array. 54 | * 55 | * @param mixed $value 56 | * @param mixed $key 57 | * 58 | * @return CollectionInterface 59 | *

(Mutable) Return this CollectionInterface object, with the appended values.

60 | * 61 | * @phpstan-param T $value 62 | * @phpstan-param TKey|null $key 63 | * @phpstan-return CollectionInterface 64 | */ 65 | public function append($value, $key = null); 66 | 67 | /** 68 | * Append a (key) + values to the current array. 69 | * 70 | * @param array $values 71 | * @param mixed $key 72 | * 73 | * @return CollectionInterface 74 | *

(Mutable) Return this CollectionInterface object, with the appended values.

75 | * 76 | * @phpstan-param array $values 77 | * @phpstan-param TKey $key 78 | * @phpstan-return CollectionInterface 79 | */ 80 | public function appendArrayValues(array $values, $key = null); 81 | 82 | /** 83 | * Clears the current collection, by removing all elements. 84 | * 85 | * @return void 86 | */ 87 | public function clear(); 88 | 89 | /** 90 | * Returns the values from given property or method. 91 | * 92 | * @param string $keyOrPropertyOrMethod the property or method name to filter by 93 | * 94 | * @throws \InvalidArgumentException if property or method is not defined 95 | * 96 | * @return array 97 | */ 98 | public function column(string $keyOrPropertyOrMethod): array; 99 | 100 | /** 101 | * Check if an item is in the current array. 102 | * 103 | * @param float|int|string $value 104 | * @param bool $recursive 105 | * @param bool $strict 106 | * 107 | * @return bool 108 | * 109 | * @phpstan-param T $value 110 | */ 111 | public function contains($value, bool $recursive = false, bool $strict = true): bool; 112 | 113 | /** 114 | * Checks whether the collection contains an element with the specified key/index. 115 | * 116 | * @param int|string $key 117 | *

The key/index to check for.

118 | * 119 | * @return bool 120 | *

TRUE if the collection contains an element with the specified key/index, 121 | * FALSE otherwise.

122 | * 123 | * @phpstan-param TKey $key 124 | */ 125 | public function containsKey($key): bool; 126 | 127 | /** 128 | * alias: for "CollectionInterface->contains()" 129 | * 130 | * @param float|int|string $value 131 | * 132 | * @return bool 133 | * 134 | * @see CollectionInterface::contains() 135 | * 136 | * @phpstan-param T $value 137 | */ 138 | public function containsValue($value): bool; 139 | 140 | /** 141 | * alias: for "CollectionInterface->contains($value, true)" 142 | * 143 | * @param float|int|string $value 144 | * 145 | * @return bool 146 | * 147 | * @see CollectionInterface::contains() 148 | * 149 | * @phpstan-param T $value 150 | */ 151 | public function containsValueRecursive($value): bool; 152 | 153 | /** 154 | * Creates an CollectionInterface object. 155 | * 156 | * @param mixed $data 157 | * @param string $iteratorClass 158 | * @param bool $checkPropertiesInConstructor 159 | * 160 | * @return CollectionInterface 161 | *

(Immutable) Returns an new instance of the CollectionInterface object.

162 | * 163 | * @template TKeyCreate as TKey 164 | * @template TCreate as T 165 | * @phpstan-param array $data 166 | * @phpstan-param class-string<\Arrayy\ArrayyIterator> $iteratorClass 167 | * @phpstan-return static 168 | * 169 | * @psalm-mutation-free 170 | */ 171 | public static function create( 172 | $data = [], 173 | string $iteratorClass = ArrayyIterator::class, 174 | bool $checkPropertiesInConstructor = true 175 | ); 176 | 177 | /** 178 | * Gets the element of the collection at the current iterator position. 179 | * 180 | * @return false|mixed 181 | * 182 | * @phpstan-return T|false 183 | */ 184 | public function current(); 185 | 186 | /** 187 | * Tests for the existence of an element that satisfies the given predicate. 188 | * 189 | * @param \Closure $closure the predicate 190 | * 191 | * @return bool 192 | *

TRUE if the predicate is TRUE for at least one element, FALSE otherwise.

193 | * 194 | * @phpstan-param \Closure(T,TKey):bool $closure 195 | */ 196 | public function exists(\Closure $closure): bool; 197 | 198 | /** 199 | * Returns all the elements of this collection that satisfy the predicate p. 200 | * The order of the elements is preserved. 201 | * 202 | * @param \Closure $closure the predicate used for filtering 203 | * @param int $flag [optional] 204 | * 205 | * @return CollectionInterface 206 | *

A collection with the results of the filter operation.

207 | * 208 | * @phpstan-param \Closure(T,TKey):bool $closure 209 | * @phpstan-return CollectionInterface 210 | */ 211 | public function filter($closure = null, int $flag = \ARRAY_FILTER_USE_BOTH); 212 | 213 | /** 214 | * Sets the internal iterator to the first element in the collection and returns this element. 215 | * 216 | * @return mixed 217 | * 218 | * @phpstan-return T|false 219 | */ 220 | public function first(); 221 | 222 | /** 223 | * Tests whether the given closure retrun something valid for all elements of this array. 224 | * 225 | * @param \Closure $closure the predicate 226 | * 227 | * @return bool TRUE, if the predicate yields TRUE for all elements, FALSE otherwise 228 | * 229 | * @phpstan-param \Closure(T,TKey):bool $closure 230 | */ 231 | public function validate(\Closure $closure): bool; 232 | 233 | /** 234 | * Gets the element at the specified key/index. 235 | * 236 | * @param int|string $key 237 | *

The key/index of the element to retrieve.

238 | * 239 | * @return mixed 240 | * 241 | * @phpstan-param TKey $key 242 | * @phpstan-return T|null 243 | */ 244 | public function get($key); 245 | 246 | /** 247 | * Creates a copy of the CollectionInterface. 248 | * 249 | * @return array 250 | * 251 | * @phpstan-return array 252 | */ 253 | public function getArrayCopy(): array; 254 | 255 | /** 256 | * @return array 257 | * 258 | * @phpstan-return array 259 | */ 260 | public function getCollection(): array; 261 | 262 | /** 263 | * Gets all keys/indices of the collection. 264 | * 265 | * @return CollectionInterface 266 | * 267 | * @phpstan-return TKey[] 268 | */ 269 | public function getKeys(); 270 | 271 | /** 272 | * The type (FQCN) associated with this collection. 273 | * 274 | * @return string|string[]|TypeCheckArray|TypeCheckInterface[] 275 | * 276 | * @phpstan-return string|string[]|class-string|class-string[]|TypeCheckArray|TypeCheckInterface[] 277 | */ 278 | public function getType(); 279 | 280 | /** 281 | * Gets all values of the collection. 282 | * 283 | * @return CollectionInterface 284 | * 285 | * @phpstan-return T[] 286 | */ 287 | public function getValues(); 288 | 289 | /** 290 | * Check if an array has a given value. 291 | * 292 | * INFO: if you need to search recursive please use ```contains()``` 293 | * 294 | * @param mixed $value 295 | * 296 | * @return bool 297 | * 298 | * @phpstan-param T $value 299 | */ 300 | public function hasValue($value): bool; 301 | 302 | /** 303 | * Gets the index/key of a given element. The comparison of two elements is strict, 304 | * that means not only the value but also the type must match. 305 | * For objects this means reference equality. 306 | * 307 | * @param mixed $element the element to search for 308 | * 309 | * @return false|mixed the key/index of the element or FALSE if the element was not found 310 | * 311 | * @phpstan-param T $element 312 | * @phpstan-return TKey|false 313 | */ 314 | public function indexOf($element); 315 | 316 | /** 317 | * Checks whether the collection is empty (contains no elements). 318 | * 319 | * @param int|int[]|string|string[]|null $keys 320 | * 321 | * @return bool 322 | *

TRUE if the collection is empty, FALSE otherwise.

323 | * 324 | * @phpstan-param TKey|TKey[]|null $keys 325 | */ 326 | public function isEmpty($keys = null): bool; 327 | 328 | /** 329 | * Gets the key/index of the element at the current iterator position. 330 | * 331 | * @return int|string|null 332 | * 333 | * @phpstan-return TKey|null 334 | */ 335 | public function key(); 336 | 337 | /** 338 | * Sets the internal iterator to the last element in the collection and returns this element. 339 | * 340 | * @return mixed 341 | * 342 | * @phpstan-return T|false 343 | */ 344 | public function last(); 345 | 346 | /** 347 | * Applies the given function to each element in the collection and returns 348 | * a new collection with the elements returned by the function. 349 | * 350 | * @param callable $callable 351 | * @param bool $useKeyAsSecondParameter 352 | * @param mixed ...$arguments 353 | * 354 | * @return CollectionInterface 355 | * 356 | * @phpstan-param callable(T,TKey,mixed):mixed $callable 357 | * @phpstan-return CollectionInterface 358 | */ 359 | public function map(callable $callable, bool $useKeyAsSecondParameter = false, ...$arguments); 360 | 361 | /** 362 | * Merge current items and items of given collections into a new one. 363 | * 364 | * @param CollectionInterface ...$collections The collections to merge. 365 | * 366 | * @throws \InvalidArgumentException if any of the given collections are not of the same type 367 | * 368 | * @return CollectionInterface 369 | * 370 | * @phpstan-param CollectionInterface ...$collections 371 | * @phpstan-return CollectionInterface 372 | */ 373 | public function merge(self ...$collections); 374 | 375 | /** 376 | * Moves the internal iterator position to the next element and returns this element. 377 | * 378 | * @return mixed 379 | * 380 | * @phpstan-return T|false 381 | */ 382 | public function next(); 383 | 384 | /** 385 | * Assigns a value to the specified offset + check the type. 386 | * 387 | * @param int|string|null $offset 388 | * @param mixed $value 389 | * 390 | * @return void 391 | * 392 | * @phpstan-param TKey $offset 393 | * @phpstan-param T $value 394 | */ 395 | #[\ReturnTypeWillChange] 396 | public function offsetSet($offset, $value); 397 | 398 | /** 399 | * Partitions this collection in two collections according to a predicate. 400 | * Keys are preserved in the resulting collections. 401 | * 402 | * @param \Closure $p the predicate on which to partition 403 | * 404 | * @return array An array with two elements. The first element contains the collection 405 | * of elements where the predicate returned TRUE, the second element 406 | * contains the collection of elements where the predicate returned FALSE. 407 | * 408 | * @phpstan-param \Closure(T,TKey):bool $p 409 | * @phpstan-return array{0: CollectionInterface, 1: CollectionInterface} 410 | */ 411 | public function partition(\Closure $p): array; 412 | 413 | /** 414 | * Prepend a (key) + value to the current array. 415 | * 416 | * @param mixed $value 417 | * @param mixed $key 418 | * 419 | * @return CollectionInterface 420 | *

(Mutable) Return this CollectionInterface object, with the prepended value.

421 | * 422 | * @phpstan-param T $value 423 | * @phpstan-param TKey|null $key 424 | * @phpstan-return CollectionInterface 425 | */ 426 | public function prepend($value, $key = null); 427 | 428 | /** 429 | * Removes the element at the specified index from the collection. 430 | * 431 | * Remove a value from the current array (optional using dot-notation). 432 | * 433 | * @param mixed $key 434 | * 435 | * @return CollectionInterface 436 | * 437 | * @phpstan-param TKey $key 438 | * @phpstan-return CollectionInterface 439 | */ 440 | public function remove($key); 441 | 442 | /** 443 | * Removes the specified element from the collection, if it is found. 444 | * 445 | * @param mixed $element 446 | *

The element to remove.

447 | * 448 | * @return CollectionInterface 449 | * 450 | * @phpstan-param T $element 451 | * @phpstan-return CollectionInterface 452 | */ 453 | public function removeElement($element); 454 | 455 | /** 456 | * Removes a particular value from an array (numeric or associative). 457 | * 458 | * @param mixed $value 459 | * 460 | * @return CollectionInterface 461 | *

(Immutable)

462 | * 463 | * @phpstan-param T $value 464 | * @phpstan-return CollectionInterface 465 | */ 466 | public function removeValue($value); 467 | 468 | /** 469 | * Sets an element in the collection at the specified key/index. 470 | * 471 | * @param int|string $key 472 | *

The key/index of the element to set.

473 | * @param mixed $value 474 | *

The element to set.

475 | * 476 | * @return CollectionInterface 477 | * 478 | * @phpstan-param TKey $key 479 | * @phpstan-param T $value 480 | * @phpstan-return CollectionInterface 481 | */ 482 | public function set($key, $value); 483 | 484 | /** 485 | * Extracts a slice of $length elements starting at position $offset from the Collection. 486 | * 487 | * If $length is null it returns all elements from $offset to the end of the Collection. 488 | * Keys have to be preserved by this method. Calling this method will only return the 489 | * selected slice and NOT change the elements contained in the collection slice is called on. 490 | * 491 | * @param int $offset the offset to start from 492 | * @param int|null $length the maximum number of elements to return, or null for no limit 493 | * @param bool $preserveKeys 494 | * 495 | * @return CollectionInterface 496 | * 497 | * @phpstan-return CollectionInterface 498 | */ 499 | public function slice(int $offset, int $length = null, bool $preserveKeys = false); 500 | 501 | /** 502 | * Gets a native PHP array representation of the collection. 503 | * 504 | * @return array 505 | * 506 | * @phpstan-return array 507 | */ 508 | public function toArray(): array; 509 | 510 | /** 511 | * Returns a collection of matching items. 512 | * 513 | * @param string $keyOrPropertyOrMethod the property or method to evaluate 514 | * @param mixed $value the value to match 515 | * 516 | * @throws \InvalidArgumentException if property or method is not defined 517 | * 518 | * @return CollectionInterface 519 | * 520 | * @phpstan-return CollectionInterface 521 | */ 522 | public function where(string $keyOrPropertyOrMethod, $value); 523 | } 524 | -------------------------------------------------------------------------------- /src/Create.php: -------------------------------------------------------------------------------- 1 | $array 28 | * 29 | * @return int|string|null 30 | */ 31 | function array_key_first(array $array) 32 | { 33 | foreach ($array as $key => $value) { 34 | return $key; 35 | } 36 | 37 | return null; 38 | } 39 | } 40 | 41 | if (!\function_exists('array_key_last')) { 42 | /** 43 | * @param array $array 44 | * 45 | * @return int|string|null 46 | */ 47 | function array_key_last(array $array) 48 | { 49 | if (\count($array) === 0) { 50 | return null; 51 | } 52 | 53 | return \array_keys( 54 | \array_slice($array, -1, 1, true) 55 | )[0]; 56 | } 57 | } 58 | } 59 | } 60 | 61 | namespace Arrayy { 62 | use Arrayy\Collection\Collection; 63 | use Arrayy\TypeCheck\TypeCheckArray; 64 | use Arrayy\TypeCheck\TypeCheckInterface; 65 | 66 | if (!\function_exists('Arrayy\create')) { 67 | /** 68 | * Creates a Arrayy object. 69 | * 70 | * @param mixed $data 71 | * 72 | * @return Arrayy 73 | */ 74 | function create($data): Arrayy 75 | { 76 | return new Arrayy($data); 77 | } 78 | } 79 | 80 | if (!\function_exists('Arrayy\collection')) { 81 | /** 82 | * Creates a Collection object. 83 | * 84 | * @param string|TypeCheckArray|TypeCheckInterface[] $type 85 | * @param array $data 86 | * 87 | * @return Collection 88 | * 89 | * @template T 90 | * @phpstan-param T $type 91 | * @phpstan-return Collection 92 | */ 93 | function collection($type, $data = []): Collection 94 | { 95 | /** @phpstan-var Collection */ 96 | return Collection::construct($type, $data); 97 | } 98 | } 99 | 100 | /** 101 | * @param array $array 102 | * @param mixed $fallback

This fallback will be used, if the array is empty.

103 | * 104 | * @return mixed|null 105 | * 106 | * @template TLast 107 | * @template TLastFallback 108 | * @phpstan-param TLast[] $array 109 | * @phpstan-param TLastFallback $fallback 110 | * @phpstan-return TLast|TLastFallback 111 | */ 112 | function array_last(array $array, $fallback = null) 113 | { 114 | $key_last = \array_key_last($array); 115 | if ($key_last === null) { 116 | return $fallback; 117 | } 118 | 119 | return $array[$key_last]; 120 | } 121 | 122 | /** 123 | * @param array $array 124 | * @param mixed $fallback

This fallback will be used, if the array is empty.

125 | * 126 | * @return mixed|null 127 | * 128 | * @template TFirst 129 | * @template TFirstFallback 130 | * @phpstan-param TFirst[] $array 131 | * @phpstan-param TFirstFallback $fallback 132 | * @phpstan-return TFirst|TFirstFallback 133 | */ 134 | function array_first(array $array, $fallback = null) 135 | { 136 | $key_first = \array_key_first($array); 137 | if ($key_first === null) { 138 | return $fallback; 139 | } 140 | 141 | return $array[$key_first]; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Mapper/Json.php: -------------------------------------------------------------------------------- 1 | JSON object structure from json_decode()

53 | * @param object|string $object 54 | *

Object to map $json data into

55 | * 56 | * @return mixed 57 | *

mapped object is returned

58 | * 59 | * @see mapArray() 60 | * 61 | * @template TObject 62 | * @phpstan-param TObject|class-string $object 63 | *

Object to map $json data into.

64 | * @phpstan-return TObject 65 | * 66 | */ 67 | public function map($json, $object) 68 | { 69 | if (\is_string($object) && \class_exists($object)) { 70 | $object = self::createInstance($object); 71 | } 72 | 73 | if (!\is_object($object)) { 74 | throw new \InvalidArgumentException( 75 | 'JsonMapper::map() requires second argument to be an object, ' . \gettype($object) . ' given.' 76 | ); 77 | } 78 | 79 | $strClassName = \get_class($object); 80 | $rc = new \ReflectionClass($object); 81 | $strNs = $rc->getNamespaceName(); 82 | foreach ($json as $key => $jsonValue) { 83 | $key = $this->getSafeName($key); 84 | 85 | // Store the property inspection results, so we don't have to do it 86 | // again for subsequent objects of the same type. 87 | if (!isset($this->arInspectedClasses[$strClassName][$key])) { 88 | $this->arInspectedClasses[$strClassName][$key] = $this->inspectProperty($rc, $key); 89 | } 90 | 91 | list( 92 | $hasProperty, 93 | $accessor, 94 | $type 95 | ) = $this->arInspectedClasses[$strClassName][$key]; 96 | 97 | if (!$hasProperty) { 98 | if (\is_callable($this->undefinedPropertyHandler)) { 99 | \call_user_func( 100 | $this->undefinedPropertyHandler, 101 | $object, 102 | $key, 103 | $jsonValue 104 | ); 105 | } 106 | 107 | continue; 108 | } 109 | 110 | if ($accessor === null) { 111 | continue; 112 | } 113 | 114 | if ($this->isNullable($type)) { 115 | if ($jsonValue === null) { 116 | $this->setProperty($object, $accessor, null); 117 | 118 | continue; 119 | } 120 | 121 | $type = $this->removeNullable($type); 122 | } elseif ($jsonValue === null) { 123 | throw new \InvalidArgumentException( 124 | 'JSON property "' . $key . '" in class "' . $strClassName . '" must not be NULL' 125 | ); 126 | } 127 | 128 | $type = $this->getFullNamespace($type, $strNs); 129 | $type = $this->getMappedType($type, $jsonValue); 130 | 131 | if ( 132 | $type === null 133 | || 134 | $type === 'mixed' 135 | ) { 136 | // no given type - simply set the json data 137 | $this->setProperty($object, $accessor, $jsonValue); 138 | 139 | continue; 140 | } 141 | 142 | if ($this->isObjectOfSameType($type, $jsonValue)) { 143 | $this->setProperty($object, $accessor, $jsonValue); 144 | 145 | continue; 146 | } 147 | 148 | if ($this->isSimpleType($type)) { 149 | if ($type === 'string' && \is_object($jsonValue)) { 150 | throw new \InvalidArgumentException( 151 | 'JSON property "' . $key . '" in class "' . $strClassName . '" is an object and cannot be converted to a string' 152 | ); 153 | } 154 | 155 | if (\strpos($type, '|') !== false) { 156 | foreach (\explode('|', $type) as $tmpType) { 157 | if (\gettype($jsonValue) === $tmpType) { 158 | \settype($jsonValue, $tmpType); 159 | } 160 | } 161 | } else { 162 | \settype($jsonValue, $type); 163 | } 164 | 165 | $this->setProperty($object, $accessor, $jsonValue); 166 | 167 | continue; 168 | } 169 | 170 | if ($type === '') { 171 | throw new \InvalidArgumentException( 172 | 'Empty type at property "' . $strClassName . '::$' . $key . '"' 173 | ); 174 | } 175 | 176 | $array = null; 177 | $subtype = null; 178 | if ($this->isArrayOfType($type)) { 179 | $array = []; 180 | $subtype = \substr($type, 0, -2); 181 | } elseif (\substr($type, -1) == ']') { 182 | list($proptype, $subtype) = \explode('[', \substr($type, 0, -1)); 183 | if ($proptype == 'array') { 184 | $array = []; 185 | } else { 186 | /** @noinspection PhpSillyAssignmentInspection - phpstan helper */ 187 | /** @phpstan-var class-string $proptype */ 188 | $proptype = $proptype; 189 | $array = self::createInstance($proptype, false, $jsonValue); 190 | } 191 | } elseif (\is_a($type, \ArrayObject::class, true)) { 192 | /** @noinspection PhpSillyAssignmentInspection - phpstan helper */ 193 | /** @phpstan-var \ArrayObject $type */ 194 | $type = $type; 195 | $array = self::createInstance($type, false, $jsonValue); 196 | } 197 | 198 | if ($array !== null) { 199 | /** @noinspection NotOptimalIfConditionsInspection */ 200 | if ( 201 | !\is_array($jsonValue) 202 | && 203 | $this->isScalarType(\gettype($jsonValue)) 204 | ) { 205 | throw new \InvalidArgumentException( 206 | 'JSON property "' . $key . '" must be an array, ' . \gettype($jsonValue) . ' given' 207 | ); 208 | } 209 | 210 | $cleanSubtype = $this->removeNullable($subtype); 211 | $subtype = $this->getFullNamespace($cleanSubtype, $strNs); 212 | $child = $this->mapArray($jsonValue, $array, $subtype, $key); 213 | } elseif ($this->isScalarType(\gettype($jsonValue))) { 214 | // use constructor parameter if we have a class, but only a flat type (i.e. string, int) 215 | /** @noinspection PhpSillyAssignmentInspection - phpstan helper */ 216 | /** @phpstan-var object $type */ 217 | $type = $type; 218 | $child = self::createInstance($type, true, $jsonValue); 219 | } else { 220 | /** @noinspection PhpSillyAssignmentInspection - phpstan helper */ 221 | /** @phpstan-var object $type */ 222 | $type = $type; 223 | $child = self::createInstance($type, false, $jsonValue); 224 | $this->map($jsonValue, $child); 225 | } 226 | 227 | $this->setProperty($object, $accessor, $child); 228 | } 229 | 230 | /** @noinspection PhpSillyAssignmentInspection */ 231 | /** @phpstan-var TObject $object */ 232 | $object = $object; 233 | 234 | return $object; 235 | } 236 | 237 | /** 238 | * Map an array 239 | * 240 | * @param array $json JSON array structure from json_decode() 241 | * @param mixed $array Array or ArrayObject that gets filled with 242 | * data from $json 243 | * @param string|null $class Class name for children objects. 244 | * All children will get mapped onto this type. 245 | * Supports class names and simple types 246 | * like "string" and nullability "string|null". 247 | * Pass "null" to not convert any values 248 | * @param string $parent_key defines the key this array belongs to 249 | * in order to aid debugging 250 | * 251 | * @pslam-param null|class-string $class 252 | * 253 | * @return mixed Mapped $array is returned 254 | */ 255 | public function mapArray($json, $array, $class = null, $parent_key = '') 256 | { 257 | $originalClass = $class; 258 | foreach ($json as $key => $jsonValue) { 259 | $class = $this->getMappedType($originalClass, $jsonValue); 260 | if ($class === null) { 261 | $foundArrayy = false; 262 | 263 | if ($array instanceof \Arrayy\Arrayy && $jsonValue instanceof \stdClass) { 264 | foreach ($array->getPhpDocPropertiesFromClass() as $typesKey => $typesTmp) { 265 | if ( 266 | ( 267 | $typesKey === $key 268 | || 269 | $typesKey === \Arrayy\Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES 270 | ) 271 | && 272 | \count($typesTmp->getTypes()) === 1 273 | && 274 | \is_subclass_of($typesTmp->getTypes()[0], \Arrayy\Arrayy::class) 275 | ) { 276 | $array[$key] = $typesTmp->getTypes()[0]::createFromObjectVars($jsonValue); 277 | $foundArrayy = true; 278 | 279 | break; 280 | } 281 | } 282 | } 283 | if ($foundArrayy === false) { 284 | if ($array instanceof \Arrayy\Arrayy && $jsonValue instanceof \stdClass) { 285 | foreach ($array->getPhpDocPropertiesFromClass() as $typesKey => $typesTmp) { 286 | if ( 287 | ( 288 | $typesKey === $key 289 | || 290 | $typesKey === \Arrayy\Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES 291 | ) 292 | && 293 | \count($typesTmp->getTypes()) === 1 294 | ) { 295 | $array[$key] = $this->map($jsonValue, $typesTmp->getTypes()[0]); 296 | $foundArrayy = true; 297 | 298 | break; 299 | } 300 | } 301 | } 302 | if ($foundArrayy === false) { 303 | $array[$key] = $jsonValue; 304 | } 305 | } 306 | } elseif ($this->isArrayOfType($class)) { 307 | $array[$key] = $this->mapArray( 308 | $jsonValue, 309 | [], 310 | \substr($class, 0, -2) 311 | ); 312 | } elseif ($this->isScalarType(\gettype($jsonValue))) { 313 | // Use constructor parameter if we have a class, but only a flat type (i.e. string, int). 314 | if ($jsonValue === null) { 315 | $array[$key] = null; 316 | } elseif ($this->isSimpleType($class)) { 317 | \settype($jsonValue, $class); 318 | $array[$key] = $jsonValue; 319 | } else { 320 | /** @noinspection PhpSillyAssignmentInspection - phpstan helper */ 321 | /** @phpstan-var class-string $class */ 322 | $class = $class; 323 | $array[$key] = self::createInstance( 324 | $class, 325 | true, 326 | $jsonValue 327 | ); 328 | } 329 | } elseif ($this->isScalarType($class)) { 330 | throw new \InvalidArgumentException( 331 | 'JSON property "' . ($parent_key ?: '?') . '" is an array of type "' . $class . '" but contained a value of type "' . \gettype($jsonValue) . '"' 332 | ); 333 | } elseif (\is_a($class, \ArrayObject::class, true)) { 334 | /** @noinspection PhpSillyAssignmentInspection - phpstan helper */ 335 | /** @phpstan-var \ArrayObject $class */ 336 | $class = $class; 337 | $array[$key] = $this->mapArray( 338 | $jsonValue, 339 | self::createInstance($class) 340 | ); 341 | } else { 342 | /** @noinspection PhpSillyAssignmentInspection - phpstan helper */ 343 | /** @phpstan-var class-string $class */ 344 | $class = $class; 345 | $array[$key] = $this->map( 346 | $jsonValue, 347 | self::createInstance($class, false, $jsonValue) 348 | ); 349 | } 350 | } 351 | 352 | return $array; 353 | } 354 | 355 | /** 356 | * Convert a type name to a fully namespaced type name. 357 | * 358 | * @param string|null $type Type name (simple type or class name) 359 | * @param string $strNs Base namespace that gets prepended to the type name 360 | * 361 | * @return string|null Fully-qualified type name with namespace 362 | */ 363 | private function getFullNamespace($type, $strNs) 364 | { 365 | if ( 366 | $type === null 367 | || 368 | $type === '' 369 | || 370 | $type[0] == '\\' 371 | || 372 | $strNs == '' 373 | ) { 374 | return $type; 375 | } 376 | 377 | list($first) = \explode('[', $type, 2); 378 | if ( 379 | $first === 'mixed' 380 | || 381 | $this->isSimpleType($first) 382 | ) { 383 | return $type; 384 | } 385 | 386 | //create a full qualified namespace 387 | return '\\' . $strNs . '\\' . $type; 388 | } 389 | 390 | /** 391 | * Try to find out if a property exists in a given class. 392 | * Checks property first, falls back to setter method. 393 | * 394 | * @param \ReflectionClass $rc Reflection class to check 395 | * @param string $name Property name 396 | * 397 | * @return array First value: if the property exists 398 | * Second value: the accessor to use ( 399 | * Array-Key-String or ReflectionMethod or ReflectionProperty, or null) 400 | * Third value: type of the property 401 | */ 402 | private function inspectProperty(\ReflectionClass $rc, $name): array 403 | { 404 | // now try to set the property directly, we have to look it up in the class hierarchy 405 | $class = $rc; 406 | $accessor = null; 407 | 408 | /** @var \Arrayy\Arrayy[] $ARRAYY_CACHE */ 409 | /** @phpstan-var array> $ARRAYY_CACHE */ 410 | static $ARRAYY_CACHE = []; 411 | 412 | if (\is_subclass_of($class->name, \Arrayy\Arrayy::class)) { 413 | if (!isset($ARRAYY_CACHE[$class->name])) { 414 | $ARRAYY_CACHE[$class->name] = new $class->name(); 415 | } 416 | 417 | $tmpProps = $ARRAYY_CACHE[$class->name]->getPhpDocPropertiesFromClass(); 418 | if ($tmpProps === []) { 419 | return [true, $name, 'mixed']; 420 | } 421 | 422 | foreach ($tmpProps as $tmpName => $tmpProp) { 423 | if ($tmpName === $name) { 424 | return [true, $name, \implode('|', $tmpProp->getTypes())]; 425 | } 426 | } 427 | } 428 | 429 | do { 430 | if ($class->hasProperty($name)) { 431 | $accessor = $class->getProperty($name); 432 | } 433 | } while ($accessor === null && $class = $class->getParentClass()); 434 | 435 | if ($accessor === null) { 436 | // case-insensitive property matching 437 | foreach ($rc->getProperties() as $p) { 438 | if ((\strcasecmp($p->name, $name) === 0)) { 439 | $accessor = $p; 440 | 441 | break; 442 | } 443 | } 444 | } 445 | 446 | if ($accessor !== null) { 447 | if ($accessor->isPublic()) { 448 | $docblock = $accessor->getDocComment(); 449 | if ($docblock === false) { 450 | return [true, null, null]; 451 | } 452 | 453 | $annotations = self::parseAnnotations($docblock); 454 | 455 | if (!isset($annotations['var'][0])) { 456 | return [true, $accessor, null]; 457 | } 458 | 459 | // support "@var type description" 460 | list($type) = \explode(' ', $annotations['var'][0]); 461 | 462 | return [true, $accessor, $type]; 463 | } 464 | 465 | // no private property 466 | return [true, null, null]; 467 | } 468 | 469 | // no setter, no property 470 | return [false, null, null]; 471 | } 472 | 473 | /** 474 | * Copied from PHPUnit 3.7.29, Util/Test.php 475 | * 476 | * @param string $docblock Full method docblock 477 | * 478 | * @return array 479 | */ 480 | private static function parseAnnotations($docblock): array 481 | { 482 | // init 483 | $annotations = []; 484 | 485 | // Strip away the docblock header and footer 486 | // to ease parsing of one line annotations 487 | $docblock = \substr($docblock, 3, -2); 488 | 489 | $re = '/@(?P[A-Za-z_-]+)(?:[ \t]+(?P.*?))?[ \t]*\r?$/m'; 490 | if (\preg_match_all($re, $docblock, $matches)) { 491 | $numMatches = \count($matches[0]); 492 | 493 | for ($i = 0; $i < $numMatches; ++$i) { 494 | $annotations[$matches['name'][$i]][] = $matches['value'][$i]; 495 | } 496 | } 497 | 498 | return $annotations; 499 | } 500 | 501 | /** 502 | * Removes - and _ and makes the next letter uppercase 503 | * 504 | * @param string $name Property name 505 | * 506 | * @return string CamelCasedVariableName 507 | */ 508 | private function getCamelCaseName($name): string 509 | { 510 | return \str_replace( 511 | ' ', 512 | '', 513 | \ucwords(\str_replace(['_', '-'], ' ', $name)) 514 | ); 515 | } 516 | 517 | /** 518 | * Since hyphens cannot be used in variables we have to uppercase them. 519 | * 520 | * Technically you may use them, but they are awkward to access. 521 | * 522 | * @param string $name Property name 523 | * 524 | * @return string Name without hyphen 525 | */ 526 | private function getSafeName($name): string 527 | { 528 | $convertHyphens = \strpos($name, '-') !== false; 529 | $convertSnake = \strpos($name, '_') !== false; 530 | 531 | if ($convertHyphens || $convertSnake) { 532 | $name = $this->getCamelCaseName($name); 533 | } 534 | 535 | return $name; 536 | } 537 | 538 | /** 539 | * Set a property on a given object to a given value. 540 | * 541 | * Checks if the setter or the property are public are made before 542 | * calling this method. 543 | * 544 | * @param \Arrayy\Arrayy|object $object Object to set property on 545 | * @param \ReflectionMethod|\ReflectionProperty|string $accessor Array-Key-String or ReflectionMethod or ReflectionProperty 546 | * @param mixed $value Value of property 547 | * 548 | * @return void 549 | */ 550 | private function setProperty( 551 | $object, 552 | $accessor, 553 | $value 554 | ) { 555 | if (\is_string($accessor) && $object instanceof \Arrayy\Arrayy) { 556 | $object[$accessor] = $value; 557 | } elseif ($accessor instanceof \ReflectionProperty) { 558 | $accessor->setValue($object, $value); 559 | } elseif ($accessor instanceof \ReflectionMethod) { 560 | // setter method 561 | $accessor->invoke($object, $value); 562 | } 563 | } 564 | 565 | /** 566 | * Get the mapped class/type name for this class. 567 | * Returns the incoming classname if not mapped. 568 | * 569 | * @param string|null $type Type name to map 570 | * @param mixed $jsonValue Constructor parameter (the json value) 571 | * 572 | * @return string|null The mapped type/class name 573 | * 574 | * @phpstan-return class-string|string|null 575 | */ 576 | private function getMappedType($type, $jsonValue = null) 577 | { 578 | if (isset($this->classMap[$type])) { 579 | $target = $this->classMap[$type]; 580 | } elseif ( 581 | \is_string($type) 582 | && 583 | $type !== '' 584 | && 585 | $type[0] == '\\' 586 | && 587 | isset($this->classMap[\substr($type, 1)]) 588 | ) { 589 | $target = $this->classMap[\substr($type, 1)]; 590 | } else { 591 | $target = null; 592 | } 593 | 594 | if ($target) { 595 | if (\is_callable($target)) { 596 | $type = $target($type, $jsonValue); 597 | } else { 598 | $type = $target; 599 | } 600 | } 601 | 602 | return $type; 603 | } 604 | 605 | /** 606 | * Checks if the given type is a "simple type" 607 | * 608 | * @param string $type type name from gettype() 609 | * 610 | * @return bool True if it is a simple PHP type 611 | * 612 | * @see isScalarType() 613 | */ 614 | private function isSimpleType($type): bool 615 | { 616 | if (\strpos($type, '|') !== false) { 617 | foreach (\explode('|', $type) as $tmpType) { 618 | if ($this->isSimpleType($tmpType)) { 619 | return true; 620 | } 621 | } 622 | } 623 | 624 | /** @noinspection InArrayCanBeUsedInspection */ 625 | return $type == 'string' 626 | || $type == 'boolean' || $type == 'bool' 627 | || $type == 'integer' || $type == 'int' || $type == 'int' 628 | || $type == 'double' || $type == 'float' 629 | || $type == 'array' || $type == 'object'; 630 | } 631 | 632 | /** 633 | * Checks if the object is of this type or has this type as one of its parents 634 | * 635 | * @param string $type class name of type being required 636 | * @param mixed $value Some PHP value to be tested 637 | * 638 | * @return bool True if $object has type of $type 639 | */ 640 | private function isObjectOfSameType($type, $value): bool 641 | { 642 | if (\is_object($value) === false) { 643 | return false; 644 | } 645 | 646 | return \is_a($value, $type); 647 | } 648 | 649 | /** 650 | * Checks if the given type is a type that is not nested 651 | * (simple type except array and object) 652 | * 653 | * @param string $type type name from gettype() 654 | * 655 | * @return bool True if it is a non-nested PHP type 656 | * 657 | * @see isSimpleType() 658 | */ 659 | private function isScalarType($type): bool 660 | { 661 | /** @noinspection InArrayCanBeUsedInspection */ 662 | return $type == 'NULL' 663 | || $type == 'string' 664 | || $type == 'boolean' || $type == 'bool' 665 | || $type == 'integer' || $type == 'int' 666 | || $type == 'double' || $type == 'float'; 667 | } 668 | 669 | /** 670 | * Returns true if type is an array of elements 671 | * (bracket notation) 672 | * 673 | * @param string $strType type to be matched 674 | * 675 | * @return bool 676 | */ 677 | private function isArrayOfType($strType): bool 678 | { 679 | return \substr($strType, -2) === '[]'; 680 | } 681 | 682 | /** 683 | * Checks if the given type is nullable 684 | * 685 | * @param string $type type name from the phpdoc param 686 | * 687 | * @return bool True if it is nullable 688 | */ 689 | private function isNullable($type): bool 690 | { 691 | return \stripos('|' . $type . '|', '|null|') !== false; 692 | } 693 | 694 | /** 695 | * Remove the 'null' section of a type 696 | * 697 | * @param false|string|null $type type name from the phpdoc param 698 | * 699 | * @return string|null The new type value 700 | */ 701 | private function removeNullable($type) 702 | { 703 | if ($type === null || $type === false) { 704 | return null; 705 | } 706 | 707 | return \substr( 708 | \str_ireplace('|null|', '|', '|' . $type . '|'), 709 | 1, 710 | -1 711 | ); 712 | } 713 | 714 | /** 715 | * Create a new object of the given type. 716 | * 717 | * This method exists to be overwritten in child classes, 718 | * so you can do dependency injection or so. 719 | * 720 | * @param object|string $class 721 | *

Class name to instantiate

722 | * @param bool $useParameter 723 | *

Pass $parameter to the constructor or not

724 | * @param mixed $jsonValue 725 | *

Constructor parameter (the json value)

726 | * 727 | * @return object 728 | *

Freshly created object

729 | * 730 | * @internal 731 | * 732 | * @template TClass 733 | * @phpstan-param TClass|class-string $class 734 | * @phpstan-return TClass 735 | */ 736 | private static function createInstance( 737 | $class, 738 | bool $useParameter = false, 739 | $jsonValue = null 740 | ) { 741 | if ($useParameter) { 742 | /** @phpstan-var TClass $return */ 743 | $return = new $class($jsonValue); 744 | 745 | return $return; 746 | } 747 | 748 | $reflectClass = new \ReflectionClass($class); 749 | $constructor = $reflectClass->getConstructor(); 750 | if ( 751 | $constructor === null 752 | || 753 | $constructor->getNumberOfRequiredParameters() > 0 754 | ) { 755 | /** @phpstan-var TClass $return */ 756 | $return = $reflectClass->newInstanceWithoutConstructor(); 757 | } else { 758 | /** @phpstan-var TClass $return */ 759 | $return = $reflectClass->newInstance(); 760 | } 761 | 762 | return $return; 763 | } 764 | } 765 | -------------------------------------------------------------------------------- /src/StaticArrayy.php: -------------------------------------------------------------------------------- 1 | getMethods(\ReflectionMethod::IS_PUBLIC); 37 | 38 | foreach ($methods as $method) { 39 | $params = $method->getNumberOfParameters() + 2; 40 | static::$methodArgs[$method->name] = $params; 41 | } 42 | } 43 | 44 | if (!isset(static::$methodArgs[$name])) { 45 | throw new \BadMethodCallException($name . ' is not a valid method'); 46 | } 47 | 48 | $numArgs = \count($arguments); 49 | $array = $numArgs ? $arguments[0] : ''; 50 | 51 | if ($numArgs === static::$methodArgs[$name]) { 52 | $args = \array_slice($arguments, 1, -1); 53 | } else { 54 | $args = \array_slice($arguments, 1); 55 | } 56 | 57 | $arrayy = Arrayy::create($array); 58 | 59 | return \call_user_func_array([$arrayy, $name], $args); 60 | } 61 | 62 | //////////////////////////////////////////////////////////////////// 63 | ///////////////////////////// GENERATE ///////////////////////////// 64 | //////////////////////////////////////////////////////////////////// 65 | 66 | /** 67 | * Generate an array from a range. 68 | * 69 | * @param int $base The base number 70 | * @param int|null $stop The stopping point 71 | * @param int $step How many to increment of 72 | * 73 | * @return Arrayy 74 | * 75 | * @psalm-suppress InvalidReturnStatement - why? 76 | * @psalm-suppress InvalidReturnType - why? 77 | */ 78 | public static function range(int $base, int $stop = null, int $step = 1): Arrayy 79 | { 80 | if ($stop !== null) { 81 | $start = $base; 82 | } else { 83 | $start = 1; 84 | $stop = $base; 85 | } 86 | 87 | return Arrayy::create(\range($start, $stop, $step)); 88 | } 89 | 90 | /** 91 | * Fill an array with $times times some $data. 92 | * 93 | * @param float|int|string|null $data 94 | * @param int $times 95 | * 96 | * @return Arrayy 97 | * 98 | * @psalm-suppress InvalidReturnStatement - why? 99 | * @psalm-suppress InvalidReturnType - why? 100 | */ 101 | public static function repeat($data, int $times): Arrayy 102 | { 103 | if ($times === 0 || empty($data)) { 104 | return Arrayy::create(); 105 | } 106 | 107 | return Arrayy::create(\array_fill(0, $times, $data)); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Type/ArrayCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ArrayCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'array'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/BoolArrayCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class BoolArrayCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'bool[]'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/BoolCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class BoolCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'bool'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/CallableCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class CallableCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'callable'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/DetectFirstValueTypeCollection.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class DetectFirstValueTypeCollection extends Collection implements TypeInterface 18 | { 19 | /** 20 | * @var string 21 | */ 22 | private $getTypeHelper; 23 | 24 | /** 25 | * @param array|Arrayy|mixed $data 26 | * @param string $iteratorClass 27 | * @param bool $checkPropertiesInConstructor 28 | * 29 | * @phpstan-param array|Arrayy $data 30 | * @phpstan-param class-string<\Arrayy\ArrayyIterator> $iteratorClass 31 | */ 32 | public function __construct( 33 | $data = [], 34 | string $iteratorClass = ArrayyIterator::class, 35 | bool $checkPropertiesInConstructor = true 36 | ) { 37 | /** 38 | * @psalm-suppress RedundantConditionGivenDocblockType - We also allow other types here, 39 | * but I don't know how to tell psalm about that. :/ 40 | */ 41 | if ($data instanceof Arrayy) { 42 | $firstValue = $data->first(); 43 | } elseif (\is_array($data)) { 44 | $firstValue = array_first($data); 45 | } else { 46 | $firstValue = $data; 47 | $data = [$data]; 48 | } 49 | 50 | $this->getTypeHelper = $this->getTypeFromFirstValue($firstValue); 51 | 52 | parent::__construct( 53 | $data, 54 | $iteratorClass, 55 | $checkPropertiesInConstructor 56 | ); 57 | } 58 | 59 | /** 60 | * The type (FQCN) associated with this collection. 61 | * 62 | * @return string 63 | */ 64 | public function getType() 65 | { 66 | return $this->getTypeHelper; 67 | } 68 | 69 | /** 70 | * @param mixed $value 71 | * 72 | * @return string 73 | */ 74 | private function getTypeFromFirstValue($value): string 75 | { 76 | return \is_object($value) ? \get_class($value) : \gettype($value); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Type/FloatArrayCollection.php: -------------------------------------------------------------------------------- 1 | > 11 | */ 12 | final class FloatArrayCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'float[]'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/FloatCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class FloatCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'float'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/FloatIntArrayCollection.php: -------------------------------------------------------------------------------- 1 | > 11 | */ 12 | final class FloatIntArrayCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'float[]|int[]'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/FloatIntCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class FloatIntCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'float|int'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/InstanceCollection.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class InstanceCollection extends Collection implements TypeInterface 16 | { 17 | /** 18 | * @param array $data 19 | * @param string|null $iteratorClass 20 | * @param bool|null $checkPropertiesInConstructor 21 | * @param string|null $className 22 | * 23 | * @phpstan-param array $data 24 | * @phpstan-param class-string<\Arrayy\ArrayyIterator>|null $iteratorClass 25 | * @phpstan-param class-string|null $className 26 | */ 27 | public function __construct( 28 | array $data = [], 29 | string $iteratorClass = null, 30 | bool $checkPropertiesInConstructor = null, 31 | $className = null 32 | ) { 33 | // fallback 34 | if ($iteratorClass === null) { 35 | $iteratorClass = ArrayyIterator::class; 36 | } 37 | if ($checkPropertiesInConstructor === null) { 38 | $checkPropertiesInConstructor = true; 39 | } 40 | 41 | parent::__construct( 42 | $data, 43 | $iteratorClass, 44 | $checkPropertiesInConstructor, 45 | self::convertIntoTypeCheckArray($className) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Type/InstancesCollection.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class InstancesCollection extends Collection implements TypeInterface 16 | { 17 | /** 18 | * @param array $data 19 | * @param string|null $iteratorClass 20 | * @param bool|null $checkPropertiesInConstructor 21 | * @param string[]|null $classNames 22 | * 23 | * @phpstan-param array $data 24 | * @phpstan-param class-string<\Arrayy\ArrayyIterator>|null $iteratorClass 25 | * @phpstan-param array>|null $classNames 26 | */ 27 | public function __construct( 28 | array $data = [], 29 | string $iteratorClass = null, 30 | bool $checkPropertiesInConstructor = null, 31 | array $classNames = null 32 | ) { 33 | // fallback 34 | if ($iteratorClass === null) { 35 | $iteratorClass = ArrayyIterator::class; 36 | } 37 | if ($checkPropertiesInConstructor === null) { 38 | $checkPropertiesInConstructor = true; 39 | } 40 | 41 | parent::__construct( 42 | $data, 43 | $iteratorClass, 44 | $checkPropertiesInConstructor, 45 | self::convertIntoTypeCheckArray($classNames) 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Type/IntArrayCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class IntArrayCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'int[]'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/IntCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class IntCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'int'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/JsonSerializableCollection.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class JsonSerializableCollection extends Collection implements TypeInterface 14 | { 15 | /** 16 | * The type (FQCN) associated with this collection. 17 | * 18 | * @return string 19 | * 20 | * @phpstan-return class-string<\JsonSerializable> 21 | */ 22 | public function getType() 23 | { 24 | return \JsonSerializable::class; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Type/MixedCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class MixedCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'mixed'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/NonEmptyStringCollection.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class NonEmptyStringCollection extends Collection implements TypeInterface 17 | { 18 | /** 19 | * @return TypeCheckArray 20 | */ 21 | public function getType() 22 | { 23 | /** @phpstan-var TypeCheckArray $return */ 24 | $return = TypeCheckArray::create( 25 | [ 26 | Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES => new TypeCheckCallback( 27 | static function ($value) { 28 | return \is_string($value) && $value !== ''; 29 | } 30 | ), 31 | ] 32 | ); 33 | 34 | return $return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Type/NumericCollection.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class NumericCollection extends Collection implements TypeInterface 17 | { 18 | /** 19 | * @return TypeCheckArray 20 | */ 21 | public function getType() 22 | { 23 | /** @phpstan-var TypeCheckArray $return */ 24 | $return = TypeCheckArray::create( 25 | [ 26 | Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES => new TypeCheckCallback( 27 | static function ($value) { 28 | return \is_numeric($value); 29 | } 30 | ), 31 | ] 32 | ); 33 | 34 | return $return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Type/NumericStringCollection.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class NumericStringCollection extends Collection implements TypeInterface 17 | { 18 | /** 19 | * @return TypeCheckArray 20 | */ 21 | public function getType() 22 | { 23 | /** @phpstan-var TypeCheckArray $return */ 24 | $return = TypeCheckArray::create( 25 | [ 26 | Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES => new TypeCheckCallback( 27 | static function ($value) { 28 | return \is_string($value) && \is_numeric($value); 29 | } 30 | ), 31 | ] 32 | ); 33 | 34 | return $return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Type/ObjectCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ObjectCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'object'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/ResourceCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ResourceCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'resource'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/ScalarCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ScalarCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'scalar'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/StdClassCollection.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StdClassCollection extends Collection implements TypeInterface 14 | { 15 | /** 16 | * The type (FQCN) associated with this collection. 17 | * 18 | * @return string 19 | * 20 | * @phpstan-return class-string<\stdClass> 21 | */ 22 | public function getType() 23 | { 24 | return \stdClass::class; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Type/StringArrayCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class StringArrayCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'string[]'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/StringCollection.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class StringCollection extends Collection implements TypeInterface 13 | { 14 | /** 15 | * The type (FQCN) associated with this collection. 16 | * 17 | * @return string 18 | */ 19 | public function getType() 20 | { 21 | return 'string'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Type/TypeInterface.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | private static $typeMapping = [ 23 | 'int' => 'integer', 24 | 'bool' => 'boolean', 25 | 'float' => 'double', 26 | ]; 27 | 28 | /** 29 | * @return string[] 30 | */ 31 | public function getTypes(): array 32 | { 33 | return $this->types; 34 | } 35 | 36 | /** 37 | * @param mixed $value 38 | * 39 | * @return bool 40 | */ 41 | public function checkType(&$value): bool 42 | { 43 | if ($this->isNullable && $value === null) { 44 | return true; 45 | } 46 | 47 | foreach ($this->types as $currentType) { 48 | $isValidType = $this->assertTypeEquals($currentType, $value); 49 | 50 | if ($isValidType) { 51 | return true; 52 | } 53 | } 54 | 55 | $type = \gettype($value); 56 | 57 | $expectedTypes = \implode('|', $this->types); 58 | 59 | $this->throwException($expectedTypes, $value, $type); 60 | 61 | return false; 62 | } 63 | 64 | /** 65 | * @param string $type 66 | * @param mixed $value 67 | * 68 | * @return bool 69 | */ 70 | protected function assertTypeEquals(string $type, &$value): bool 71 | { 72 | if (\strpos($type, '[]') !== false) { 73 | return $this->isValidGenericCollection($type, $value); 74 | } 75 | 76 | if ($type === 'mixed' && $value !== null) { 77 | return true; 78 | } 79 | 80 | return $value instanceof $type 81 | || 82 | \gettype($value) === (self::$typeMapping[$type] ?? $type) 83 | || 84 | ( 85 | $type === 'scalar' 86 | && 87 | \is_scalar($value) 88 | ) 89 | || 90 | ( 91 | $type === 'callable' 92 | && 93 | \is_callable($value) 94 | ) 95 | || 96 | ( 97 | $type === 'numeric' 98 | && 99 | ( 100 | \is_float($value) 101 | || 102 | \is_int($value) 103 | ) 104 | ) 105 | || 106 | ( 107 | $type === 'resource' 108 | && 109 | \is_resource($value) 110 | ); 111 | } 112 | 113 | /** 114 | * @param mixed $value 115 | * 116 | * @return string 117 | */ 118 | protected function valueToString($value): string 119 | { 120 | // null 121 | if ($value === null) { 122 | return 'NULL'; 123 | } 124 | 125 | // bool 126 | if (\is_bool($value)) { 127 | return $value ? 'TRUE' : 'FALSE'; 128 | } 129 | 130 | // array 131 | if (\is_array($value)) { 132 | return 'Array'; 133 | } 134 | 135 | // scalar types (integer, float, string) 136 | if (\is_scalar($value)) { 137 | return (string) $value; 138 | } 139 | 140 | // resource 141 | if (\is_resource($value)) { 142 | return \get_resource_type($value) . ' resource #' . (int) $value; 143 | } 144 | 145 | if (\is_object($value)) { 146 | return \get_class($value) . ' Object'; 147 | } 148 | 149 | return ''; 150 | } 151 | 152 | /** 153 | * @param string $type 154 | * @param mixed $collection 155 | * 156 | * @return bool 157 | */ 158 | private function isValidGenericCollection(string $type, &$collection): bool 159 | { 160 | if (!\is_array($collection)) { 161 | return false; 162 | } 163 | 164 | $valueType = \str_replace('[]', '', $type); 165 | 166 | foreach ($collection as $value) { 167 | if ($this->assertTypeEquals($valueType, $value)) { 168 | return true; 169 | } 170 | } 171 | 172 | return false; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/TypeCheck/TypeCheckArray.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class TypeCheckArray extends \Arrayy\ArrayyStrict 14 | { 15 | /** 16 | * Initializes 17 | * 18 | * @param mixed $data

19 | * Should be an array or a generator, otherwise it will try 20 | * to convert it into an array. 21 | *

22 | * @param string $iteratorClass optional

23 | * You can overwrite the ArrayyIterator, but mostly you don't 24 | * need this option. 25 | *

26 | * @param bool $checkPropertiesInConstructor optional

27 | * You need to extend the "Arrayy"-class and you need to set 28 | * the $checkPropertiesMismatchInConstructor class property 29 | * to 30 | * true, otherwise this option didn't not work anyway. 31 | *

32 | * 33 | * @phpstan-param class-string<\Arrayy\ArrayyIterator> $iteratorClass 34 | */ 35 | public function __construct( 36 | $data = [], 37 | string $iteratorClass = ArrayyIterator::class, 38 | bool $checkPropertiesInConstructor = true 39 | ) { 40 | $this->properties[Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES] = new TypeCheckSimple(TypeCheckInterface::class); 41 | 42 | parent::__construct($data, $iteratorClass, $checkPropertiesInConstructor); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/TypeCheck/TypeCheckCallback.php: -------------------------------------------------------------------------------- 1 | callable = $callable; 29 | 30 | $this->isNullable = $isNullable; 31 | } 32 | 33 | /** 34 | * @param mixed $value 35 | * 36 | * @return bool 37 | */ 38 | public function checkType(&$value): bool 39 | { 40 | if ($this->isNullable && $value === null) { 41 | return true; 42 | } 43 | 44 | if (\call_user_func($this->callable, $value)) { 45 | return true; 46 | } 47 | 48 | $this->throwException('', $value, \gettype($value)); 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * @return array 55 | */ 56 | public function getTypes(): array 57 | { 58 | return []; 59 | } 60 | 61 | /** 62 | * @param string $expectedTypes 63 | * @param mixed $value 64 | * @param string $type 65 | * 66 | * @return \TypeError 67 | */ 68 | public function throwException($expectedTypes, $value, $type): \Throwable 69 | { 70 | throw new \TypeError('Invalid type: callable failed, got value `' . \print_r($value, true) . "` with type {{$type}}."); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/TypeCheck/TypeCheckInterface.php: -------------------------------------------------------------------------------- 1 | property_name = $reflectionPropertyName; 35 | } 36 | 37 | /** 38 | * @param \phpDocumentor\Reflection\DocBlock\Tags\Property $phpDocumentorReflectionProperty 39 | * @param string $property 40 | * 41 | * @return self|null 42 | */ 43 | public static function fromPhpDocumentorProperty(\phpDocumentor\Reflection\DocBlock\Tags\Property $phpDocumentorReflectionProperty, string $property = '') 44 | { 45 | if (!$property) { 46 | /** @var string|null $propertyTmp */ 47 | $propertyTmp = $phpDocumentorReflectionProperty->getVariableName(); 48 | if ($propertyTmp === null) { 49 | return null; 50 | } 51 | 52 | $property = $propertyTmp; 53 | } 54 | 55 | $tmpObject = new \stdClass(); 56 | $tmpObject->{$property} = null; 57 | 58 | $tmpReflection = new self((new \ReflectionProperty($tmpObject, $property))->getName()); 59 | 60 | $type = $phpDocumentorReflectionProperty->getType(); 61 | 62 | /** @noinspection PhpSillyAssignmentInspection */ 63 | /** @var Type|null $type */ 64 | $type = $type; 65 | 66 | if ($type) { 67 | $tmpReflection->hasTypeDeclaration = true; 68 | 69 | $docTypes = self::parseDocTypeObject($type); 70 | if (\is_array($docTypes) === true) { 71 | foreach ($docTypes as $docType) { 72 | $tmpReflection->types[] = $docType; 73 | } 74 | } else { 75 | $tmpReflection->types[] = $docTypes; 76 | } 77 | 78 | if (\in_array('null', $tmpReflection->types, true)) { 79 | $tmpReflection->isNullable = true; 80 | } 81 | } 82 | 83 | return $tmpReflection; 84 | } 85 | 86 | /** 87 | * @param \phpDocumentor\Reflection\Type $type 88 | * 89 | * @return string|string[] 90 | */ 91 | public static function parseDocTypeObject($type) 92 | { 93 | if ($type instanceof \phpDocumentor\Reflection\Types\Object_) { 94 | $tmpObject = (string) $type->getFqsen(); 95 | if ($tmpObject) { 96 | return $tmpObject; 97 | } 98 | 99 | return 'object'; 100 | } 101 | 102 | if ($type instanceof \phpDocumentor\Reflection\Types\Compound) { 103 | $types = []; 104 | foreach ($type as $subType) { 105 | $typeTmp = self::parseDocTypeObject($subType); 106 | 107 | /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */ 108 | /** @var string $typeTmp */ 109 | $typeTmp = $typeTmp; 110 | 111 | $types[] = $typeTmp; 112 | } 113 | 114 | return $types; 115 | } 116 | 117 | if ($type instanceof \phpDocumentor\Reflection\Types\Array_) { 118 | $valueTypeTmp = $type->getValueType()->__toString(); 119 | if ($valueTypeTmp !== 'mixed') { 120 | return $valueTypeTmp . '[]'; 121 | } 122 | 123 | return 'array'; 124 | } 125 | 126 | if ($type instanceof \phpDocumentor\Reflection\Types\Null_) { 127 | return 'null'; 128 | } 129 | 130 | if ($type instanceof \phpDocumentor\Reflection\Types\Mixed_) { 131 | return 'mixed'; 132 | } 133 | 134 | if ($type instanceof \phpDocumentor\Reflection\Types\Scalar) { 135 | return 'string|int|float|bool'; 136 | } 137 | 138 | if ($type instanceof \phpDocumentor\Reflection\Types\Boolean) { 139 | return 'bool'; 140 | } 141 | 142 | if ($type instanceof \phpDocumentor\Reflection\Types\Callable_) { 143 | return 'callable'; 144 | } 145 | 146 | if ($type instanceof \phpDocumentor\Reflection\Types\Float_) { 147 | return 'float'; 148 | } 149 | 150 | if ($type instanceof \phpDocumentor\Reflection\Types\String_) { 151 | return 'string'; 152 | } 153 | 154 | if ($type instanceof \phpDocumentor\Reflection\Types\Integer) { 155 | return 'int'; 156 | } 157 | 158 | if ($type instanceof \phpDocumentor\Reflection\Types\Void_) { 159 | return 'void'; 160 | } 161 | 162 | if ($type instanceof \phpDocumentor\Reflection\Types\Resource_) { 163 | return 'resource'; 164 | } 165 | 166 | return $type->__toString(); 167 | } 168 | 169 | /** 170 | * @param string $expectedTypes 171 | * @param mixed $value 172 | * @param string $type 173 | * 174 | * @return \TypeError 175 | */ 176 | public function throwException($expectedTypes, $value, $type): \Throwable 177 | { 178 | throw new \TypeError("Invalid type: expected \"{$this->property_name}\" to be of type {{$expectedTypes}}, instead got value \"" . $this->valueToString($value) . '" (' . \print_r($value, true) . ") with type {{$type}}."); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/TypeCheck/TypeCheckSimple.php: -------------------------------------------------------------------------------- 1 | getTypesHelper($type); 18 | 19 | $this->isNullable = $isNullable; 20 | } 21 | 22 | /** 23 | * @param string $expectedTypes 24 | * @param mixed $value 25 | * @param string $type 26 | * 27 | * @return \TypeError 28 | */ 29 | public function throwException($expectedTypes, $value, $type): \Throwable 30 | { 31 | throw new \TypeError("Invalid type: expected to be of type {{$expectedTypes}}, instead got value `" . \print_r($value, true) . "` with type {{$type}}."); 32 | } 33 | 34 | /** 35 | * @param string|string[] $type 36 | * 37 | * @return void 38 | */ 39 | protected function getTypesHelper($type) 40 | { 41 | if (\is_array($type)) { 42 | foreach ($type as $typeTmp) { 43 | $this->getTypesHelper($typeTmp); 44 | } 45 | 46 | return; 47 | } 48 | 49 | if (\strpos($type, '|') !== false) { 50 | $typesTmp = \explode('|', $type); 51 | 52 | foreach ($typesTmp as $typeTmp) { 53 | $this->types[] = $typeTmp; 54 | } 55 | } else { 56 | $this->types[] = $type; 57 | } 58 | } 59 | } 60 | --------------------------------------------------------------------------------