commit 1cec4a45f6d815d2898350808dcc23ed9ea28dcf Author: Josha von Gizycki Date: Mon Feb 16 16:05:55 2026 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8e6f5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +var +vendor +tests/.phpunit.cache diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d615590 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: cc +cc: stan test + +.PHONY: stan +stan: + vendor/bin/phpstan analyse src --level=10 + +.PHONY: test +test: + vendor/bin/phpunit + +.PHONY: classes +classes: var/classes.png + +var/classes.png: $(shell find src) + vendor/bin/php-class-diagram src | plantuml -p > var/classes.png diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e56cfae --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "kartierung/kartierung", + "version": "0.1", + "require": { + "php": "^8.2", + "ext-pdo": "*" + }, + "autoload": { + "psr-4": { + "Kartierung\\": [ + "src/", + "tests/" + ] + } + }, + "require-dev": { + "phpunit/phpunit": "^12.4", + "phpstan/phpstan": "^2.1", + "smeghead/php-class-diagram": "^1.6", + "ext-pdo_sqlite": "*" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..15aba29 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1922 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "16013e0a687be0db8ad3de75868e4f02", + "packages": [], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + }, + "time": "2025-08-13T20:13:15+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.31", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", + "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-10-10T14:14:11+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.4.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", + "reference": "67e8aed88f93d0e6e1cb7effe1a2dfc2fee6022c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.6.1", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.3.7" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.4.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-09-24T13:44:41+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/961bc913d42fe24a257bfff826a5068079ac7782", + "reference": "961bc913d42fe24a257bfff826a5068079ac7782", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:37+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "12.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc5413a2e6d240d2f6d9317bdf7f0a24e73de194", + "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.4.0", + "phpunit/php-file-iterator": "^6.0.0", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.0", + "sebastian/comparator": "^7.1.3", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/type": "^6.0.3", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.4-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.1" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2025-10-09T14:08:29+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2025-09-14T09:36:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/dc904b4bb3ab070865fa4068cd84f3da8b945148", + "reference": "dc904b4bb3ab070865fa4068cd84f3da8b945148", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.2" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2025-08-20T11:27:00+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "reference": "24a711b5c916efc6d6e62aa65aa2ec98fef77f68", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-08-12T14:11:56+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:16:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-29T11:29:25+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:28+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:17+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:44:59+00:00" + }, + { + "name": "sebastian/type", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:57:12+00:00" + }, + { + "name": "sebastian/version", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T05:00:38+00:00" + }, + { + "name": "smeghead/php-class-diagram", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/smeghead/php-class-diagram.git", + "reference": "964306a904534a7b3a3c6a2f6504a0cb19a78b52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/smeghead/php-class-diagram/zipball/964306a904534a7b3a3c6a2f6504a0cb19a78b52", + "reference": "964306a904534a7b3a3c6a2f6504a0cb19a78b52", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.2", + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.3", + "symfony/finder": "^5.3|^6.0|^7.0" + }, + "require-dev": { + "clue/phar-composer": "^1.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^11.5", + "smeghead/php-vendor-credits": "~0.0.4" + }, + "bin": [ + "bin/php-class-diagram" + ], + "type": "library", + "autoload": { + "psr-4": { + "Smeghead\\PhpClassDiagram\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "smeghead", + "email": "smeghead7@gmail.com" + } + ], + "description": "A CLI tool that parses the PHP source directory and outputs PlantUML scripts.", + "keywords": [ + "classdiagram", + "plantuml" + ], + "support": { + "issues": "https://github.com/smeghead/php-class-diagram/issues", + "source": "https://github.com/smeghead/php-class-diagram/tree/v1.6.1" + }, + "time": "2026-01-12T05:09:20+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-26T15:07:59+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.2", + "ext-pdo": "*" + }, + "platform-dev": { + "ext-pdo_sqlite": "*" + }, + "plugin-api-version": "2.6.0" +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d70c857 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,24 @@ + + + + + tests + + + + + + src + + + diff --git a/src/Analyze/EntityAnalyzer.php b/src/Analyze/EntityAnalyzer.php new file mode 100644 index 0000000..ef312d3 --- /dev/null +++ b/src/Analyze/EntityAnalyzer.php @@ -0,0 +1,172 @@ + $entity + */ + public function __construct( + private string $entity + ) {} + + /** + * @return EntityResult + */ + public function analyze(): EntityResult + { + $refClass = new ReflectionClass($this->entity); + + $fields = $this->fields($refClass); + + return new EntityResult( + classFqcn: $refClass->getName(), + tableName: $this->tableName($refClass), + fields: $fields, + idField: $this->idField($fields) + ); + } + + /** + * @param ReflectionClass $refClass + * @return string + */ + private function tableName(ReflectionClass $refClass): string + { + $attrs = $refClass->getAttributes(Table::class); + + if (count($attrs) !== 1) { + //throw new InvalidEntity('Attribute #[Table] not found on entity ' . $refClass->name); + return $refClass->getShortName(); + } + + /** @var string */ + return $attrs[0]->getArguments()[0]; + } + + /** + * @param ReflectionClass $refClass + * @return list> + */ + private function fields(ReflectionClass $refClass): array + { + $props = $refClass->getProperties(); + + return array_map(fn(ReflectionProperty $prop) => $this->fieldFromProperty($prop), $props); + } + + /** + * @param list> $fields + * @return EntityField + */ + private function idField(array $fields): EntityField + { + $idFields = array_filter($fields, static fn(EntityField $f) => $f->isIdField); + $nrIdFields = count($idFields); + + if ($nrIdFields === 0 || $nrIdFields > 1) { + throw new InvalidEntity("Invalid number of id fields found: $nrIdFields, expected: 1"); + } + + return $idFields[array_key_first($idFields)]; + } + + /** + * @param ReflectionProperty $prop + * @return EntityField + */ + private function fieldFromProperty(ReflectionProperty $prop): EntityField + { + $attrs = $prop->getAttributes(Column::class); + + if (count($attrs) === 1) { + /** @var string $name */ + $name = $attrs[0]->getArguments()[0]; + } else { + $name = $prop->getName(); + } + + $refClass = $prop->getDeclaringClass(); + $writeAccess = $this->writeAccess($refClass, $prop); + $readAccess = $this->readAccess($refClass, $prop); + + return new EntityField( + name: $prop->name, + fqcn: $this->entity, + columnName: $name, + isIdField: count($prop->getAttributes(Id::class)) > 0, + writeAccess: $writeAccess, + readAccess: $readAccess + ); + } + + /** + * @param ReflectionClass $refClass + * @param ReflectionProperty $prop + * @return FieldAccess + */ + public function writeAccess(ReflectionClass $refClass, ReflectionProperty $prop): FieldAccess + { + $wither = FieldAccess::witherName($prop->getName()); + $setter = FieldAccess::setterName($prop->getName()); + + if ($refClass->hasMethod($setter)) { + $writeAccess = FieldAccess::GETSET; + } elseif ($refClass->hasMethod($wither)) { + $returnType = $refClass->getMethod($wither)->getReturnType(); + if (!$returnType instanceof ReflectionNamedType || $returnType->getName() !== 'self') { + throw new InvalidEntity( + "Field $refClass->name::$prop->name defines wither writing method with invalid return type, " . + "expecting `self`." + ); + } + + $writeAccess = FieldAccess::WITHER; + } elseif ($prop->isPublic() && !$prop->isReadOnly()) { + $writeAccess = FieldAccess::PUBLIC; + } else { + throw new InvalidEntity( + "Field $refClass->name::$prop->name has no valid writing method. " + . "Implement $setter, $wither or make the field public." + ); + } + + return $writeAccess; + } + + /** + * @param ReflectionClass $refClass + * @param ReflectionProperty $prop + * @return FieldAccess + */ + public function readAccess(ReflectionClass $refClass, ReflectionProperty $prop): FieldAccess + { + $getter = 'get' . ucfirst($prop->getName()); + + if ($refClass->hasMethod($getter)) { + $readAccess = FieldAccess::GETSET; + } elseif ($prop->isPublic()) { + $readAccess = FieldAccess::PUBLIC; + } else { + throw new InvalidEntity( + "Field $refClass->name::$prop->name has no valid reading method. " + . "Implement $getter or make the field public." + ); + } + + return $readAccess; + } +} diff --git a/src/Analyze/EntityField.php b/src/Analyze/EntityField.php new file mode 100644 index 0000000..f44806e --- /dev/null +++ b/src/Analyze/EntityField.php @@ -0,0 +1,23 @@ + $fqcn + */ + public function __construct( + public string $name, + public string $fqcn, + public string $columnName, + public bool $isIdField, + public FieldAccess $writeAccess, + public FieldAccess $readAccess, + ) {} +} diff --git a/src/Analyze/EntityResult.php b/src/Analyze/EntityResult.php new file mode 100644 index 0000000..7ff9091 --- /dev/null +++ b/src/Analyze/EntityResult.php @@ -0,0 +1,23 @@ + $classFqcn + * @param list> $fields + * @param EntityField $idField + */ + public function __construct( + public string $classFqcn, + public string $tableName, + public array $fields, + public EntityField $idField + ) {} +} diff --git a/src/Analyze/FieldAccess.php b/src/Analyze/FieldAccess.php new file mode 100644 index 0000000..f5a1624 --- /dev/null +++ b/src/Analyze/FieldAccess.php @@ -0,0 +1,22 @@ + $repositoryClass + * @noinspection PhpDocMissingThrowsInspection + */ + public function analyze( + string $repositoryClass + ): RepositoryResult + { + /** @noinspection PhpUnhandledExceptionInspection */ + $refClass = new ReflectionClass($repositoryClass); + + return new RepositoryResult( + methods: $this->methods($refClass) + ); + } + + /** + * @param ReflectionClass $refClass + * @return array + */ + private function methods(ReflectionClass $refClass): array + { + $refMethods = $refClass->getMethods(); + + $methods = []; + foreach ($refMethods as $refMethod) { + $query = $this->queryString($refMethod); + $returnType = $this->returnType($refMethod); + $listType = $this->listType($refMethod); + $params = $this->parameters($refMethod); + + $name = $refMethod->name; + $resolutionStrategy = $this->methodValidator->forRepositoryMethod( + $refClass->name, + $name, + $query, + $returnType, + $listType, + $params + ); + + $methods[$refMethod->name] = new RepositoryMethod( + query: $query, + returnType: $returnType, + returnListType: $listType, + name: $name, + parameters: $params, + resolutionStrategy: $resolutionStrategy + ); + } + + return $methods; + } + + /** + * @param ReflectionMethod $refMethod + * @return string|null + */ + private function queryString(ReflectionMethod $refMethod): ?string + { + $queryAttr = $refMethod->getAttributes(Query::class); + + $query = null; + if (count($queryAttr) === 1) { + /** @var Query $queryInst */ + $queryInst = $queryAttr[0]->newInstance(); + $query = $queryInst->query; + } + + return $query; + } + + /** + * @param ReflectionMethod $refMethod + * @return class-string|string + */ + private function returnType(ReflectionMethod $refMethod): string + { + $returnType = $refMethod->getReturnType(); + + if ($returnType instanceof ReflectionUnionType + || $returnType instanceof ReflectionIntersectionType + || $returnType === null) { + throw new InvalidRepository( + "Return type of $refMethod->class::$refMethod->name has an invalid return type. " + . 'Simple type expected. Unions, Intersections or no type are not simple.' + ); + } + + /** @var class-string|string */ + return $returnType->__toString(); + } + + /** + * @param ReflectionMethod $refMethod + * @return class-string|null + */ + private function listType(ReflectionMethod $refMethod): ?string + { + $listResultOfAttr = $refMethod->getAttributes(ListResultOf::class); + + $listType = null; + if (count($listResultOfAttr) === 1) { + /** @var ListResultOf $inst */ + $inst = $listResultOfAttr[0]->newInstance(); + $listType = $inst->entitiyFqcn; + + if (!class_exists($listType)) { + throw new InvalidRepository( + "ListResultOf parameter of $refMethod->class::$refMethod->name does not map to a class. " + . 'Fqcn of entity class expected.' + ); + } + } + + return $listType; + } + + /** + * @param ReflectionMethod $refMethod + * @return list + */ + private function parameters(ReflectionMethod $refMethod): array + { + $params = []; + foreach ($refMethod->getParameters() as $param) { + $type = $param->getType(); + + if ($type instanceof ReflectionUnionType + || $type instanceof ReflectionIntersectionType + || $type === null) { + throw new InvalidRepository( + "Type of $refMethod->class::$refMethod->name::$param->name has an invalid type. " + . 'Simple type expected. Unions, Intersections or no type are not simple.' + ); + } + + $params[] = new RepositoryMethodParameter( + name: $param->getName(), + type: $type->__toString(), + function: ParameterFunction::STATEMENT_PARAMETER + ); + } + + return $params; + } +} diff --git a/src/Analyze/RepositoryMethod.php b/src/Analyze/RepositoryMethod.php new file mode 100644 index 0000000..1ac2d83 --- /dev/null +++ b/src/Analyze/RepositoryMethod.php @@ -0,0 +1,28 @@ + $parameters + */ + public function __construct( + public ?string $query, + public string $returnType, + public ?string $returnListType, + public string $name, + public array $parameters, + public ResolutionStrategy $resolutionStrategy + ) {} + + public function returnsNullable(): bool + { + return str_starts_with($this->returnType, '?'); + } +} diff --git a/src/Analyze/RepositoryMethodParameter.php b/src/Analyze/RepositoryMethodParameter.php new file mode 100644 index 0000000..19e2f56 --- /dev/null +++ b/src/Analyze/RepositoryMethodParameter.php @@ -0,0 +1,14 @@ + $methods + */ + public function __construct( + public array $methods + ) {} +} diff --git a/src/Analyze/Validate/ResolutionStrategyFactory.php b/src/Analyze/Validate/ResolutionStrategyFactory.php new file mode 100644 index 0000000..8ba248c --- /dev/null +++ b/src/Analyze/Validate/ResolutionStrategyFactory.php @@ -0,0 +1,63 @@ + $params + * @param class-string|null $listType + */ + public function forRepositoryMethod( + string $repName, + string $methodName, + ?string $query, + string $returnType, + ?string $listType, + array $params + ): ResolutionStrategy + { + $oneParam = count($params) === 1; + $returnsNullable = str_starts_with($returnType, '?'); + + return match (true) { + $query !== null => new UserDefinedQuery(), + + $methodName === 'save' && $oneParam => new SaveEntity(), + $methodName === 'save' => throw new InvalidRepository( + "'save' functions expect exactly one parameter. Invalid function found in $repName" + ), + + $methodName === 'delete' && $oneParam && $returnType === 'int' => new DeleteEntity(), + $methodName === 'delete' => throw new InvalidRepository( + "'delete' functions expect exactly one parameter and integer return type. " . + "Invalid function found in $repName" + ), + + $methodName === 'findById' && $oneParam && $returnsNullable => new FindById(), + $methodName === 'findById' => throw new InvalidRepository( + "fnyById functions expect exactly one parameter and nullable return type." . + "Invalid function found in $repName" + ), + + $methodName === 'findAll' && $returnType === 'array' && $listType !== null => new FindAll(), + $methodName === 'findAll' => throw new InvalidRepository( + "FindAll" + ), + + default => throw new InvalidRepository( + "No suitable strategy for repository function $repName::$methodName can be found." + ) + }; + } +} diff --git a/src/Attribute/Column.php b/src/Attribute/Column.php new file mode 100644 index 0000000..f740686 --- /dev/null +++ b/src/Attribute/Column.php @@ -0,0 +1,15 @@ + $clazz + * @return R + * @noinspection PhpDocMissingThrowsInspection + */ + public function ofType(string $clazz): object + { + /** @noinspection PhpUnhandledExceptionInspection */ + $refClass = new ReflectionClass($clazz); + + /** + * @var R of object + * @phpstan-ignore varTag.nativeType + */ + return new class($refClass) { + /** + * @param ReflectionClass $refClass + */ + public function __construct( + private readonly ReflectionClass $refClass + ) {} + + /** + * @param string $name + * @param list $arguments + * @return mixed + */ + public function __call(string $name, array $arguments): mixed + { + $method = $this->refClass->getMethod($name); + $queryAttr = $method->getAttributes(Query::class); + + if (count($queryAttr) !== 1) { + throw new InvalidRepository( + "Called method $name has an invalid number of Query attributes. Expected: 1" + ); + } + + return ''; + } + }; + } +} diff --git a/src/Repository/ResolutionStrategy/DeleteEntity.php b/src/Repository/ResolutionStrategy/DeleteEntity.php new file mode 100644 index 0000000..6fa053b --- /dev/null +++ b/src/Repository/ResolutionStrategy/DeleteEntity.php @@ -0,0 +1,21 @@ +returnListType; + $entityResult = (new EntityAnalyzer($entityFqcn))->analyze(); + $mapper = new DataToEntityMapper($entityResult); + + $fields = implode( + ', ', + array_map( + static fn(EntityField $e) => $e->columnName, $entityResult->fields + ) + ); + $sql = " + SELECT $fields + FROM {$entityResult->tableName} + "; + + /** @var PDOStatement $stmt */ + $stmt = $pdo->prepare($sql); + $stmt->execute(); + + $statementResult = []; + + while ($row = $stmt->fetch( + mode: PDO::FETCH_ASSOC + )) { + /** @var array $row */ + $entity = $mapper->toEntity($row); + $statementResult[] = $entity; + } + + return $statementResult; + }); + } +} diff --git a/src/Repository/ResolutionStrategy/FindById.php b/src/Repository/ResolutionStrategy/FindById.php new file mode 100644 index 0000000..c8e4330 --- /dev/null +++ b/src/Repository/ResolutionStrategy/FindById.php @@ -0,0 +1,21 @@ + $parameters + * @return LazyQuery + */ + public function execute(RepositoryMethod $method, array $parameters): LazyQuery; +} diff --git a/src/Repository/ResolutionStrategy/SaveEntity.php b/src/Repository/ResolutionStrategy/SaveEntity.php new file mode 100644 index 0000000..f1234ec --- /dev/null +++ b/src/Repository/ResolutionStrategy/SaveEntity.php @@ -0,0 +1,21 @@ + $entityResult + */ + public function __construct( + private EntityResult $entityResult + ) {} + + /** + * @param array $row + * @return E + */ + public function toEntity(array $row): object + { + /** @var E $entity */ + $entity = new $this->entityResult->classFqcn(); + + foreach ($this->entityResult->fields as $field) { + /** @var object $dbValue */ + $dbValue = $row[$field->columnName]; + + match ($field->writeAccess) { + FieldAccess::GETSET => $this->getsetWrite($entity, $field, $dbValue), + FieldAccess::PUBLIC => $this->publicWrite($entity, $field, $dbValue), + FieldAccess::WITHER => $entity = $this->witherWrite($entity, $field, $dbValue), + }; + } + + return $entity; + } + + /** + * @param E $entity + * @param EntityField $field + * @param object $value + */ + private function getsetWrite(object $entity, EntityField $field, mixed $value): void + { + $setter = FieldAccess::setterName($field->name); + $entity->$setter($value); + } + + /** + * @param E $entity + * @param EntityField $field + * @param object $value + */ + private function publicWrite(object $entity, EntityField $field, mixed $value): void + { + $entity->{$field->name} = $value; + } + + /** + * @param E $entity + * @param EntityField $field + * @param object $value + * @return E + */ + private function witherWrite(object $entity, EntityField $field, mixed $value): object + { + $wither = FieldAccess::witherName($field->name); + /** @var E */ + return $entity->$wither($value); + } +} diff --git a/src/Sql/LazyQuery.php b/src/Sql/LazyQuery.php new file mode 100644 index 0000000..13281c3 --- /dev/null +++ b/src/Sql/LazyQuery.php @@ -0,0 +1,18 @@ +analyze(); + + // Then + $this->assertEquals('simple-table', $result->tableName); + $this->assertEquals(FullEntity::class, $result->classFqcn); + } + + public function testFieldsAreAnalyzed(): void + { + // Given + $entity = FullEntity::class; + + // When + $result = (new EntityAnalyzer($entity))->analyze(); + + // Then + $this->assertContainsEquals( + new EntityField( + name: 'stringField', + fqcn: $entity, + columnName: 'stringField', + isIdField: false, + writeAccess: FieldAccess::WITHER, + readAccess: FieldAccess::PUBLIC + ), + $result->fields + ); + $this->assertContainsEquals( + new EntityField( + name: 'intField', + fqcn: $entity, + columnName: 'int-field', + isIdField: false, + writeAccess: FieldAccess::PUBLIC, + readAccess: FieldAccess::GETSET + ), + $result->fields + ); + $this->assertContainsEquals( + new EntityField( + name: 'idField', + fqcn: $entity, + columnName: 'renamed-id-field', + isIdField: true, + writeAccess: FieldAccess::GETSET, + readAccess: FieldAccess::PUBLIC + ), + $result->fields + ); + } + + public function testIdFieldIsFound(): void + { + // Given + $entity = FullEntity::class; + + // When + $result = (new EntityAnalyzer($entity))->analyze(); + + // Then + $this->assertEquals( + new EntityField( + name: 'idField', + fqcn: $entity, + columnName: 'renamed-id-field', + isIdField: true, + writeAccess: FieldAccess::GETSET, + readAccess: FieldAccess::PUBLIC + ), + $result->idField + ); + } + + public function testNoIdIsInvalid(): void + { + // Given + $entity = EntityWithoutId::class; + + // Then + $this->expectException(InvalidEntity::class); + + // When + (new EntityAnalyzer($entity))->analyze(); + } + + public function testMultipleIdsIsInvalid(): void + { + // Given + $entity = EntityWithTwoIds::class; + + // Then + $this->expectException(InvalidEntity::class); + + // When + (new EntityAnalyzer($entity))->analyze(); + } +} diff --git a/tests/Analyze/RepositoryAnalyzerTest.php b/tests/Analyze/RepositoryAnalyzerTest.php new file mode 100644 index 0000000..2b33b03 --- /dev/null +++ b/tests/Analyze/RepositoryAnalyzerTest.php @@ -0,0 +1,136 @@ +analyze(Repository::class); + + // Then + $this->assertEquals('SELECT something', $result->methods['fetchWithQuery']->query); + } + + public function testEntityReturnTypeIsAnalyzed(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // When + $result = $analyzer->analyze(Repository::class); + + // Then + $this->assertEquals(FullEntity::class, $result->methods['fetchWithQuery']->returnType); + } + + public function testArrayReturnTypeIsRead(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // When + $result = $analyzer->analyze(Repository::class); + + // Then + $this->assertEquals('array', $result->methods['fetchArray']->returnType); + } + + public function testVoidReturnTypeIsRead(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // When + $result = $analyzer->analyze(Repository::class); + + // Then + $this->assertEquals('void', $result->methods['insert']->returnType); + } + + public function testListResultOfIsRead(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // When + $result = $analyzer->analyze(Repository::class); + + // Then + $this->assertEquals(FullEntity::class, $result->methods['fetchArray']->returnListType); + } + + public function testInvalidListResultOfIsReported(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // Then + $this->expectException(InvalidRepository::class); + + // When + $analyzer->analyze(InvalidListResultOfRepository::class); + } + + public function testUnionReturnTypeIsReported(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // Then + $this->expectException(InvalidRepository::class); + + // When + $analyzer->analyze(InvalidRepository::class); + } + + public function testParametersAreAnalyzed(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // When + $result = $analyzer->analyze(Repository::class); + + // Then + $method = $result->methods['doSomethingWithParameters']; + /** @var array $parameters */ + $parameters = $method->parameters; + + $this->assertCount(2, $parameters); + $this->assertEquals('stringParam', $parameters[0]->name); + $this->assertEquals('string', $parameters[0]->type); + + $this->assertEquals('intParam', $parameters[1]->name); + $this->assertEquals('int', $parameters[1]->type); + } + + public function testNullableReturnType(): void + { + // Given + $analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory()); + + // When + $result = $analyzer->analyze(Repository::class); + + // Then + $method = $result->methods['findById']; + $this->assertEquals('?Kartierung\FullEntity', $method->returnType); + $this->assertTrue($method->returnsNullable()); + } +} diff --git a/tests/Analyze/Validate/RepositoryValidatorTest.php b/tests/Analyze/Validate/RepositoryValidatorTest.php new file mode 100644 index 0000000..f361df7 --- /dev/null +++ b/tests/Analyze/Validate/RepositoryValidatorTest.php @@ -0,0 +1,54 @@ +forRepositoryMethod( + $repName, + $name, + $query, + $returnType, + $listType, + $params + ); + + // Then + $this->assertInstanceOf($strategyClass, $function); + } +} diff --git a/tests/EntityWithTwoIds.php b/tests/EntityWithTwoIds.php new file mode 100644 index 0000000..8b8e183 --- /dev/null +++ b/tests/EntityWithTwoIds.php @@ -0,0 +1,18 @@ +idField = $idField; + } + + public function getIntField(): int + { + return $this->intField; + } + + public function withStringField(string $stringField): self + { + $this->stringField = $stringField; + return $this; + } +} diff --git a/tests/InvalidListResultOfRepository.php b/tests/InvalidListResultOfRepository.php new file mode 100644 index 0000000..456157b --- /dev/null +++ b/tests/InvalidListResultOfRepository.php @@ -0,0 +1,13 @@ +pdo = new \PDO("sqlite:/$tmpfile"); + + $this->pdo->exec( + " + CREATE TABLE SimpleEntity ( + stringcol VARCHAR(250), + intcol INT + ); + INSERT INTO SimpleEntity(stringcol, intcol) + VALUES ('dings', 1), + ('bumms', 2); + " + ); + } + + #[Group('integration')] + public function testSelectsData(): void + { + // Given + $query = (new FindAll())->execute( + method: new RepositoryMethod( + query: '', + returnType: '', + returnListType: SimpleEntity::class, + name: '', + parameters: [], + resolutionStrategy: new FindAll() + ), + parameters: [] + ); + + // When + $result = $query->execute->__invoke($this->pdo); + + // Then + $this->assertNotEmpty($result); + } + +} diff --git a/tests/Repository/ResolutionStrategy/SimpleEntity.php b/tests/Repository/ResolutionStrategy/SimpleEntity.php new file mode 100644 index 0000000..7a3a015 --- /dev/null +++ b/tests/Repository/ResolutionStrategy/SimpleEntity.php @@ -0,0 +1,23 @@ +stringcol = $value; + return $new; + } + +} diff --git a/tests/Sql/DateToEntityMapperTest.php b/tests/Sql/DateToEntityMapperTest.php new file mode 100644 index 0000000..05a0d9a --- /dev/null +++ b/tests/Sql/DateToEntityMapperTest.php @@ -0,0 +1,73 @@ + 1, + 'publicVal' => 2, + 'setterVal' => 3 + ]; + + // When + $entity = $mapper->toEntity($row); + + // Then + $this->assertInstanceOf(EntityWithThreeAccessMethods::class, $entity); + $this->assertEquals(1, $entity->getWitherVal()); + $this->assertEquals(2, $entity->publicVal); + $this->assertEquals(3, $entity->getSetterVal()); + } +} diff --git a/tests/Sql/EntityWithThreeAccessMethods.php b/tests/Sql/EntityWithThreeAccessMethods.php new file mode 100644 index 0000000..0201442 --- /dev/null +++ b/tests/Sql/EntityWithThreeAccessMethods.php @@ -0,0 +1,35 @@ +setterVal; + } + + public function setSetterVal(int $value): void + { + $this->setterVal = $value; + } + + public function withWitherVal(int $value): self + { + $this->witherVal = $value; + return $this; + } + + public function getWitherVal(): int + { + return $this->witherVal; + } +} diff --git a/tests/UnionReturnTypeRepository.php b/tests/UnionReturnTypeRepository.php new file mode 100644 index 0000000..ec5aa56 --- /dev/null +++ b/tests/UnionReturnTypeRepository.php @@ -0,0 +1,10 @@ +