├── .editorconfig ├── .gitattributes ├── .gitignore ├── composer.json ├── composer.lock ├── license.md ├── phpcs.xml ├── phpunit.xml ├── readme.md └── src ├── Attributes └── PostMapping.php ├── Exceptions ├── CouldNotMapValueException.php ├── CouldNotResolveClassException.php ├── InvalidTypeException.php ├── UnexpectedNullValueException.php └── UnexpectedTokenException.php ├── Mapper.php ├── MapperCommands.php ├── MapperConfig.php ├── MapsItself.php ├── Objects ├── ClassBluePrint.php ├── ClassBluePrinter.php ├── ClassResolver.php ├── DocBlock.php ├── DocBlockParser.php └── ObjectMapper.php └── Types ├── DataType.php ├── DataTypeCollection.php └── DataTypeFactory.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .github/ export-ignore 2 | tests/ export-ignore 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | jmapper_* 4 | .phpunit.result.cache 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jerodev/data-mapper", 3 | "description": "Maps raw data to a typed PHP object", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Jeroen Deviaene", 9 | "email": "jeroen@deviaene.eu" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Jerodev\\DataMapper\\": "src/" 15 | } 16 | }, 17 | "autoload-dev": { 18 | "psr-4": { 19 | "Jerodev\\DataMapper\\Tests\\": "tests/" 20 | } 21 | }, 22 | "repositories": [ 23 | { 24 | "type": "vcs", 25 | "url": "git@github.com:jerodev/code-styles.git" 26 | } 27 | ], 28 | "require": { 29 | "php": "^8.1" 30 | }, 31 | "require-dev": { 32 | "jerodev/code-styles": "dev-master", 33 | "phpunit/phpunit": "^9.4" 34 | }, 35 | "minimum-stability": "dev", 36 | "prefer-stable": true, 37 | "config": { 38 | "sort-packages": true, 39 | "allow-plugins": { 40 | "dealerdirect/phpcodesniffer-composer-installer": true 41 | } 42 | }, 43 | "scripts": { 44 | "post-autoload-dump": [ 45 | "Jerodev\\DataMapper\\MapperCommands::clearCache" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "7b010f8270a95cc28b36c706e8fddfeb", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "dealerdirect/phpcodesniffer-composer-installer", 12 | "version": "v1.0.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/PHPCSStandards/composer-installer.git", 16 | "reference": "4be43904336affa5c2f70744a348312336afd0da" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", 21 | "reference": "4be43904336affa5c2f70744a348312336afd0da", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "composer-plugin-api": "^1.0 || ^2.0", 26 | "php": ">=5.4", 27 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" 28 | }, 29 | "require-dev": { 30 | "composer/composer": "*", 31 | "ext-json": "*", 32 | "ext-zip": "*", 33 | "php-parallel-lint/php-parallel-lint": "^1.3.1", 34 | "phpcompatibility/php-compatibility": "^9.0", 35 | "yoast/phpunit-polyfills": "^1.0" 36 | }, 37 | "type": "composer-plugin", 38 | "extra": { 39 | "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" 44 | } 45 | }, 46 | "notification-url": "https://packagist.org/downloads/", 47 | "license": [ 48 | "MIT" 49 | ], 50 | "authors": [ 51 | { 52 | "name": "Franck Nijhof", 53 | "email": "franck.nijhof@dealerdirect.com", 54 | "homepage": "http://www.frenck.nl", 55 | "role": "Developer / IT Manager" 56 | }, 57 | { 58 | "name": "Contributors", 59 | "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" 60 | } 61 | ], 62 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin", 63 | "homepage": "http://www.dealerdirect.com", 64 | "keywords": [ 65 | "PHPCodeSniffer", 66 | "PHP_CodeSniffer", 67 | "code quality", 68 | "codesniffer", 69 | "composer", 70 | "installer", 71 | "phpcbf", 72 | "phpcs", 73 | "plugin", 74 | "qa", 75 | "quality", 76 | "standard", 77 | "standards", 78 | "style guide", 79 | "stylecheck", 80 | "tests" 81 | ], 82 | "support": { 83 | "issues": "https://github.com/PHPCSStandards/composer-installer/issues", 84 | "source": "https://github.com/PHPCSStandards/composer-installer" 85 | }, 86 | "time": "2023-01-05T11:28:13+00:00" 87 | }, 88 | { 89 | "name": "doctrine/instantiator", 90 | "version": "2.0.0", 91 | "source": { 92 | "type": "git", 93 | "url": "https://github.com/doctrine/instantiator.git", 94 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" 95 | }, 96 | "dist": { 97 | "type": "zip", 98 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 99 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 100 | "shasum": "" 101 | }, 102 | "require": { 103 | "php": "^8.1" 104 | }, 105 | "require-dev": { 106 | "doctrine/coding-standard": "^11", 107 | "ext-pdo": "*", 108 | "ext-phar": "*", 109 | "phpbench/phpbench": "^1.2", 110 | "phpstan/phpstan": "^1.9.4", 111 | "phpstan/phpstan-phpunit": "^1.3", 112 | "phpunit/phpunit": "^9.5.27", 113 | "vimeo/psalm": "^5.4" 114 | }, 115 | "type": "library", 116 | "autoload": { 117 | "psr-4": { 118 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 119 | } 120 | }, 121 | "notification-url": "https://packagist.org/downloads/", 122 | "license": [ 123 | "MIT" 124 | ], 125 | "authors": [ 126 | { 127 | "name": "Marco Pivetta", 128 | "email": "ocramius@gmail.com", 129 | "homepage": "https://ocramius.github.io/" 130 | } 131 | ], 132 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 133 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 134 | "keywords": [ 135 | "constructor", 136 | "instantiate" 137 | ], 138 | "support": { 139 | "issues": "https://github.com/doctrine/instantiator/issues", 140 | "source": "https://github.com/doctrine/instantiator/tree/2.0.0" 141 | }, 142 | "funding": [ 143 | { 144 | "url": "https://www.doctrine-project.org/sponsorship.html", 145 | "type": "custom" 146 | }, 147 | { 148 | "url": "https://www.patreon.com/phpdoctrine", 149 | "type": "patreon" 150 | }, 151 | { 152 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 153 | "type": "tidelift" 154 | } 155 | ], 156 | "time": "2022-12-30T00:23:10+00:00" 157 | }, 158 | { 159 | "name": "jerodev/code-styles", 160 | "version": "dev-master", 161 | "source": { 162 | "type": "git", 163 | "url": "https://github.com/jerodev/code-styles.git", 164 | "reference": "3a0d433404bc5d9f398619d28130affcf25eca76" 165 | }, 166 | "dist": { 167 | "type": "zip", 168 | "url": "https://api.github.com/repos/jerodev/code-styles/zipball/3a0d433404bc5d9f398619d28130affcf25eca76", 169 | "reference": "3a0d433404bc5d9f398619d28130affcf25eca76", 170 | "shasum": "" 171 | }, 172 | "require": { 173 | "slevomat/coding-standard": "^8.0", 174 | "squizlabs/php_codesniffer": "^3.6" 175 | }, 176 | "default-branch": true, 177 | "type": "library", 178 | "license": [ 179 | "private" 180 | ], 181 | "authors": [ 182 | { 183 | "name": "jerodev", 184 | "email": "jeroen@deviaene.eu" 185 | } 186 | ], 187 | "description": "phpcs configurations for projects", 188 | "support": { 189 | "source": "https://github.com/jerodev/code-styles/tree/master", 190 | "issues": "https://github.com/jerodev/code-styles/issues" 191 | }, 192 | "time": "2023-05-10T18:43:22+00:00" 193 | }, 194 | { 195 | "name": "myclabs/deep-copy", 196 | "version": "1.11.1", 197 | "source": { 198 | "type": "git", 199 | "url": "https://github.com/myclabs/DeepCopy.git", 200 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" 201 | }, 202 | "dist": { 203 | "type": "zip", 204 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 205 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 206 | "shasum": "" 207 | }, 208 | "require": { 209 | "php": "^7.1 || ^8.0" 210 | }, 211 | "conflict": { 212 | "doctrine/collections": "<1.6.8", 213 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 214 | }, 215 | "require-dev": { 216 | "doctrine/collections": "^1.6.8", 217 | "doctrine/common": "^2.13.3 || ^3.2.2", 218 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 219 | }, 220 | "type": "library", 221 | "autoload": { 222 | "files": [ 223 | "src/DeepCopy/deep_copy.php" 224 | ], 225 | "psr-4": { 226 | "DeepCopy\\": "src/DeepCopy/" 227 | } 228 | }, 229 | "notification-url": "https://packagist.org/downloads/", 230 | "license": [ 231 | "MIT" 232 | ], 233 | "description": "Create deep copies (clones) of your objects", 234 | "keywords": [ 235 | "clone", 236 | "copy", 237 | "duplicate", 238 | "object", 239 | "object graph" 240 | ], 241 | "support": { 242 | "issues": "https://github.com/myclabs/DeepCopy/issues", 243 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" 244 | }, 245 | "funding": [ 246 | { 247 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 248 | "type": "tidelift" 249 | } 250 | ], 251 | "time": "2023-03-08T13:26:56+00:00" 252 | }, 253 | { 254 | "name": "nikic/php-parser", 255 | "version": "v5.0.0", 256 | "source": { 257 | "type": "git", 258 | "url": "https://github.com/nikic/PHP-Parser.git", 259 | "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" 260 | }, 261 | "dist": { 262 | "type": "zip", 263 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", 264 | "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", 265 | "shasum": "" 266 | }, 267 | "require": { 268 | "ext-ctype": "*", 269 | "ext-json": "*", 270 | "ext-tokenizer": "*", 271 | "php": ">=7.4" 272 | }, 273 | "require-dev": { 274 | "ircmaxell/php-yacc": "^0.0.7", 275 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 276 | }, 277 | "bin": [ 278 | "bin/php-parse" 279 | ], 280 | "type": "library", 281 | "extra": { 282 | "branch-alias": { 283 | "dev-master": "5.0-dev" 284 | } 285 | }, 286 | "autoload": { 287 | "psr-4": { 288 | "PhpParser\\": "lib/PhpParser" 289 | } 290 | }, 291 | "notification-url": "https://packagist.org/downloads/", 292 | "license": [ 293 | "BSD-3-Clause" 294 | ], 295 | "authors": [ 296 | { 297 | "name": "Nikita Popov" 298 | } 299 | ], 300 | "description": "A PHP parser written in PHP", 301 | "keywords": [ 302 | "parser", 303 | "php" 304 | ], 305 | "support": { 306 | "issues": "https://github.com/nikic/PHP-Parser/issues", 307 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" 308 | }, 309 | "time": "2024-01-07T17:17:35+00:00" 310 | }, 311 | { 312 | "name": "phar-io/manifest", 313 | "version": "2.0.3", 314 | "source": { 315 | "type": "git", 316 | "url": "https://github.com/phar-io/manifest.git", 317 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 318 | }, 319 | "dist": { 320 | "type": "zip", 321 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 322 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 323 | "shasum": "" 324 | }, 325 | "require": { 326 | "ext-dom": "*", 327 | "ext-phar": "*", 328 | "ext-xmlwriter": "*", 329 | "phar-io/version": "^3.0.1", 330 | "php": "^7.2 || ^8.0" 331 | }, 332 | "type": "library", 333 | "extra": { 334 | "branch-alias": { 335 | "dev-master": "2.0.x-dev" 336 | } 337 | }, 338 | "autoload": { 339 | "classmap": [ 340 | "src/" 341 | ] 342 | }, 343 | "notification-url": "https://packagist.org/downloads/", 344 | "license": [ 345 | "BSD-3-Clause" 346 | ], 347 | "authors": [ 348 | { 349 | "name": "Arne Blankerts", 350 | "email": "arne@blankerts.de", 351 | "role": "Developer" 352 | }, 353 | { 354 | "name": "Sebastian Heuer", 355 | "email": "sebastian@phpeople.de", 356 | "role": "Developer" 357 | }, 358 | { 359 | "name": "Sebastian Bergmann", 360 | "email": "sebastian@phpunit.de", 361 | "role": "Developer" 362 | } 363 | ], 364 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 365 | "support": { 366 | "issues": "https://github.com/phar-io/manifest/issues", 367 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 368 | }, 369 | "time": "2021-07-20T11:28:43+00:00" 370 | }, 371 | { 372 | "name": "phar-io/version", 373 | "version": "3.2.1", 374 | "source": { 375 | "type": "git", 376 | "url": "https://github.com/phar-io/version.git", 377 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 378 | }, 379 | "dist": { 380 | "type": "zip", 381 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 382 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 383 | "shasum": "" 384 | }, 385 | "require": { 386 | "php": "^7.2 || ^8.0" 387 | }, 388 | "type": "library", 389 | "autoload": { 390 | "classmap": [ 391 | "src/" 392 | ] 393 | }, 394 | "notification-url": "https://packagist.org/downloads/", 395 | "license": [ 396 | "BSD-3-Clause" 397 | ], 398 | "authors": [ 399 | { 400 | "name": "Arne Blankerts", 401 | "email": "arne@blankerts.de", 402 | "role": "Developer" 403 | }, 404 | { 405 | "name": "Sebastian Heuer", 406 | "email": "sebastian@phpeople.de", 407 | "role": "Developer" 408 | }, 409 | { 410 | "name": "Sebastian Bergmann", 411 | "email": "sebastian@phpunit.de", 412 | "role": "Developer" 413 | } 414 | ], 415 | "description": "Library for handling version information and constraints", 416 | "support": { 417 | "issues": "https://github.com/phar-io/version/issues", 418 | "source": "https://github.com/phar-io/version/tree/3.2.1" 419 | }, 420 | "time": "2022-02-21T01:04:05+00:00" 421 | }, 422 | { 423 | "name": "phpstan/phpdoc-parser", 424 | "version": "1.25.0", 425 | "source": { 426 | "type": "git", 427 | "url": "https://github.com/phpstan/phpdoc-parser.git", 428 | "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" 429 | }, 430 | "dist": { 431 | "type": "zip", 432 | "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", 433 | "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", 434 | "shasum": "" 435 | }, 436 | "require": { 437 | "php": "^7.2 || ^8.0" 438 | }, 439 | "require-dev": { 440 | "doctrine/annotations": "^2.0", 441 | "nikic/php-parser": "^4.15", 442 | "php-parallel-lint/php-parallel-lint": "^1.2", 443 | "phpstan/extension-installer": "^1.0", 444 | "phpstan/phpstan": "^1.5", 445 | "phpstan/phpstan-phpunit": "^1.1", 446 | "phpstan/phpstan-strict-rules": "^1.0", 447 | "phpunit/phpunit": "^9.5", 448 | "symfony/process": "^5.2" 449 | }, 450 | "type": "library", 451 | "autoload": { 452 | "psr-4": { 453 | "PHPStan\\PhpDocParser\\": [ 454 | "src/" 455 | ] 456 | } 457 | }, 458 | "notification-url": "https://packagist.org/downloads/", 459 | "license": [ 460 | "MIT" 461 | ], 462 | "description": "PHPDoc parser with support for nullable, intersection and generic types", 463 | "support": { 464 | "issues": "https://github.com/phpstan/phpdoc-parser/issues", 465 | "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" 466 | }, 467 | "time": "2024-01-04T17:06:16+00:00" 468 | }, 469 | { 470 | "name": "phpunit/php-code-coverage", 471 | "version": "9.2.30", 472 | "source": { 473 | "type": "git", 474 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 475 | "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" 476 | }, 477 | "dist": { 478 | "type": "zip", 479 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", 480 | "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", 481 | "shasum": "" 482 | }, 483 | "require": { 484 | "ext-dom": "*", 485 | "ext-libxml": "*", 486 | "ext-xmlwriter": "*", 487 | "nikic/php-parser": "^4.18 || ^5.0", 488 | "php": ">=7.3", 489 | "phpunit/php-file-iterator": "^3.0.3", 490 | "phpunit/php-text-template": "^2.0.2", 491 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 492 | "sebastian/complexity": "^2.0", 493 | "sebastian/environment": "^5.1.2", 494 | "sebastian/lines-of-code": "^1.0.3", 495 | "sebastian/version": "^3.0.1", 496 | "theseer/tokenizer": "^1.2.0" 497 | }, 498 | "require-dev": { 499 | "phpunit/phpunit": "^9.3" 500 | }, 501 | "suggest": { 502 | "ext-pcov": "PHP extension that provides line coverage", 503 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 504 | }, 505 | "type": "library", 506 | "extra": { 507 | "branch-alias": { 508 | "dev-master": "9.2-dev" 509 | } 510 | }, 511 | "autoload": { 512 | "classmap": [ 513 | "src/" 514 | ] 515 | }, 516 | "notification-url": "https://packagist.org/downloads/", 517 | "license": [ 518 | "BSD-3-Clause" 519 | ], 520 | "authors": [ 521 | { 522 | "name": "Sebastian Bergmann", 523 | "email": "sebastian@phpunit.de", 524 | "role": "lead" 525 | } 526 | ], 527 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 528 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 529 | "keywords": [ 530 | "coverage", 531 | "testing", 532 | "xunit" 533 | ], 534 | "support": { 535 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 536 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", 537 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" 538 | }, 539 | "funding": [ 540 | { 541 | "url": "https://github.com/sebastianbergmann", 542 | "type": "github" 543 | } 544 | ], 545 | "time": "2023-12-22T06:47:57+00:00" 546 | }, 547 | { 548 | "name": "phpunit/php-file-iterator", 549 | "version": "3.0.6", 550 | "source": { 551 | "type": "git", 552 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 553 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" 554 | }, 555 | "dist": { 556 | "type": "zip", 557 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 558 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 559 | "shasum": "" 560 | }, 561 | "require": { 562 | "php": ">=7.3" 563 | }, 564 | "require-dev": { 565 | "phpunit/phpunit": "^9.3" 566 | }, 567 | "type": "library", 568 | "extra": { 569 | "branch-alias": { 570 | "dev-master": "3.0-dev" 571 | } 572 | }, 573 | "autoload": { 574 | "classmap": [ 575 | "src/" 576 | ] 577 | }, 578 | "notification-url": "https://packagist.org/downloads/", 579 | "license": [ 580 | "BSD-3-Clause" 581 | ], 582 | "authors": [ 583 | { 584 | "name": "Sebastian Bergmann", 585 | "email": "sebastian@phpunit.de", 586 | "role": "lead" 587 | } 588 | ], 589 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 590 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 591 | "keywords": [ 592 | "filesystem", 593 | "iterator" 594 | ], 595 | "support": { 596 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 597 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" 598 | }, 599 | "funding": [ 600 | { 601 | "url": "https://github.com/sebastianbergmann", 602 | "type": "github" 603 | } 604 | ], 605 | "time": "2021-12-02T12:48:52+00:00" 606 | }, 607 | { 608 | "name": "phpunit/php-invoker", 609 | "version": "3.1.1", 610 | "source": { 611 | "type": "git", 612 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 613 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 614 | }, 615 | "dist": { 616 | "type": "zip", 617 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 618 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 619 | "shasum": "" 620 | }, 621 | "require": { 622 | "php": ">=7.3" 623 | }, 624 | "require-dev": { 625 | "ext-pcntl": "*", 626 | "phpunit/phpunit": "^9.3" 627 | }, 628 | "suggest": { 629 | "ext-pcntl": "*" 630 | }, 631 | "type": "library", 632 | "extra": { 633 | "branch-alias": { 634 | "dev-master": "3.1-dev" 635 | } 636 | }, 637 | "autoload": { 638 | "classmap": [ 639 | "src/" 640 | ] 641 | }, 642 | "notification-url": "https://packagist.org/downloads/", 643 | "license": [ 644 | "BSD-3-Clause" 645 | ], 646 | "authors": [ 647 | { 648 | "name": "Sebastian Bergmann", 649 | "email": "sebastian@phpunit.de", 650 | "role": "lead" 651 | } 652 | ], 653 | "description": "Invoke callables with a timeout", 654 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 655 | "keywords": [ 656 | "process" 657 | ], 658 | "support": { 659 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 660 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 661 | }, 662 | "funding": [ 663 | { 664 | "url": "https://github.com/sebastianbergmann", 665 | "type": "github" 666 | } 667 | ], 668 | "time": "2020-09-28T05:58:55+00:00" 669 | }, 670 | { 671 | "name": "phpunit/php-text-template", 672 | "version": "2.0.4", 673 | "source": { 674 | "type": "git", 675 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 676 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 677 | }, 678 | "dist": { 679 | "type": "zip", 680 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 681 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 682 | "shasum": "" 683 | }, 684 | "require": { 685 | "php": ">=7.3" 686 | }, 687 | "require-dev": { 688 | "phpunit/phpunit": "^9.3" 689 | }, 690 | "type": "library", 691 | "extra": { 692 | "branch-alias": { 693 | "dev-master": "2.0-dev" 694 | } 695 | }, 696 | "autoload": { 697 | "classmap": [ 698 | "src/" 699 | ] 700 | }, 701 | "notification-url": "https://packagist.org/downloads/", 702 | "license": [ 703 | "BSD-3-Clause" 704 | ], 705 | "authors": [ 706 | { 707 | "name": "Sebastian Bergmann", 708 | "email": "sebastian@phpunit.de", 709 | "role": "lead" 710 | } 711 | ], 712 | "description": "Simple template engine.", 713 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 714 | "keywords": [ 715 | "template" 716 | ], 717 | "support": { 718 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 719 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 720 | }, 721 | "funding": [ 722 | { 723 | "url": "https://github.com/sebastianbergmann", 724 | "type": "github" 725 | } 726 | ], 727 | "time": "2020-10-26T05:33:50+00:00" 728 | }, 729 | { 730 | "name": "phpunit/php-timer", 731 | "version": "5.0.3", 732 | "source": { 733 | "type": "git", 734 | "url": "https://github.com/sebastianbergmann/php-timer.git", 735 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 736 | }, 737 | "dist": { 738 | "type": "zip", 739 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 740 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 741 | "shasum": "" 742 | }, 743 | "require": { 744 | "php": ">=7.3" 745 | }, 746 | "require-dev": { 747 | "phpunit/phpunit": "^9.3" 748 | }, 749 | "type": "library", 750 | "extra": { 751 | "branch-alias": { 752 | "dev-master": "5.0-dev" 753 | } 754 | }, 755 | "autoload": { 756 | "classmap": [ 757 | "src/" 758 | ] 759 | }, 760 | "notification-url": "https://packagist.org/downloads/", 761 | "license": [ 762 | "BSD-3-Clause" 763 | ], 764 | "authors": [ 765 | { 766 | "name": "Sebastian Bergmann", 767 | "email": "sebastian@phpunit.de", 768 | "role": "lead" 769 | } 770 | ], 771 | "description": "Utility class for timing", 772 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 773 | "keywords": [ 774 | "timer" 775 | ], 776 | "support": { 777 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 778 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 779 | }, 780 | "funding": [ 781 | { 782 | "url": "https://github.com/sebastianbergmann", 783 | "type": "github" 784 | } 785 | ], 786 | "time": "2020-10-26T13:16:10+00:00" 787 | }, 788 | { 789 | "name": "phpunit/phpunit", 790 | "version": "9.6.16", 791 | "source": { 792 | "type": "git", 793 | "url": "https://github.com/sebastianbergmann/phpunit.git", 794 | "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" 795 | }, 796 | "dist": { 797 | "type": "zip", 798 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", 799 | "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", 800 | "shasum": "" 801 | }, 802 | "require": { 803 | "doctrine/instantiator": "^1.3.1 || ^2", 804 | "ext-dom": "*", 805 | "ext-json": "*", 806 | "ext-libxml": "*", 807 | "ext-mbstring": "*", 808 | "ext-xml": "*", 809 | "ext-xmlwriter": "*", 810 | "myclabs/deep-copy": "^1.10.1", 811 | "phar-io/manifest": "^2.0.3", 812 | "phar-io/version": "^3.0.2", 813 | "php": ">=7.3", 814 | "phpunit/php-code-coverage": "^9.2.28", 815 | "phpunit/php-file-iterator": "^3.0.5", 816 | "phpunit/php-invoker": "^3.1.1", 817 | "phpunit/php-text-template": "^2.0.3", 818 | "phpunit/php-timer": "^5.0.2", 819 | "sebastian/cli-parser": "^1.0.1", 820 | "sebastian/code-unit": "^1.0.6", 821 | "sebastian/comparator": "^4.0.8", 822 | "sebastian/diff": "^4.0.3", 823 | "sebastian/environment": "^5.1.3", 824 | "sebastian/exporter": "^4.0.5", 825 | "sebastian/global-state": "^5.0.1", 826 | "sebastian/object-enumerator": "^4.0.3", 827 | "sebastian/resource-operations": "^3.0.3", 828 | "sebastian/type": "^3.2", 829 | "sebastian/version": "^3.0.2" 830 | }, 831 | "suggest": { 832 | "ext-soap": "To be able to generate mocks based on WSDL files", 833 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 834 | }, 835 | "bin": [ 836 | "phpunit" 837 | ], 838 | "type": "library", 839 | "extra": { 840 | "branch-alias": { 841 | "dev-master": "9.6-dev" 842 | } 843 | }, 844 | "autoload": { 845 | "files": [ 846 | "src/Framework/Assert/Functions.php" 847 | ], 848 | "classmap": [ 849 | "src/" 850 | ] 851 | }, 852 | "notification-url": "https://packagist.org/downloads/", 853 | "license": [ 854 | "BSD-3-Clause" 855 | ], 856 | "authors": [ 857 | { 858 | "name": "Sebastian Bergmann", 859 | "email": "sebastian@phpunit.de", 860 | "role": "lead" 861 | } 862 | ], 863 | "description": "The PHP Unit Testing framework.", 864 | "homepage": "https://phpunit.de/", 865 | "keywords": [ 866 | "phpunit", 867 | "testing", 868 | "xunit" 869 | ], 870 | "support": { 871 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 872 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 873 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16" 874 | }, 875 | "funding": [ 876 | { 877 | "url": "https://phpunit.de/sponsors.html", 878 | "type": "custom" 879 | }, 880 | { 881 | "url": "https://github.com/sebastianbergmann", 882 | "type": "github" 883 | }, 884 | { 885 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 886 | "type": "tidelift" 887 | } 888 | ], 889 | "time": "2024-01-19T07:03:14+00:00" 890 | }, 891 | { 892 | "name": "sebastian/cli-parser", 893 | "version": "1.0.1", 894 | "source": { 895 | "type": "git", 896 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 897 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 898 | }, 899 | "dist": { 900 | "type": "zip", 901 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 902 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 903 | "shasum": "" 904 | }, 905 | "require": { 906 | "php": ">=7.3" 907 | }, 908 | "require-dev": { 909 | "phpunit/phpunit": "^9.3" 910 | }, 911 | "type": "library", 912 | "extra": { 913 | "branch-alias": { 914 | "dev-master": "1.0-dev" 915 | } 916 | }, 917 | "autoload": { 918 | "classmap": [ 919 | "src/" 920 | ] 921 | }, 922 | "notification-url": "https://packagist.org/downloads/", 923 | "license": [ 924 | "BSD-3-Clause" 925 | ], 926 | "authors": [ 927 | { 928 | "name": "Sebastian Bergmann", 929 | "email": "sebastian@phpunit.de", 930 | "role": "lead" 931 | } 932 | ], 933 | "description": "Library for parsing CLI options", 934 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 935 | "support": { 936 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 937 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 938 | }, 939 | "funding": [ 940 | { 941 | "url": "https://github.com/sebastianbergmann", 942 | "type": "github" 943 | } 944 | ], 945 | "time": "2020-09-28T06:08:49+00:00" 946 | }, 947 | { 948 | "name": "sebastian/code-unit", 949 | "version": "1.0.8", 950 | "source": { 951 | "type": "git", 952 | "url": "https://github.com/sebastianbergmann/code-unit.git", 953 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 954 | }, 955 | "dist": { 956 | "type": "zip", 957 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 958 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 959 | "shasum": "" 960 | }, 961 | "require": { 962 | "php": ">=7.3" 963 | }, 964 | "require-dev": { 965 | "phpunit/phpunit": "^9.3" 966 | }, 967 | "type": "library", 968 | "extra": { 969 | "branch-alias": { 970 | "dev-master": "1.0-dev" 971 | } 972 | }, 973 | "autoload": { 974 | "classmap": [ 975 | "src/" 976 | ] 977 | }, 978 | "notification-url": "https://packagist.org/downloads/", 979 | "license": [ 980 | "BSD-3-Clause" 981 | ], 982 | "authors": [ 983 | { 984 | "name": "Sebastian Bergmann", 985 | "email": "sebastian@phpunit.de", 986 | "role": "lead" 987 | } 988 | ], 989 | "description": "Collection of value objects that represent the PHP code units", 990 | "homepage": "https://github.com/sebastianbergmann/code-unit", 991 | "support": { 992 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 993 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 994 | }, 995 | "funding": [ 996 | { 997 | "url": "https://github.com/sebastianbergmann", 998 | "type": "github" 999 | } 1000 | ], 1001 | "time": "2020-10-26T13:08:54+00:00" 1002 | }, 1003 | { 1004 | "name": "sebastian/code-unit-reverse-lookup", 1005 | "version": "2.0.3", 1006 | "source": { 1007 | "type": "git", 1008 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1009 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 1010 | }, 1011 | "dist": { 1012 | "type": "zip", 1013 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1014 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1015 | "shasum": "" 1016 | }, 1017 | "require": { 1018 | "php": ">=7.3" 1019 | }, 1020 | "require-dev": { 1021 | "phpunit/phpunit": "^9.3" 1022 | }, 1023 | "type": "library", 1024 | "extra": { 1025 | "branch-alias": { 1026 | "dev-master": "2.0-dev" 1027 | } 1028 | }, 1029 | "autoload": { 1030 | "classmap": [ 1031 | "src/" 1032 | ] 1033 | }, 1034 | "notification-url": "https://packagist.org/downloads/", 1035 | "license": [ 1036 | "BSD-3-Clause" 1037 | ], 1038 | "authors": [ 1039 | { 1040 | "name": "Sebastian Bergmann", 1041 | "email": "sebastian@phpunit.de" 1042 | } 1043 | ], 1044 | "description": "Looks up which function or method a line of code belongs to", 1045 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1046 | "support": { 1047 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1048 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 1049 | }, 1050 | "funding": [ 1051 | { 1052 | "url": "https://github.com/sebastianbergmann", 1053 | "type": "github" 1054 | } 1055 | ], 1056 | "time": "2020-09-28T05:30:19+00:00" 1057 | }, 1058 | { 1059 | "name": "sebastian/comparator", 1060 | "version": "4.0.8", 1061 | "source": { 1062 | "type": "git", 1063 | "url": "https://github.com/sebastianbergmann/comparator.git", 1064 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a" 1065 | }, 1066 | "dist": { 1067 | "type": "zip", 1068 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", 1069 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a", 1070 | "shasum": "" 1071 | }, 1072 | "require": { 1073 | "php": ">=7.3", 1074 | "sebastian/diff": "^4.0", 1075 | "sebastian/exporter": "^4.0" 1076 | }, 1077 | "require-dev": { 1078 | "phpunit/phpunit": "^9.3" 1079 | }, 1080 | "type": "library", 1081 | "extra": { 1082 | "branch-alias": { 1083 | "dev-master": "4.0-dev" 1084 | } 1085 | }, 1086 | "autoload": { 1087 | "classmap": [ 1088 | "src/" 1089 | ] 1090 | }, 1091 | "notification-url": "https://packagist.org/downloads/", 1092 | "license": [ 1093 | "BSD-3-Clause" 1094 | ], 1095 | "authors": [ 1096 | { 1097 | "name": "Sebastian Bergmann", 1098 | "email": "sebastian@phpunit.de" 1099 | }, 1100 | { 1101 | "name": "Jeff Welch", 1102 | "email": "whatthejeff@gmail.com" 1103 | }, 1104 | { 1105 | "name": "Volker Dusch", 1106 | "email": "github@wallbash.com" 1107 | }, 1108 | { 1109 | "name": "Bernhard Schussek", 1110 | "email": "bschussek@2bepublished.at" 1111 | } 1112 | ], 1113 | "description": "Provides the functionality to compare PHP values for equality", 1114 | "homepage": "https://github.com/sebastianbergmann/comparator", 1115 | "keywords": [ 1116 | "comparator", 1117 | "compare", 1118 | "equality" 1119 | ], 1120 | "support": { 1121 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1122 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" 1123 | }, 1124 | "funding": [ 1125 | { 1126 | "url": "https://github.com/sebastianbergmann", 1127 | "type": "github" 1128 | } 1129 | ], 1130 | "time": "2022-09-14T12:41:17+00:00" 1131 | }, 1132 | { 1133 | "name": "sebastian/complexity", 1134 | "version": "2.0.3", 1135 | "source": { 1136 | "type": "git", 1137 | "url": "https://github.com/sebastianbergmann/complexity.git", 1138 | "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" 1139 | }, 1140 | "dist": { 1141 | "type": "zip", 1142 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", 1143 | "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", 1144 | "shasum": "" 1145 | }, 1146 | "require": { 1147 | "nikic/php-parser": "^4.18 || ^5.0", 1148 | "php": ">=7.3" 1149 | }, 1150 | "require-dev": { 1151 | "phpunit/phpunit": "^9.3" 1152 | }, 1153 | "type": "library", 1154 | "extra": { 1155 | "branch-alias": { 1156 | "dev-master": "2.0-dev" 1157 | } 1158 | }, 1159 | "autoload": { 1160 | "classmap": [ 1161 | "src/" 1162 | ] 1163 | }, 1164 | "notification-url": "https://packagist.org/downloads/", 1165 | "license": [ 1166 | "BSD-3-Clause" 1167 | ], 1168 | "authors": [ 1169 | { 1170 | "name": "Sebastian Bergmann", 1171 | "email": "sebastian@phpunit.de", 1172 | "role": "lead" 1173 | } 1174 | ], 1175 | "description": "Library for calculating the complexity of PHP code units", 1176 | "homepage": "https://github.com/sebastianbergmann/complexity", 1177 | "support": { 1178 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1179 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" 1180 | }, 1181 | "funding": [ 1182 | { 1183 | "url": "https://github.com/sebastianbergmann", 1184 | "type": "github" 1185 | } 1186 | ], 1187 | "time": "2023-12-22T06:19:30+00:00" 1188 | }, 1189 | { 1190 | "name": "sebastian/diff", 1191 | "version": "4.0.5", 1192 | "source": { 1193 | "type": "git", 1194 | "url": "https://github.com/sebastianbergmann/diff.git", 1195 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" 1196 | }, 1197 | "dist": { 1198 | "type": "zip", 1199 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1200 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1201 | "shasum": "" 1202 | }, 1203 | "require": { 1204 | "php": ">=7.3" 1205 | }, 1206 | "require-dev": { 1207 | "phpunit/phpunit": "^9.3", 1208 | "symfony/process": "^4.2 || ^5" 1209 | }, 1210 | "type": "library", 1211 | "extra": { 1212 | "branch-alias": { 1213 | "dev-master": "4.0-dev" 1214 | } 1215 | }, 1216 | "autoload": { 1217 | "classmap": [ 1218 | "src/" 1219 | ] 1220 | }, 1221 | "notification-url": "https://packagist.org/downloads/", 1222 | "license": [ 1223 | "BSD-3-Clause" 1224 | ], 1225 | "authors": [ 1226 | { 1227 | "name": "Sebastian Bergmann", 1228 | "email": "sebastian@phpunit.de" 1229 | }, 1230 | { 1231 | "name": "Kore Nordmann", 1232 | "email": "mail@kore-nordmann.de" 1233 | } 1234 | ], 1235 | "description": "Diff implementation", 1236 | "homepage": "https://github.com/sebastianbergmann/diff", 1237 | "keywords": [ 1238 | "diff", 1239 | "udiff", 1240 | "unidiff", 1241 | "unified diff" 1242 | ], 1243 | "support": { 1244 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1245 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" 1246 | }, 1247 | "funding": [ 1248 | { 1249 | "url": "https://github.com/sebastianbergmann", 1250 | "type": "github" 1251 | } 1252 | ], 1253 | "time": "2023-05-07T05:35:17+00:00" 1254 | }, 1255 | { 1256 | "name": "sebastian/environment", 1257 | "version": "5.1.5", 1258 | "source": { 1259 | "type": "git", 1260 | "url": "https://github.com/sebastianbergmann/environment.git", 1261 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" 1262 | }, 1263 | "dist": { 1264 | "type": "zip", 1265 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1266 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1267 | "shasum": "" 1268 | }, 1269 | "require": { 1270 | "php": ">=7.3" 1271 | }, 1272 | "require-dev": { 1273 | "phpunit/phpunit": "^9.3" 1274 | }, 1275 | "suggest": { 1276 | "ext-posix": "*" 1277 | }, 1278 | "type": "library", 1279 | "extra": { 1280 | "branch-alias": { 1281 | "dev-master": "5.1-dev" 1282 | } 1283 | }, 1284 | "autoload": { 1285 | "classmap": [ 1286 | "src/" 1287 | ] 1288 | }, 1289 | "notification-url": "https://packagist.org/downloads/", 1290 | "license": [ 1291 | "BSD-3-Clause" 1292 | ], 1293 | "authors": [ 1294 | { 1295 | "name": "Sebastian Bergmann", 1296 | "email": "sebastian@phpunit.de" 1297 | } 1298 | ], 1299 | "description": "Provides functionality to handle HHVM/PHP environments", 1300 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1301 | "keywords": [ 1302 | "Xdebug", 1303 | "environment", 1304 | "hhvm" 1305 | ], 1306 | "support": { 1307 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1308 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" 1309 | }, 1310 | "funding": [ 1311 | { 1312 | "url": "https://github.com/sebastianbergmann", 1313 | "type": "github" 1314 | } 1315 | ], 1316 | "time": "2023-02-03T06:03:51+00:00" 1317 | }, 1318 | { 1319 | "name": "sebastian/exporter", 1320 | "version": "4.0.5", 1321 | "source": { 1322 | "type": "git", 1323 | "url": "https://github.com/sebastianbergmann/exporter.git", 1324 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" 1325 | }, 1326 | "dist": { 1327 | "type": "zip", 1328 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1329 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1330 | "shasum": "" 1331 | }, 1332 | "require": { 1333 | "php": ">=7.3", 1334 | "sebastian/recursion-context": "^4.0" 1335 | }, 1336 | "require-dev": { 1337 | "ext-mbstring": "*", 1338 | "phpunit/phpunit": "^9.3" 1339 | }, 1340 | "type": "library", 1341 | "extra": { 1342 | "branch-alias": { 1343 | "dev-master": "4.0-dev" 1344 | } 1345 | }, 1346 | "autoload": { 1347 | "classmap": [ 1348 | "src/" 1349 | ] 1350 | }, 1351 | "notification-url": "https://packagist.org/downloads/", 1352 | "license": [ 1353 | "BSD-3-Clause" 1354 | ], 1355 | "authors": [ 1356 | { 1357 | "name": "Sebastian Bergmann", 1358 | "email": "sebastian@phpunit.de" 1359 | }, 1360 | { 1361 | "name": "Jeff Welch", 1362 | "email": "whatthejeff@gmail.com" 1363 | }, 1364 | { 1365 | "name": "Volker Dusch", 1366 | "email": "github@wallbash.com" 1367 | }, 1368 | { 1369 | "name": "Adam Harvey", 1370 | "email": "aharvey@php.net" 1371 | }, 1372 | { 1373 | "name": "Bernhard Schussek", 1374 | "email": "bschussek@gmail.com" 1375 | } 1376 | ], 1377 | "description": "Provides the functionality to export PHP variables for visualization", 1378 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1379 | "keywords": [ 1380 | "export", 1381 | "exporter" 1382 | ], 1383 | "support": { 1384 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1385 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" 1386 | }, 1387 | "funding": [ 1388 | { 1389 | "url": "https://github.com/sebastianbergmann", 1390 | "type": "github" 1391 | } 1392 | ], 1393 | "time": "2022-09-14T06:03:37+00:00" 1394 | }, 1395 | { 1396 | "name": "sebastian/global-state", 1397 | "version": "5.0.6", 1398 | "source": { 1399 | "type": "git", 1400 | "url": "https://github.com/sebastianbergmann/global-state.git", 1401 | "reference": "bde739e7565280bda77be70044ac1047bc007e34" 1402 | }, 1403 | "dist": { 1404 | "type": "zip", 1405 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", 1406 | "reference": "bde739e7565280bda77be70044ac1047bc007e34", 1407 | "shasum": "" 1408 | }, 1409 | "require": { 1410 | "php": ">=7.3", 1411 | "sebastian/object-reflector": "^2.0", 1412 | "sebastian/recursion-context": "^4.0" 1413 | }, 1414 | "require-dev": { 1415 | "ext-dom": "*", 1416 | "phpunit/phpunit": "^9.3" 1417 | }, 1418 | "suggest": { 1419 | "ext-uopz": "*" 1420 | }, 1421 | "type": "library", 1422 | "extra": { 1423 | "branch-alias": { 1424 | "dev-master": "5.0-dev" 1425 | } 1426 | }, 1427 | "autoload": { 1428 | "classmap": [ 1429 | "src/" 1430 | ] 1431 | }, 1432 | "notification-url": "https://packagist.org/downloads/", 1433 | "license": [ 1434 | "BSD-3-Clause" 1435 | ], 1436 | "authors": [ 1437 | { 1438 | "name": "Sebastian Bergmann", 1439 | "email": "sebastian@phpunit.de" 1440 | } 1441 | ], 1442 | "description": "Snapshotting of global state", 1443 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1444 | "keywords": [ 1445 | "global state" 1446 | ], 1447 | "support": { 1448 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1449 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" 1450 | }, 1451 | "funding": [ 1452 | { 1453 | "url": "https://github.com/sebastianbergmann", 1454 | "type": "github" 1455 | } 1456 | ], 1457 | "time": "2023-08-02T09:26:13+00:00" 1458 | }, 1459 | { 1460 | "name": "sebastian/lines-of-code", 1461 | "version": "1.0.4", 1462 | "source": { 1463 | "type": "git", 1464 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1465 | "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" 1466 | }, 1467 | "dist": { 1468 | "type": "zip", 1469 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", 1470 | "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", 1471 | "shasum": "" 1472 | }, 1473 | "require": { 1474 | "nikic/php-parser": "^4.18 || ^5.0", 1475 | "php": ">=7.3" 1476 | }, 1477 | "require-dev": { 1478 | "phpunit/phpunit": "^9.3" 1479 | }, 1480 | "type": "library", 1481 | "extra": { 1482 | "branch-alias": { 1483 | "dev-master": "1.0-dev" 1484 | } 1485 | }, 1486 | "autoload": { 1487 | "classmap": [ 1488 | "src/" 1489 | ] 1490 | }, 1491 | "notification-url": "https://packagist.org/downloads/", 1492 | "license": [ 1493 | "BSD-3-Clause" 1494 | ], 1495 | "authors": [ 1496 | { 1497 | "name": "Sebastian Bergmann", 1498 | "email": "sebastian@phpunit.de", 1499 | "role": "lead" 1500 | } 1501 | ], 1502 | "description": "Library for counting the lines of code in PHP source code", 1503 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1504 | "support": { 1505 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1506 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" 1507 | }, 1508 | "funding": [ 1509 | { 1510 | "url": "https://github.com/sebastianbergmann", 1511 | "type": "github" 1512 | } 1513 | ], 1514 | "time": "2023-12-22T06:20:34+00:00" 1515 | }, 1516 | { 1517 | "name": "sebastian/object-enumerator", 1518 | "version": "4.0.4", 1519 | "source": { 1520 | "type": "git", 1521 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1522 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 1523 | }, 1524 | "dist": { 1525 | "type": "zip", 1526 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 1527 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 1528 | "shasum": "" 1529 | }, 1530 | "require": { 1531 | "php": ">=7.3", 1532 | "sebastian/object-reflector": "^2.0", 1533 | "sebastian/recursion-context": "^4.0" 1534 | }, 1535 | "require-dev": { 1536 | "phpunit/phpunit": "^9.3" 1537 | }, 1538 | "type": "library", 1539 | "extra": { 1540 | "branch-alias": { 1541 | "dev-master": "4.0-dev" 1542 | } 1543 | }, 1544 | "autoload": { 1545 | "classmap": [ 1546 | "src/" 1547 | ] 1548 | }, 1549 | "notification-url": "https://packagist.org/downloads/", 1550 | "license": [ 1551 | "BSD-3-Clause" 1552 | ], 1553 | "authors": [ 1554 | { 1555 | "name": "Sebastian Bergmann", 1556 | "email": "sebastian@phpunit.de" 1557 | } 1558 | ], 1559 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1560 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1561 | "support": { 1562 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1563 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 1564 | }, 1565 | "funding": [ 1566 | { 1567 | "url": "https://github.com/sebastianbergmann", 1568 | "type": "github" 1569 | } 1570 | ], 1571 | "time": "2020-10-26T13:12:34+00:00" 1572 | }, 1573 | { 1574 | "name": "sebastian/object-reflector", 1575 | "version": "2.0.4", 1576 | "source": { 1577 | "type": "git", 1578 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1579 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 1580 | }, 1581 | "dist": { 1582 | "type": "zip", 1583 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1584 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1585 | "shasum": "" 1586 | }, 1587 | "require": { 1588 | "php": ">=7.3" 1589 | }, 1590 | "require-dev": { 1591 | "phpunit/phpunit": "^9.3" 1592 | }, 1593 | "type": "library", 1594 | "extra": { 1595 | "branch-alias": { 1596 | "dev-master": "2.0-dev" 1597 | } 1598 | }, 1599 | "autoload": { 1600 | "classmap": [ 1601 | "src/" 1602 | ] 1603 | }, 1604 | "notification-url": "https://packagist.org/downloads/", 1605 | "license": [ 1606 | "BSD-3-Clause" 1607 | ], 1608 | "authors": [ 1609 | { 1610 | "name": "Sebastian Bergmann", 1611 | "email": "sebastian@phpunit.de" 1612 | } 1613 | ], 1614 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1615 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1616 | "support": { 1617 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1618 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 1619 | }, 1620 | "funding": [ 1621 | { 1622 | "url": "https://github.com/sebastianbergmann", 1623 | "type": "github" 1624 | } 1625 | ], 1626 | "time": "2020-10-26T13:14:26+00:00" 1627 | }, 1628 | { 1629 | "name": "sebastian/recursion-context", 1630 | "version": "4.0.5", 1631 | "source": { 1632 | "type": "git", 1633 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1634 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" 1635 | }, 1636 | "dist": { 1637 | "type": "zip", 1638 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1639 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1640 | "shasum": "" 1641 | }, 1642 | "require": { 1643 | "php": ">=7.3" 1644 | }, 1645 | "require-dev": { 1646 | "phpunit/phpunit": "^9.3" 1647 | }, 1648 | "type": "library", 1649 | "extra": { 1650 | "branch-alias": { 1651 | "dev-master": "4.0-dev" 1652 | } 1653 | }, 1654 | "autoload": { 1655 | "classmap": [ 1656 | "src/" 1657 | ] 1658 | }, 1659 | "notification-url": "https://packagist.org/downloads/", 1660 | "license": [ 1661 | "BSD-3-Clause" 1662 | ], 1663 | "authors": [ 1664 | { 1665 | "name": "Sebastian Bergmann", 1666 | "email": "sebastian@phpunit.de" 1667 | }, 1668 | { 1669 | "name": "Jeff Welch", 1670 | "email": "whatthejeff@gmail.com" 1671 | }, 1672 | { 1673 | "name": "Adam Harvey", 1674 | "email": "aharvey@php.net" 1675 | } 1676 | ], 1677 | "description": "Provides functionality to recursively process PHP variables", 1678 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1679 | "support": { 1680 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1681 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" 1682 | }, 1683 | "funding": [ 1684 | { 1685 | "url": "https://github.com/sebastianbergmann", 1686 | "type": "github" 1687 | } 1688 | ], 1689 | "time": "2023-02-03T06:07:39+00:00" 1690 | }, 1691 | { 1692 | "name": "sebastian/resource-operations", 1693 | "version": "3.0.3", 1694 | "source": { 1695 | "type": "git", 1696 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1697 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 1698 | }, 1699 | "dist": { 1700 | "type": "zip", 1701 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1702 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1703 | "shasum": "" 1704 | }, 1705 | "require": { 1706 | "php": ">=7.3" 1707 | }, 1708 | "require-dev": { 1709 | "phpunit/phpunit": "^9.0" 1710 | }, 1711 | "type": "library", 1712 | "extra": { 1713 | "branch-alias": { 1714 | "dev-master": "3.0-dev" 1715 | } 1716 | }, 1717 | "autoload": { 1718 | "classmap": [ 1719 | "src/" 1720 | ] 1721 | }, 1722 | "notification-url": "https://packagist.org/downloads/", 1723 | "license": [ 1724 | "BSD-3-Clause" 1725 | ], 1726 | "authors": [ 1727 | { 1728 | "name": "Sebastian Bergmann", 1729 | "email": "sebastian@phpunit.de" 1730 | } 1731 | ], 1732 | "description": "Provides a list of PHP built-in functions that operate on resources", 1733 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1734 | "support": { 1735 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1736 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 1737 | }, 1738 | "funding": [ 1739 | { 1740 | "url": "https://github.com/sebastianbergmann", 1741 | "type": "github" 1742 | } 1743 | ], 1744 | "time": "2020-09-28T06:45:17+00:00" 1745 | }, 1746 | { 1747 | "name": "sebastian/type", 1748 | "version": "3.2.1", 1749 | "source": { 1750 | "type": "git", 1751 | "url": "https://github.com/sebastianbergmann/type.git", 1752 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" 1753 | }, 1754 | "dist": { 1755 | "type": "zip", 1756 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 1757 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 1758 | "shasum": "" 1759 | }, 1760 | "require": { 1761 | "php": ">=7.3" 1762 | }, 1763 | "require-dev": { 1764 | "phpunit/phpunit": "^9.5" 1765 | }, 1766 | "type": "library", 1767 | "extra": { 1768 | "branch-alias": { 1769 | "dev-master": "3.2-dev" 1770 | } 1771 | }, 1772 | "autoload": { 1773 | "classmap": [ 1774 | "src/" 1775 | ] 1776 | }, 1777 | "notification-url": "https://packagist.org/downloads/", 1778 | "license": [ 1779 | "BSD-3-Clause" 1780 | ], 1781 | "authors": [ 1782 | { 1783 | "name": "Sebastian Bergmann", 1784 | "email": "sebastian@phpunit.de", 1785 | "role": "lead" 1786 | } 1787 | ], 1788 | "description": "Collection of value objects that represent the types of the PHP type system", 1789 | "homepage": "https://github.com/sebastianbergmann/type", 1790 | "support": { 1791 | "issues": "https://github.com/sebastianbergmann/type/issues", 1792 | "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" 1793 | }, 1794 | "funding": [ 1795 | { 1796 | "url": "https://github.com/sebastianbergmann", 1797 | "type": "github" 1798 | } 1799 | ], 1800 | "time": "2023-02-03T06:13:03+00:00" 1801 | }, 1802 | { 1803 | "name": "sebastian/version", 1804 | "version": "3.0.2", 1805 | "source": { 1806 | "type": "git", 1807 | "url": "https://github.com/sebastianbergmann/version.git", 1808 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 1809 | }, 1810 | "dist": { 1811 | "type": "zip", 1812 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 1813 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 1814 | "shasum": "" 1815 | }, 1816 | "require": { 1817 | "php": ">=7.3" 1818 | }, 1819 | "type": "library", 1820 | "extra": { 1821 | "branch-alias": { 1822 | "dev-master": "3.0-dev" 1823 | } 1824 | }, 1825 | "autoload": { 1826 | "classmap": [ 1827 | "src/" 1828 | ] 1829 | }, 1830 | "notification-url": "https://packagist.org/downloads/", 1831 | "license": [ 1832 | "BSD-3-Clause" 1833 | ], 1834 | "authors": [ 1835 | { 1836 | "name": "Sebastian Bergmann", 1837 | "email": "sebastian@phpunit.de", 1838 | "role": "lead" 1839 | } 1840 | ], 1841 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1842 | "homepage": "https://github.com/sebastianbergmann/version", 1843 | "support": { 1844 | "issues": "https://github.com/sebastianbergmann/version/issues", 1845 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 1846 | }, 1847 | "funding": [ 1848 | { 1849 | "url": "https://github.com/sebastianbergmann", 1850 | "type": "github" 1851 | } 1852 | ], 1853 | "time": "2020-09-28T06:39:44+00:00" 1854 | }, 1855 | { 1856 | "name": "slevomat/coding-standard", 1857 | "version": "8.14.1", 1858 | "source": { 1859 | "type": "git", 1860 | "url": "https://github.com/slevomat/coding-standard.git", 1861 | "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926" 1862 | }, 1863 | "dist": { 1864 | "type": "zip", 1865 | "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/fea1fd6f137cc84f9cba0ae30d549615dbc6a926", 1866 | "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926", 1867 | "shasum": "" 1868 | }, 1869 | "require": { 1870 | "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", 1871 | "php": "^7.2 || ^8.0", 1872 | "phpstan/phpdoc-parser": "^1.23.1", 1873 | "squizlabs/php_codesniffer": "^3.7.1" 1874 | }, 1875 | "require-dev": { 1876 | "phing/phing": "2.17.4", 1877 | "php-parallel-lint/php-parallel-lint": "1.3.2", 1878 | "phpstan/phpstan": "1.10.37", 1879 | "phpstan/phpstan-deprecation-rules": "1.1.4", 1880 | "phpstan/phpstan-phpunit": "1.3.14", 1881 | "phpstan/phpstan-strict-rules": "1.5.1", 1882 | "phpunit/phpunit": "8.5.21|9.6.8|10.3.5" 1883 | }, 1884 | "type": "phpcodesniffer-standard", 1885 | "extra": { 1886 | "branch-alias": { 1887 | "dev-master": "8.x-dev" 1888 | } 1889 | }, 1890 | "autoload": { 1891 | "psr-4": { 1892 | "SlevomatCodingStandard\\": "SlevomatCodingStandard/" 1893 | } 1894 | }, 1895 | "notification-url": "https://packagist.org/downloads/", 1896 | "license": [ 1897 | "MIT" 1898 | ], 1899 | "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", 1900 | "keywords": [ 1901 | "dev", 1902 | "phpcs" 1903 | ], 1904 | "support": { 1905 | "issues": "https://github.com/slevomat/coding-standard/issues", 1906 | "source": "https://github.com/slevomat/coding-standard/tree/8.14.1" 1907 | }, 1908 | "funding": [ 1909 | { 1910 | "url": "https://github.com/kukulich", 1911 | "type": "github" 1912 | }, 1913 | { 1914 | "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", 1915 | "type": "tidelift" 1916 | } 1917 | ], 1918 | "time": "2023-10-08T07:28:08+00:00" 1919 | }, 1920 | { 1921 | "name": "squizlabs/php_codesniffer", 1922 | "version": "3.8.1", 1923 | "source": { 1924 | "type": "git", 1925 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", 1926 | "reference": "14f5fff1e64118595db5408e946f3a22c75807f7" 1927 | }, 1928 | "dist": { 1929 | "type": "zip", 1930 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7", 1931 | "reference": "14f5fff1e64118595db5408e946f3a22c75807f7", 1932 | "shasum": "" 1933 | }, 1934 | "require": { 1935 | "ext-simplexml": "*", 1936 | "ext-tokenizer": "*", 1937 | "ext-xmlwriter": "*", 1938 | "php": ">=5.4.0" 1939 | }, 1940 | "require-dev": { 1941 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" 1942 | }, 1943 | "bin": [ 1944 | "bin/phpcbf", 1945 | "bin/phpcs" 1946 | ], 1947 | "type": "library", 1948 | "extra": { 1949 | "branch-alias": { 1950 | "dev-master": "3.x-dev" 1951 | } 1952 | }, 1953 | "notification-url": "https://packagist.org/downloads/", 1954 | "license": [ 1955 | "BSD-3-Clause" 1956 | ], 1957 | "authors": [ 1958 | { 1959 | "name": "Greg Sherwood", 1960 | "role": "Former lead" 1961 | }, 1962 | { 1963 | "name": "Juliette Reinders Folmer", 1964 | "role": "Current lead" 1965 | }, 1966 | { 1967 | "name": "Contributors", 1968 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" 1969 | } 1970 | ], 1971 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 1972 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 1973 | "keywords": [ 1974 | "phpcs", 1975 | "standards", 1976 | "static analysis" 1977 | ], 1978 | "support": { 1979 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", 1980 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", 1981 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 1982 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" 1983 | }, 1984 | "funding": [ 1985 | { 1986 | "url": "https://github.com/PHPCSStandards", 1987 | "type": "github" 1988 | }, 1989 | { 1990 | "url": "https://github.com/jrfnl", 1991 | "type": "github" 1992 | }, 1993 | { 1994 | "url": "https://opencollective.com/php_codesniffer", 1995 | "type": "open_collective" 1996 | } 1997 | ], 1998 | "time": "2024-01-11T20:47:48+00:00" 1999 | }, 2000 | { 2001 | "name": "theseer/tokenizer", 2002 | "version": "1.2.2", 2003 | "source": { 2004 | "type": "git", 2005 | "url": "https://github.com/theseer/tokenizer.git", 2006 | "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" 2007 | }, 2008 | "dist": { 2009 | "type": "zip", 2010 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", 2011 | "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", 2012 | "shasum": "" 2013 | }, 2014 | "require": { 2015 | "ext-dom": "*", 2016 | "ext-tokenizer": "*", 2017 | "ext-xmlwriter": "*", 2018 | "php": "^7.2 || ^8.0" 2019 | }, 2020 | "type": "library", 2021 | "autoload": { 2022 | "classmap": [ 2023 | "src/" 2024 | ] 2025 | }, 2026 | "notification-url": "https://packagist.org/downloads/", 2027 | "license": [ 2028 | "BSD-3-Clause" 2029 | ], 2030 | "authors": [ 2031 | { 2032 | "name": "Arne Blankerts", 2033 | "email": "arne@blankerts.de", 2034 | "role": "Developer" 2035 | } 2036 | ], 2037 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2038 | "support": { 2039 | "issues": "https://github.com/theseer/tokenizer/issues", 2040 | "source": "https://github.com/theseer/tokenizer/tree/1.2.2" 2041 | }, 2042 | "funding": [ 2043 | { 2044 | "url": "https://github.com/theseer", 2045 | "type": "github" 2046 | } 2047 | ], 2048 | "time": "2023-11-20T00:12:19+00:00" 2049 | } 2050 | ], 2051 | "aliases": [], 2052 | "minimum-stability": "dev", 2053 | "stability-flags": { 2054 | "jerodev/code-styles": 20 2055 | }, 2056 | "prefer-stable": true, 2057 | "prefer-lowest": false, 2058 | "platform": { 2059 | "php": "^8.1" 2060 | }, 2061 | "platform-dev": [], 2062 | "plugin-api-version": "2.3.0" 2063 | } 2064 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Jerodev 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 all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | tests 7 | 8 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Data Mapper 2 | ![run-tests](https://github.com/jerodev/data-mapper/workflows/run-tests/badge.svg) [![Latest Stable Version](http://poser.pugx.org/jerodev/data-mapper/v)](https://packagist.org/packages/jerodev/data-mapper) 3 | 4 | This package will map any raw data into a predefined strong-typed PHP object. 5 | 6 | ## Installation 7 | The mapper has no external dependencies apart from PHP8.1 or higher. It can be installed using composer: 8 | 9 | ```bash 10 | composer require jerodev/data-mapper 11 | ``` 12 | 13 | ## Basic mapping 14 | Let's start with the basics. The mapper will map data directly to public properties on objects. If these properties have 15 | types defined either using types introduced in PHP7.4 or through [PHPDoc](https://phpstan.org/writing-php-code/phpdoc-types), the mapper will attempt to cast the data to these 16 | types. 17 | 18 | For example: imagine having an `Entity` class with the public properties `$id` and `$name`: 19 | 20 | ```php 21 | class User 22 | { 23 | public int $id; 24 | public string $name; 25 | } 26 | ``` 27 | 28 | To map data from an array we simply pass the class name and an array with data to the mapper. 29 | 30 | ```php 31 | $mapper = new \Jerodev\DataMapper\Mapper(); 32 | $entity = $mapper->map(User::class, [ 33 | 'id' => '5', 34 | 'name' => 'John Doe', 35 | ]); 36 | 37 | // User { 38 | // +id: 5, 39 | // +name: "John Doe", 40 | // } 41 | ``` 42 | 43 | This is a simple example, but the mapper can also map nested objects, arrays of objects, keyed arrays, and even multi-level arrays. 44 | 45 | ## Documentation 46 | More information about mapping, configuration and best practices can be found in [the documentation](https://docs.deviaene.eu/data-mapper/). 47 | 48 | ## License 49 | This library is licensed under the MIT License (MIT). Please see [License File](license.md) for more information. 50 | -------------------------------------------------------------------------------- /src/Attributes/PostMapping.php: -------------------------------------------------------------------------------- 1 | dataTypeFactory = new DataTypeFactory(); 22 | $this->objectMapper = new ObjectMapper( 23 | $this, 24 | $this->dataTypeFactory, 25 | ); 26 | } 27 | 28 | /** 29 | * Map anything! 30 | * 31 | * @param DataTypeCollection|string $typeCollection The type to map to. 32 | * @param mixed $data Deserialized data to map to the type. 33 | * @return mixed 34 | */ 35 | public function map($typeCollection, $data) 36 | { 37 | if (\is_string($typeCollection)) { 38 | return $this->map( 39 | $this->dataTypeFactory->fromString($typeCollection), 40 | $data, 41 | ); 42 | } 43 | 44 | if ($data === 'null' || $data === null) { 45 | if ($this->config->strictNullMapping === false || $typeCollection->isNullable()) { 46 | return null; 47 | } 48 | 49 | throw new UnexpectedNullValueException($this->dataTypeFactory->print($typeCollection)); 50 | } 51 | 52 | // Loop over all possible types and parse to the first one that matches 53 | foreach ($typeCollection->types as $type) { 54 | try { 55 | if ($type->isNative()) { 56 | return $this->mapNativeType($type, $data); 57 | } 58 | 59 | if ($type->isArray()) { 60 | return $this->mapArray($type, $data); 61 | } 62 | 63 | return $this->mapObject($type, $data); 64 | } catch (CouldNotMapValueException) { 65 | continue; 66 | } 67 | } 68 | 69 | throw new CouldNotMapValueException($data, $typeCollection); 70 | } 71 | 72 | /** 73 | * Remove cached class mappers. 74 | */ 75 | public function clearCache(): void 76 | { 77 | $this->objectMapper->clearCache(); 78 | } 79 | 80 | private function mapNativeType(DataType $type, mixed $data): float|object|bool|int|string|null 81 | { 82 | return match ($type->type) { 83 | 'null' => null, 84 | 'bool' => \filter_var($data, \FILTER_VALIDATE_BOOL), 85 | 'int' => (int) $data, 86 | 'float' => (float) $data, 87 | 'string' => (string) $data, 88 | 'object' => (object) $data, 89 | default => throw new CouldNotMapValueException($data, $type), 90 | }; 91 | } 92 | 93 | private function mapArray(DataType $type, mixed $data): array 94 | { 95 | if (! \is_iterable($data)) { 96 | throw new CouldNotMapValueException($data, $type); 97 | } 98 | 99 | if ($type->isGenericArray()) { 100 | return (array) $data; 101 | } 102 | 103 | $keyType = null; 104 | $valueType = $type->genericTypes[0]; 105 | if (\count($type->genericTypes) > 1) { 106 | [$keyType, $valueType] = $type->genericTypes; 107 | } 108 | 109 | $mappedArray = []; 110 | foreach ($data as $key => $value) { 111 | if ($keyType !== null) { 112 | $key = $this->map($keyType, $key); 113 | } 114 | 115 | $mappedArray[$key] = $this->map($valueType, $value); 116 | } 117 | 118 | return $mappedArray; 119 | } 120 | 121 | private function mapObject(DataType $type, mixed $data): ?object 122 | { 123 | try { 124 | return $this->objectMapper->map($type, $data); 125 | } catch (CouldNotResolveClassException) { 126 | throw new CouldNotMapValueException($data, $type); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/MapperCommands.php: -------------------------------------------------------------------------------- 1 | clearCache(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/MapperConfig.php: -------------------------------------------------------------------------------- 1 | */ 11 | public array $constructorArguments = []; 12 | 13 | /** @var array */ 14 | public array $properties = []; 15 | 16 | /** @var array */ 17 | public array $classAttributes = []; 18 | 19 | public function __construct( 20 | public readonly string $namespacedClassName, 21 | public readonly string $fileName, 22 | ) { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Objects/ClassBluePrinter.php: -------------------------------------------------------------------------------- 1 | dataTypeFactory = new DataTypeFactory(); 19 | $this->docBlockParser = new DocBlockParser(); 20 | } 21 | 22 | public function print(string $class): ClassBluePrint 23 | { 24 | $reflection = new ReflectionClass($class); 25 | $blueprint = new ClassBluePrint($class, $reflection->getFileName()); 26 | 27 | $this->printConstructor($reflection, $blueprint); 28 | $this->printProperties($reflection, $blueprint); 29 | $this->printAttributes($reflection, $blueprint); 30 | 31 | return $blueprint; 32 | } 33 | 34 | private function printConstructor(ReflectionClass $reflection, ClassBluePrint $bluePrint): void 35 | { 36 | $constructor = $reflection->getConstructor(); 37 | if ($constructor === null || $constructor->getNumberOfParameters() === 0) { 38 | return; 39 | } 40 | 41 | // Get information from potential doc comment 42 | $doc = null; 43 | if ($constructor->getDocComment()) { 44 | $doc = $this->docBlockParser->parse($constructor->getDocComment()); 45 | } 46 | 47 | // Loop over each parameter and describe it 48 | foreach ($constructor->getParameters() as $param) { 49 | // Using the getName() function omits the question mark for nullable types. 50 | $type = (string) $param->getType(); 51 | 52 | if ($doc !== null && \in_array($type, [null, 'array', 'iterable'])) { 53 | $type = $doc->getParamType($param->getName()); 54 | } 55 | 56 | $arg = [ 57 | 'type' => $this->resolveType( 58 | $this->dataTypeFactory->fromString($type), 59 | $reflection->getName(), 60 | ), 61 | ]; 62 | if ($param->isDefaultValueAvailable()) { 63 | $arg['default'] = $param->getDefaultValue(); 64 | } 65 | 66 | $bluePrint->constructorArguments[$param->getName()] = $arg; 67 | } 68 | } 69 | 70 | private function printProperties(ReflectionClass $reflection, ClassBluePrint $blueprint): void 71 | { 72 | $properties = $reflection->getProperties(); 73 | foreach ($properties as $property) { 74 | if (! $property->isPublic() || $property->isReadOnly()) { 75 | continue; 76 | } 77 | 78 | // Already mapped through constructor? 79 | if (\array_key_exists($property->getName(), $blueprint->constructorArguments)) { 80 | continue; 81 | } 82 | 83 | $type = $property->getType()?->getName(); 84 | if (\in_array($type, [null, 'array', 'iterable']) && $property->getDocComment()) { 85 | $doc = $this->docBlockParser->parse($property->getDocComment()); 86 | if ($doc->varType !== null) { 87 | $type = $doc->varType; 88 | } 89 | } 90 | 91 | $mapped = [ 92 | 'type' => $this->resolveType( 93 | $this->dataTypeFactory->fromString($type, $property->getType()->allowsNull()), 94 | $reflection->getName(), 95 | ), 96 | ]; 97 | if ($property->hasDefaultValue()) { 98 | $mapped['default'] = $property->getDefaultValue(); 99 | } 100 | 101 | $blueprint->properties[$property->getName()] = $mapped; 102 | } 103 | } 104 | 105 | private function printAttributes(ReflectionClass $reflection, ClassBluePrint $blueprint): void 106 | { 107 | foreach ($reflection->getAttributes(PostMapping::class) as $attribute) { 108 | $blueprint->classAttributes[] = $attribute->newInstance(); 109 | } 110 | 111 | // Also check parent for relevant attributes 112 | if ($reflection->getParentClass()) { 113 | $this->printAttributes($reflection->getParentClass(), $blueprint); 114 | } 115 | } 116 | 117 | private function resolveType(DataTypeCollection $type, string $className): DataTypeCollection 118 | { 119 | $baseClassName = \explode('\\', $className); 120 | $baseClassName = \end($baseClassName); 121 | 122 | $collection = []; 123 | foreach ($type->types as $dataType) { 124 | $generics = []; 125 | foreach ($dataType->genericTypes as $genericType) { 126 | $generics[] = $this->resolveType($genericType, $className); 127 | } 128 | 129 | $typeName = $dataType->type; 130 | if (\in_array($typeName, ['self', 'static', $baseClassName])) { 131 | $typeName = $className; 132 | } 133 | 134 | $collection[] = new DataType( 135 | $typeName, 136 | $dataType->isNullable, 137 | $generics, 138 | ); 139 | } 140 | 141 | return new DataTypeCollection($collection); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Objects/ClassResolver.php: -------------------------------------------------------------------------------- 1 | findSourceFile(); 17 | if ($sourceFile === null) { 18 | throw new CouldNotResolveClassException($name); 19 | } 20 | 21 | $name = $this->findClassNameInFile($name, $sourceFile); 22 | if (\class_exists($name)) { 23 | return $name; 24 | } 25 | 26 | throw new CouldNotResolveClassException($name, $sourceFile); 27 | } 28 | 29 | private function findSourceFile(): ?string 30 | { 31 | $backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 10); 32 | $mapperFileSuffix = \implode(\DIRECTORY_SEPARATOR, ['data-mapper', 'src', 'Mapper.php']); 33 | 34 | $foundMapper = false; 35 | foreach ($backtrace as $trace) { 36 | if (\str_ends_with($trace['file'], $mapperFileSuffix)) { 37 | $foundMapper = true; 38 | continue; 39 | } 40 | 41 | if ($foundMapper) { 42 | return $trace['file']; 43 | } 44 | } 45 | 46 | return null; 47 | } 48 | 49 | private function findClassNameInFile(string $name, string $sourceFile): string 50 | { 51 | $file = \file_get_contents($sourceFile); 52 | if ($file === false) { 53 | return $name; 54 | } 55 | 56 | $nameParts = \explode('\\', $name); 57 | $lastPart = \end($nameParts); 58 | 59 | $newline = false; 60 | $fileLength = \strlen($file); 61 | for ($i = 0; $i < $fileLength; $i++) { 62 | $char = $file[$i]; 63 | 64 | // Don't care about spaces 65 | if ($char === ' ' || $char === "\t") { 66 | continue; 67 | } 68 | 69 | if ($char === \PHP_EOL) { 70 | $newline = true; 71 | continue; 72 | } 73 | 74 | // Find the class in the same namespace as the caller 75 | if ($newline && $char === 'n' && \substr($file, $i, 10) === 'namespace ') { 76 | $i += 10; 77 | $namespace = ''; 78 | while (($char = $file[$i++]) !== ';') { 79 | $namespace .= $char; 80 | } 81 | 82 | $classInNamespace = $namespace . '\\' . $lastPart; 83 | if (\class_exists($classInNamespace)) { 84 | return $classInNamespace; 85 | } 86 | } 87 | 88 | // If we are after a newline and find a use statement, parse it! 89 | if ($newline && $char === 'u' && \substr($file, $i, 4) === 'use ') { 90 | $i += 4; 91 | 92 | $startAlias = false; 93 | $importName = ''; 94 | $importAlias = ''; 95 | while (($char = $file[$i++]) !== ';') { 96 | if ($char === ' ' && \substr($file, $i, 3) === 'as ') { 97 | $startAlias = true; 98 | $i += 3; 99 | continue; 100 | } 101 | 102 | if ($startAlias) { 103 | $importAlias .= $char; 104 | } else { 105 | $importName .= $char; 106 | } 107 | } 108 | 109 | $nameParts = \explode('\\', $importName); 110 | $lastImportPart = \end($nameParts); 111 | 112 | if ($importAlias === $lastPart || $lastImportPart === $lastPart) { 113 | return $importName; 114 | } 115 | 116 | $i--; 117 | } 118 | 119 | $newline = false; 120 | } 121 | 122 | return $name; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Objects/DocBlock.php: -------------------------------------------------------------------------------- 1 | */ 8 | public array $paramTypes = []; 9 | public ?string $returnType = null; 10 | public ?string $varType = null; 11 | 12 | public function getParamType(string $name): ?string 13 | { 14 | return $this->paramTypes[$name] ?? null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Objects/DocBlockParser.php: -------------------------------------------------------------------------------- 1 | $this->parseParam($contents, $i, $docblock), 23 | '@return', '@var' => $this->parseReturn($contents, $i, $docblock, \substr($identifier, 1) . 'Type'), 24 | default => null, 25 | }; 26 | } 27 | } 28 | 29 | return $docblock; 30 | } 31 | 32 | private function parseParam(string $contents, int &$i, DocBlock $docblock): void 33 | { 34 | $type = $this->parseType($contents, $i); 35 | 36 | $docblock->paramTypes[$this->parseVariable($contents, $i)] = $type; 37 | } 38 | 39 | private function parseReturn(string $contents, int &$i, DocBlock $docblock, string $docProperty = 'returnType'): void 40 | { 41 | $docblock->{$docProperty} = $this->parseType($contents, $i); 42 | } 43 | 44 | private function parseType(string $contents, int &$i): string 45 | { 46 | $type = ''; 47 | $sawSpace = false; 48 | $lastChar = ''; 49 | for (; $i < \strlen($contents); $i++) { 50 | $char = $contents[$i]; 51 | if ($char === ' ') { 52 | $type .= $char; 53 | $sawSpace = true; 54 | continue; 55 | } 56 | 57 | if (\in_array($char, ['|', ',', ':'])) { 58 | $type .= $char; 59 | $lastChar = $char; 60 | $sawSpace = false; 61 | continue; 62 | } 63 | 64 | if ($sawSpace && ! \in_array($lastChar, ['|', ',', ':'])) { 65 | break; 66 | } 67 | 68 | $type .= $char; 69 | $lastChar = $char; 70 | $sawSpace = false; 71 | } 72 | 73 | return \trim($type); 74 | } 75 | 76 | private function parseVariable(string $contents, int &$i): string 77 | { 78 | $variable = ''; 79 | for (; $i < \strlen($contents); $i++) { 80 | $char = $contents[$i]; 81 | if ($char === ' ') { 82 | if (empty($variable)) { 83 | continue; 84 | } 85 | 86 | break; 87 | } 88 | 89 | if (empty($variable) && $char !== '$') { 90 | throw new \Exception('Expected variable to start with \'$\', found \'' . $char . '\'.'); 91 | } 92 | 93 | $variable .= $char; 94 | } 95 | 96 | return \trim(\substr($variable, 1)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Objects/ObjectMapper.php: -------------------------------------------------------------------------------- 1 | classBluePrinter = new ClassBluePrinter(); 25 | } 26 | 27 | /** 28 | * @param DataType|string $type 29 | * @param array|string $data 30 | * @return object|null 31 | * @throws CouldNotResolveClassException 32 | */ 33 | public function map(DataType|string $type, array|string $data): ?object 34 | { 35 | $class = $this->dataTypeFactory->classResolver->resolve(\is_string($type) ? $type : $type->type); 36 | if (\is_subclass_of($class, MapsItself::class)) { 37 | return \call_user_func([$class, 'mapSelf'], $data, $this->mapper); 38 | } 39 | 40 | // If the data is a string and the class is an enum, create the enum. 41 | if (\is_string($data) && \is_subclass_of($class, \BackedEnum::class)) { 42 | if ($this->mapper->config->enumTryFrom) { 43 | return $class::tryFrom($data); 44 | } 45 | 46 | return $class::from($data); 47 | } 48 | 49 | $functionName = self::MAPPER_FUNCTION_PREFIX . \md5($class . ($type instanceof DataType && $type->isNullable ? '1' : '0')); 50 | if ($this->mapper->config->classCacheKeySource === 'md5' || $this->mapper->config->classCacheKeySource === 'modified') { 51 | $reflection = new ReflectionClass($class); 52 | $functionName = match ($this->mapper->config->classCacheKeySource) { 53 | 'md5' => self::MAPPER_FUNCTION_PREFIX . \md5(\md5_file($reflection->getFileName()) . $functionName), 54 | 'modified' => self::MAPPER_FUNCTION_PREFIX . \md5(\filemtime($reflection->getFileName()) . $functionName), 55 | }; 56 | } 57 | 58 | $fileName = $this->mapperDirectory() . \DIRECTORY_SEPARATOR . $functionName . '.php'; 59 | if (! \file_exists($fileName)) { 60 | \file_put_contents( 61 | $fileName, 62 | $this->createObjectMappingFunction( 63 | $this->classBluePrinter->print($class), 64 | $functionName, 65 | $type instanceof DataType && $type->isNullable, 66 | ), 67 | ); 68 | } 69 | 70 | // Include the function containing file and call the function. 71 | require_once($fileName); 72 | return ($functionName)($this->mapper, $data); 73 | } 74 | 75 | public function clearCache(): void 76 | { 77 | foreach (\glob($this->mapperDirectory() . \DIRECTORY_SEPARATOR . self::MAPPER_FUNCTION_PREFIX . '*.php') as $file) { 78 | \unlink($file); 79 | } 80 | } 81 | 82 | public function mapperDirectory(): string 83 | { 84 | $dir = \str_replace('{$TMP}', \sys_get_temp_dir(), $this->mapper->config->classMapperDirectory); 85 | if (! \file_exists($dir) && ! \mkdir($dir, 0777, true) && ! \is_dir($dir)) { 86 | throw new \RuntimeException("Could not create caching directory '{$dir}'"); 87 | } 88 | 89 | return \rtrim($dir, \DIRECTORY_SEPARATOR); 90 | } 91 | 92 | private function createObjectMappingFunction(ClassBluePrint $blueprint, string $mapFunctionName, bool $isNullable): string 93 | { 94 | $tab = ' '; 95 | $content = ''; 96 | 97 | if ($isNullable) { 98 | $content .= $tab . $tab . 'if ($data === [] && $mapper->config->nullObjectFromEmptyArray) {' . \PHP_EOL; 99 | $content .= $tab . $tab . $tab . 'return null;' . \PHP_EOL; 100 | $content .= $tab . $tab . '}' . \PHP_EOL . \PHP_EOL; 101 | } 102 | 103 | // Instantiate a new object 104 | $args = []; 105 | foreach ($blueprint->constructorArguments as $name => $argument) { 106 | $arg = "\$data['{$name}']"; 107 | if ($argument['type']->isNullable()) { 108 | $arg = "({$arg} ?? null)"; 109 | } 110 | 111 | if ($argument['type'] !== null) { 112 | $arg = $this->castInMapperFunction($arg, $argument['type'], $blueprint); 113 | } 114 | 115 | if (\array_key_exists('default', $argument)) { 116 | $arg = $this->wrapDefault($arg, $name, $argument['default']); 117 | } 118 | 119 | $args[] = $arg; 120 | } 121 | $content .= $tab . $tab . '$x = new ' . $blueprint->namespacedClassName . '(' . \implode(', ', $args) . ');'; 122 | 123 | // Map properties 124 | foreach ($blueprint->properties as $name => $property) { 125 | // Use a foreach to map key/value arrays 126 | if (\count($property['type']->types) === 1 && $property['type']->types[0]->isArray() && \count($property['type']->types[0]->genericTypes) === 2) { 127 | $content .= $this->buildPropertyForeachMapping($name, $property, $blueprint); 128 | 129 | continue; 130 | } 131 | 132 | $propertyName = "\$data['{$name}']"; 133 | if ($property['type']->isNullable()) { 134 | $propertyName = "({$propertyName} ?? null)"; 135 | } 136 | 137 | $propertyMap = $this->castInMapperFunction($propertyName, $property['type'], $blueprint); 138 | if (\array_key_exists('default', $property)) { 139 | $propertyMap = $this->wrapDefault($propertyMap, $name, $property['default']); 140 | } 141 | 142 | $propertySet = \PHP_EOL . $tab . $tab . '$x->' . $name . ' = ' . $propertyMap . ';'; 143 | 144 | if ($this->mapper->config->allowUninitializedFields && ! \array_key_exists('default', $property)) { 145 | $propertySet = $this->wrapArrayKeyExists($propertySet, $name); 146 | } 147 | 148 | $content .= $propertySet; 149 | } 150 | 151 | // Post mapping functions? 152 | foreach ($blueprint->classAttributes as $attribute) { 153 | if ($attribute instanceof PostMapping) { 154 | if (\is_string($attribute->postMappingCallback)) { 155 | $content.= \PHP_EOL . \PHP_EOL . $tab . $tab . "\$x->{$attribute->postMappingCallback}(\$data, \$x);"; 156 | } else { 157 | $content.= \PHP_EOL . \PHP_EOL . $tab . $tab . "\call_user_func({$attribute->postMappingCallback}, \$data, \$x);"; 158 | } 159 | } 160 | } 161 | 162 | // Render the function 163 | $mapperClass = Mapper::class; 164 | return <<types) === 1) { 181 | $type = $type->types[0]; 182 | 183 | if ($type->isNullable) { 184 | return "{$propertyName} === null ? null : " . $this->castInMapperFunction($propertyName, new DataTypeCollection([$type->removeNullable()]), $bluePrint); 185 | } 186 | 187 | if ($type->isNative()) { 188 | return match ($type->type) { 189 | 'null' => 'null', 190 | 'bool' => "\\filter_var({$propertyName}, \FILTER_VALIDATE_BOOL)", 191 | 'float' => '(float) ' . $propertyName, 192 | 'int' => '(int) ' . $propertyName, 193 | 'string' => '(string) ' . $propertyName, 194 | 'object' => '(object) ' . $propertyName, 195 | default => $propertyName, 196 | }; 197 | } 198 | 199 | if ($type->isArray()) { 200 | if ($type->isGenericArray()) { 201 | return '(array) ' . $propertyName; 202 | } 203 | if (\count($type->genericTypes) === 1) { 204 | $uniqid = \uniqid(); 205 | return "\\array_map(static fn (\$x{$uniqid}) => " . $this->castInMapperFunction('$x' . $uniqid, $type->genericTypes[0], $bluePrint) . ", {$propertyName})"; 206 | } 207 | } 208 | 209 | if (\is_subclass_of($type->type, \BackedEnum::class)) { 210 | $enumFunction = $this->mapper->config->enumTryFrom ? 'tryFrom' : 'from'; 211 | 212 | return "{$type->type}::{$enumFunction}({$propertyName})"; 213 | } 214 | 215 | if (\is_subclass_of($type->type, MapsItself::class)) { 216 | return "{$type->type}::mapSelf({$propertyName}, \$mapper)"; 217 | } 218 | 219 | $className = $this->dataTypeFactory->print($type, $bluePrint->fileName); 220 | if (\class_exists($className)) { 221 | return "\$mapper->objectMapper->map('{$className}', {$propertyName})"; 222 | } 223 | } 224 | 225 | return '$mapper->map(\'' . $this->dataTypeFactory->print($type, $bluePrint->fileName) . '\', ' . $propertyName . ')'; 226 | } 227 | 228 | private function wrapDefault(string $value, string $arrayKey, mixed $defaultValue): string 229 | { 230 | if (\str_contains($value, '?')) { 231 | $value = "({$value})"; 232 | } 233 | 234 | if (\is_object($defaultValue)) { 235 | $defaultRaw = 'new ' . $defaultValue::class . '()'; 236 | } else { 237 | $defaultRaw = \var_export($defaultValue, true); 238 | } 239 | 240 | return "(\\array_key_exists('{$arrayKey}', \$data) ? {$value} : {$defaultRaw})"; 241 | } 242 | 243 | private function wrapArrayKeyExists(string $expression, string $arrayKey): string 244 | { 245 | $content = \PHP_EOL . \str_repeat(' ', 2) . "if (\\array_key_exists('{$arrayKey}', \$data)) {"; 246 | $content .= \str_replace(\PHP_EOL, \PHP_EOL . ' ', $expression) . \PHP_EOL; 247 | $content .= \str_repeat(' ', 2) . '}'; 248 | 249 | return $content; 250 | } 251 | 252 | public function __destruct() 253 | { 254 | if ($this->mapper->config->debug) { 255 | $this->clearCache(); 256 | } 257 | } 258 | 259 | /** @param array{type: DataTypeCollection, default?: mixed} $property */ 260 | private function buildPropertyForeachMapping(string $propertyName, array $property, ClassBluePrint $blueprint): string 261 | { 262 | $foreach = \PHP_EOL . \str_repeat(' ', 2) . '$x->' . $propertyName . ' = [];'; 263 | $foreach .= \PHP_EOL . \str_repeat(' ', 2) . 'foreach ($data[\'' . $propertyName . '\'] as $key => $value) {'; 264 | $foreach .= \PHP_EOL . \str_repeat(' ', 3) . '$x->' . $propertyName . '[' . $this->castInMapperFunction('$key', $property['type']->types[0]->genericTypes[0], $blueprint) . '] = '; 265 | $foreach .= $this->castInMapperFunction('$value', $property['type']->types[0]->genericTypes[1], $blueprint) . ';'; 266 | $foreach .= \PHP_EOL . \str_repeat(' ', 2) . '}'; 267 | 268 | if (\array_key_exists('default', $property) || $this->mapper->config->allowUninitializedFields) { 269 | $foreach = $this->wrapArrayKeyExists($foreach, $propertyName); 270 | } 271 | 272 | return $foreach; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/Types/DataType.php: -------------------------------------------------------------------------------- 1 | $genericTypes 11 | * @param string|null $arrayFields 12 | */ 13 | public function __construct( 14 | public readonly string $type, 15 | public readonly bool $isNullable, 16 | public readonly array $genericTypes = [], 17 | public readonly ?string $arrayFields = null, 18 | ) { 19 | } 20 | 21 | public function isArray(): bool 22 | { 23 | return \in_array( 24 | $this->type, 25 | [ 26 | 'array', 27 | 'iterable', 28 | ], 29 | ); 30 | } 31 | 32 | public function isGenericArray(): bool 33 | { 34 | return $this->isArray() && empty($this->genericTypes); 35 | } 36 | 37 | /** @see https://www.php.net/manual/en/language.types.intro.php */ 38 | public function isNative(): bool 39 | { 40 | return \in_array( 41 | $this->type, 42 | [ 43 | 'null', 44 | 'bool', 45 | 'float', 46 | 'int', 47 | 'string', 48 | 'object', 49 | ], 50 | ); 51 | } 52 | 53 | /** 54 | * returns a new instance of itself that is not nullable 55 | */ 56 | public function removeNullable(): self 57 | { 58 | return new self($this->type, false, $this->genericTypes); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Types/DataTypeCollection.php: -------------------------------------------------------------------------------- 1 | */ 8 | public readonly array $types; 9 | 10 | /** 11 | * @param array $types 12 | */ 13 | public function __construct(array $types) 14 | { 15 | // Ensure null is always the last type 16 | $typesArray = []; 17 | $null = null; 18 | foreach ($types as $type) { 19 | if ($type->type === 'null') { 20 | $null = $type; 21 | } else { 22 | $typesArray[] = $type; 23 | } 24 | } 25 | 26 | if ($null !== null) { 27 | $typesArray[] = $null; 28 | } 29 | 30 | $this->types = $typesArray; 31 | } 32 | 33 | public function isNullable(): bool 34 | { 35 | return ! empty( 36 | \array_filter( 37 | $this->types, 38 | static fn (DataType $t) => $t->isNullable || $t->type === 'null', 39 | ) 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Types/DataTypeFactory.php: -------------------------------------------------------------------------------- 1 | */ 12 | private array $typeCache = []; 13 | 14 | public function __construct( 15 | public readonly ClassResolver $classResolver = new ClassResolver(), 16 | ) { 17 | } 18 | 19 | /** 20 | * @throws InvalidTypeException 21 | * @throws UnexpectedTokenException 22 | */ 23 | public function fromString(string $rawType, bool $forceNullable = false): DataTypeCollection 24 | { 25 | $rawType = \trim($rawType); 26 | 27 | if (\array_key_exists($rawType, $this->typeCache)) { 28 | return $this->typeCache[$rawType]; 29 | } 30 | 31 | $tokens = $this->tokenize($rawType); 32 | if (\count($tokens) === 1) { 33 | return $this->typeCache[$rawType] = new DataTypeCollection([ 34 | new DataType($rawType, $forceNullable), 35 | ]); 36 | } 37 | 38 | // Parse tokens and make sure nothing is left. 39 | $type = $this->parseTokens($tokens); 40 | if (! empty($tokens)) { 41 | throw new UnexpectedTokenException(\array_shift($tokens)); 42 | } 43 | 44 | return $this->typeCache[$rawType] = $type; 45 | } 46 | 47 | /** 48 | * Prints a type with resolved class names, if possible. 49 | * 50 | * @param DataTypeCollection|DataType $type 51 | * @param string|null $sourceFile 52 | * @return string 53 | */ 54 | public function print(DataTypeCollection|DataType $type, ?string $sourceFile = null): string 55 | { 56 | if ($type instanceof DataType) { 57 | $type = new DataTypeCollection([$type]); 58 | } 59 | \assert($type instanceof DataTypeCollection); 60 | 61 | $types = []; 62 | foreach ($type->types as $t) { 63 | $typeString = $t->type; 64 | 65 | // Attempt to resolve the class name 66 | try { 67 | $typeString = $t->isNative() || $t->isArray() 68 | ? $typeString 69 | : $this->classResolver->resolve($t->type, $sourceFile); 70 | } catch (\Throwable) { 71 | } 72 | 73 | // Add generic types between < > 74 | if (\count($t->genericTypes) > 0) { 75 | $typeString .= '<' . \implode( 76 | ', ', 77 | \array_map( 78 | fn (DataTypeCollection $c) => $this->print($c, $sourceFile), 79 | $t->genericTypes, 80 | ), 81 | ) . '>'; 82 | } 83 | 84 | if ($t->isNullable) { 85 | $typeString = '?' . $typeString; 86 | } 87 | 88 | $types[] = $typeString; 89 | } 90 | 91 | return \implode('|', $types); 92 | } 93 | 94 | /** 95 | * @param string $type 96 | * @return array 97 | */ 98 | private function tokenize(string $type): array 99 | { 100 | $tokens = []; 101 | $token = ''; 102 | 103 | for ($i = 0; $i < \strlen($type); $i++) { 104 | $char = $type[$i]; 105 | if ($char === ' ') { 106 | continue; 107 | } 108 | 109 | if (\in_array($char, ['<', '>', '?', ',', '|'])) { 110 | if (! empty(\trim($token))) { 111 | $tokens[] = $token; 112 | } 113 | $tokens[] = $char; 114 | $token = ''; 115 | } else if ($char === '[') { 116 | if (! empty(\trim($token))) { 117 | $tokens[] = $token; 118 | } 119 | $token = ''; 120 | if ($type[++$i] === ']') { 121 | $tokens[] = '[]'; 122 | continue; 123 | } 124 | 125 | throw new UnexpectedTokenException('['); 126 | } else if ($char === '{') { 127 | if ($token !== 'array') { 128 | throw new UnexpectedTokenException('{'); 129 | } 130 | 131 | $tokens[] = $token; 132 | $token = ''; 133 | for (; $i < \strlen($type); $i++) { 134 | $token .= $type[$i]; 135 | if ($type[$i] === '}') { 136 | break; 137 | } 138 | } 139 | 140 | if (! \str_ends_with($token, '}')) { 141 | throw new InvalidTypeException($type, 'Missing closing }'); 142 | } 143 | 144 | $tokens[] = $token; 145 | $token = ''; 146 | } else { 147 | $token .= $char; 148 | } 149 | } 150 | 151 | if ($token !== '') { 152 | $tokens[] = $token; 153 | } 154 | 155 | return $tokens; 156 | } 157 | 158 | /** 159 | * @param array $tokens 160 | * @return DataTypeCollection 161 | */ 162 | private function parseTokens(array &$tokens): DataTypeCollection 163 | { 164 | $types = []; 165 | 166 | $stringStack = ''; 167 | $nullable = false; 168 | 169 | while ($token = \array_shift($tokens)) { 170 | if ($token === '<') { 171 | $genericTypes = $this->fetchGenericTypes($tokens); 172 | while (($tokens[0] ?? '') === '[]') { 173 | $stringStack .= \array_shift($tokens); 174 | } 175 | $types[] = $this->makeDataType( 176 | $stringStack, 177 | $nullable, 178 | $genericTypes, 179 | ); 180 | $stringStack = ''; 181 | $nullable = false; 182 | 183 | continue; 184 | } 185 | 186 | if ($token === '|') { 187 | $types[] = $this->makeDataType($stringStack, $nullable); 188 | $stringStack = ''; 189 | $nullable = false; 190 | 191 | continue; 192 | } 193 | 194 | if ($token === '?') { 195 | $nullable = true; 196 | continue; 197 | } 198 | 199 | if (\str_starts_with($token, '{')) { 200 | $types[] = new DataType('array', $nullable, arrayFields: $token); 201 | $stringStack = ''; 202 | $nullable = false; 203 | 204 | continue; 205 | } 206 | 207 | if (\in_array($token, ['>', ','])) { 208 | throw new UnexpectedTokenException($token); 209 | } 210 | 211 | $stringStack .= $token; 212 | } 213 | 214 | if (! empty($stringStack)) { 215 | $types[] = $this->makeDataType($stringStack, $nullable); 216 | } 217 | 218 | return new DataTypeCollection($types); 219 | } 220 | 221 | /** 222 | * @param array $tokens 223 | * @return array 224 | */ 225 | private function fetchGenericTypes(array &$tokens): array 226 | { 227 | $types = []; 228 | $collection = []; 229 | $stringStack = ''; 230 | $nullable = false; 231 | while ($token = \array_shift($tokens)) { 232 | if ($token === '<') { 233 | $types[] = $this->makeDataType( 234 | $stringStack, 235 | $nullable, 236 | $this->fetchGenericTypes($tokens), 237 | ); 238 | $stringStack = ''; 239 | $nullable = false; 240 | 241 | continue; 242 | } 243 | 244 | if ($token === ',' || $token === '|') { 245 | if (! empty($stringStack)) { 246 | $types[] = $this->makeDataType($stringStack, $nullable); 247 | } 248 | 249 | if ($token === ',') { 250 | $collection[] = new DataTypeCollection($types); 251 | $types = []; 252 | } 253 | 254 | $stringStack = ''; 255 | $nullable = false; 256 | continue; 257 | } 258 | 259 | if ($token === '>') { 260 | break; 261 | } 262 | 263 | if ($token === '?') { 264 | $nullable = true; 265 | continue; 266 | } 267 | 268 | if (\str_starts_with($token, '{')) { 269 | $types[] = new DataType('array', $nullable, arrayFields: $token); 270 | $stringStack = ''; 271 | $nullable = false; 272 | 273 | continue; 274 | } 275 | 276 | $stringStack .= $token; 277 | } 278 | 279 | if (! empty($stringStack)) { 280 | $types[] = $this->makeDataType($stringStack, $nullable); 281 | } 282 | if (! empty($types)) { 283 | $collection[] = new DataTypeCollection($types); 284 | } 285 | 286 | return $collection; 287 | } 288 | 289 | /** 290 | * @param string $type 291 | * @param bool $nullable 292 | * @param array $genericTypes 293 | * @return DataType 294 | */ 295 | private function makeDataType(string $type, bool $nullable = false, array $genericTypes = []): DataType 296 | { 297 | // Parse a stack of [] arrays 298 | $arrayStack = 0; 299 | while (\str_ends_with($type, '[]')) { 300 | $type = \substr($type, 0, -2); 301 | $arrayStack++; 302 | } 303 | if ($arrayStack > 0) { 304 | $type = new DataType($type, false, $genericTypes); 305 | for ($i = 0; $i < $arrayStack; $i++) { 306 | $type = new DataType( 307 | 'array', 308 | $arrayStack - $i === 1 ? $nullable : false, 309 | [ 310 | new DataTypeCollection([$type]), 311 | ], 312 | ); 313 | } 314 | 315 | return $type; 316 | } 317 | 318 | return new DataType($type, $nullable, $genericTypes); 319 | } 320 | } 321 | --------------------------------------------------------------------------------