From 5f428a2d2783ed1272dd224c6c763de99af9a7e3 Mon Sep 17 00:00:00 2001 From: ebln <34722048+ebln@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:46:10 +0200 Subject: [PATCH 01/13] Bump Psalm --- composer.json | 2 +- composer.lock | 378 ++++++++++++++++++++++---------------------------- 2 files changed, 165 insertions(+), 215 deletions(-) diff --git a/composer.json b/composer.json index 575d7aa..2717231 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "phpmd/phpmd": "^2.10", "phpunit/phpunit": "^9.5", "roave/security-advisories": "dev-latest", - "vimeo/psalm": "^4.12" + "vimeo/psalm": "^5.24" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 012d1b9..46fff23 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d34c0cd3e1aa9b32f4c362580e4f4dd4", + "content-hash": "cb320edf79fd64c86777c993b2440def", "packages": [ { "name": "ebln/phpstan-factory-mark", @@ -270,79 +270,6 @@ ], "time": "2024-04-13T18:00:56+00:00" }, - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.5", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", - "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2022-01-17T14:14:24+00:00" - }, { "name": "composer/pcre", "version": "3.1.4", @@ -1225,6 +1152,67 @@ }, "time": "2022-03-02T22:36:06+00:00" }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, { "name": "justinrainbow/json-schema", "version": "v5.2.13", @@ -1521,59 +1509,6 @@ }, "time": "2024-03-17T08:10:35+00:00" }, - { - "name": "openlss/lib-array2xml", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/nullivex/lib-array2xml.git", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "LSS": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Bryan Tong", - "email": "bryan@nullivex.com", - "homepage": "https://www.nullivex.com" - }, - { - "name": "Tony Butler", - "email": "spudz76@gmail.com", - "homepage": "https://www.nullivex.com" - } - ], - "description": "Array2XML conversion library credit to lalit.org", - "homepage": "https://www.nullivex.com", - "keywords": [ - "array", - "array conversion", - "xml", - "xml conversion" - ], - "support": { - "issues": "https://github.com/nullivex/lib-array2xml/issues", - "source": "https://github.com/nullivex/lib-array2xml/tree/master" - }, - "time": "2019-03-29T20:06:56+00:00" - }, { "name": "pdepend/pdepend", "version": "2.16.2", @@ -4343,6 +4278,70 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "spatie/array-to-xml", + "version": "2.17.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/5cbec9c6ab17e320c58a259f0cebe88bde4a7c46", + "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.4|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "phpunit/phpunit": "^9.0", + "spatie/pest-plugin-snapshots": "^1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" + } + ], + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", + "keywords": [ + "array", + "convert", + "xml" + ], + "support": { + "source": "https://github.com/spatie/array-to-xml/tree/2.17.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-12-26T08:22:07+00:00" + }, { "name": "symfony/config", "version": "v5.4.40", @@ -5515,24 +5514,24 @@ }, { "name": "vimeo/psalm", - "version": "4.30.0", + "version": "5.24.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69" + "reference": "462c80e31c34e58cc4f750c656be3927e80e550e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/d0bc6e25d89f649e4f36a534f330f8bb4643dd69", - "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/462c80e31c34e58cc4f750c656be3927e80e550e", + "reference": "462c80e31c34e58cc4f750c656be3927e80e550e", "shasum": "" }, "require": { "amphp/amp": "^2.4.2", "amphp/byte-stream": "^1.5", - "composer/package-versions-deprecated": "^1.8.0", + "composer-runtime-api": "^2", "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", "dnoegel/php-xdg-base-dir": "^0.1.1", "ext-ctype": "*", "ext-dom": "*", @@ -5541,35 +5540,38 @@ "ext-mbstring": "*", "ext-simplexml": "*", "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.0.3", - "felixfbecker/language-server-protocol": "^1.5", + "felixfbecker/advanced-json-rpc": "^3.1", + "felixfbecker/language-server-protocol": "^1.5.2", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.13", - "openlss/lib-array2xml": "^1.0", - "php": "^7.1|^8", - "sebastian/diff": "^3.0 || ^4.0", - "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", - "symfony/polyfill-php80": "^1.25", - "webmozart/path-util": "^2.3" + "nikic/php-parser": "^4.16", + "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "nikic/php-parser": "4.17.0" }, "provide": { "psalm/psalm": "self.version" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "brianium/paratest": "^4.0||^6.0", + "amphp/phpunit-util": "^2.0", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpdocumentor/reflection-docblock": "^5", - "phpmyadmin/sql-parser": "5.1.0||dev-master", - "phpspec/prophecy": ">=1.9.0", - "phpstan/phpdoc-parser": "1.2.* || 1.6.4", - "phpunit/phpunit": "^9.0", - "psalm/plugin-phpunit": "^0.16", - "slevomat/coding-standard": "^7.0", - "squizlabs/php_codesniffer": "^3.5", - "symfony/process": "^4.3 || ^5.0 || ^6.0", - "weirdan/prophecy-shim": "^1.0 || ^2.0" + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "suggest": { "ext-curl": "In order to send data to shepherd", @@ -5582,20 +5584,17 @@ "psalm-refactor", "psalter" ], - "type": "library", + "type": "project", "extra": { "branch-alias": { - "dev-master": "4.x-dev", + "dev-master": "5.x-dev", + "dev-4.x": "4.x-dev", "dev-3.x": "3.x-dev", "dev-2.x": "2.x-dev", "dev-1.x": "1.x-dev" } }, "autoload": { - "files": [ - "src/functions.php", - "src/spl_object_id.php" - ], "psr-4": { "Psalm\\": "src/Psalm/" } @@ -5613,13 +5612,15 @@ "keywords": [ "code", "inspection", - "php" + "php", + "static analysis" ], "support": { + "docs": "https://psalm.dev/docs", "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.30.0" + "source": "https://github.com/vimeo/psalm" }, - "time": "2022-11-06T20:37:08+00:00" + "time": "2024-05-01T19:32:08+00:00" }, { "name": "webmozart/assert", @@ -5678,57 +5679,6 @@ "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, "time": "2022-06-03T18:03:27+00:00" - }, - { - "name": "webmozart/path-util", - "version": "2.3.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/path-util.git", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", - "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "webmozart/assert": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\PathUtil\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", - "support": { - "issues": "https://github.com/webmozart/path-util/issues", - "source": "https://github.com/webmozart/path-util/tree/2.3.0" - }, - "abandoned": "symfony/filesystem", - "time": "2015-12-17T08:42:14+00:00" } ], "aliases": [], From a36c7d0fc7d7cbf2de21f9b1b48f13f967476db9 Mon Sep 17 00:00:00 2001 From: ebln <34722048+ebln@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:36:26 +0200 Subject: [PATCH 02/13] Add pure tests & subsequent fix - ExtendedProduct wasn't resolved and needed extra TLC TODO: Tests for mixed occurrences of Interface and Attributes Add attributes from spike - yet without tests --- .github/workflows/buildTest.yml | 9 +- composer.json | 2 +- phpstan.neon | 8 ++ src/ForceFactory.php | 36 ++++++ src/ForceFactoryRule.php | 82 ++++++++++++- tests/AttribForceFactoryRuleTest.php | 73 ++++++++++++ tests/ForceFactoryRuleTest.php | 4 +- tests/dataAttrib/EmptyFactory.php | 15 +++ tests/dataAttrib/FactoryTrait.php | 29 +++++ tests/dataAttrib/ForcedFactory.php | 109 ++++++++++++++++++ tests/dataAttrib/LoopholeFactory.php | 19 +++ tests/dataAttrib/RogueFactory.php | 97 ++++++++++++++++ tests/dataAttrib/RogueTraitFactory.php | 10 ++ tests/dataAttrib/TraitFactory.php | 10 ++ tests/dataAttrib/code/EmptyProduct.php | 12 ++ tests/dataAttrib/code/ExtendedProduct.php | 10 ++ .../dataAttrib/code/ForcedFactoryProduct.php | 14 +++ tests/dataAttrib/code/FreeProduct.php | 10 ++ 18 files changed, 535 insertions(+), 14 deletions(-) create mode 100644 src/ForceFactory.php create mode 100644 tests/AttribForceFactoryRuleTest.php create mode 100644 tests/dataAttrib/EmptyFactory.php create mode 100644 tests/dataAttrib/FactoryTrait.php create mode 100644 tests/dataAttrib/ForcedFactory.php create mode 100644 tests/dataAttrib/LoopholeFactory.php create mode 100644 tests/dataAttrib/RogueFactory.php create mode 100644 tests/dataAttrib/RogueTraitFactory.php create mode 100644 tests/dataAttrib/TraitFactory.php create mode 100644 tests/dataAttrib/code/EmptyProduct.php create mode 100644 tests/dataAttrib/code/ExtendedProduct.php create mode 100644 tests/dataAttrib/code/ForcedFactoryProduct.php create mode 100644 tests/dataAttrib/code/FreeProduct.php diff --git a/.github/workflows/buildTest.yml b/.github/workflows/buildTest.yml index 59b6416..b19ee40 100644 --- a/.github/workflows/buildTest.yml +++ b/.github/workflows/buildTest.yml @@ -37,11 +37,10 @@ jobs: ./vendor/bin/phpstan --no-interaction --no-ansi analyse - name: Mess Detector Sources run: | - ./vendor/bin/phpmd src text codesize,controversial,design,naming,unusedcode,design - - name: Mess Detector Tests - run: | - ./vendor/bin/phpmd tests text codesize,controversial,design - ./vendor/bin/phpmd src text codesize,controversial,naming,unusedcode + ./vendor/bin/phpmd src text codesize,controversial,naming,unusedcode + - name: Mess Detector Tests + run: | + ./vendor/bin/phpmd tests text codesize,controversial,design - name: php-cs-fixer run: | ./tools/php-cs-fixer/vendor/bin/php-cs-fixer fix -v --config=.php-cs-fixer.dist.php --using-cache=no --dry-run diff --git a/composer.json b/composer.json index 2717231..710dd71 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "psalm --find-unused-psalm-suppress", "phpstan analyse", "@style-check", - "phpmd src ansi codesize,controversial,design,naming,unusedcode,design", + "phpmd src ansi codesize,controversial,naming,unusedcode", "phpmd tests ansi codesize,controversial,design" ], "style-check": "php-cs-fixer fix -v --config=.php-cs-fixer.dist.php --using-cache=no --dry-run", diff --git a/phpstan.neon b/phpstan.neon index 1494f51..706d54b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,3 +3,11 @@ parameters: paths: - src - tests + + excludePaths: + analyseAndScan: + - tests/data/* + - tests/dataAttrib/* + +rules: + - \Ebln\PHPStan\EnforceFactory\ForceFactoryRule diff --git a/src/ForceFactory.php b/src/ForceFactory.php new file mode 100644 index 0000000..324fda4 --- /dev/null +++ b/src/ForceFactory.php @@ -0,0 +1,36 @@ + */ + private array $allowedFactories; + + /** @param class-string ...$factories */ + public function __construct(string ...$factories) + { + $allowedFactories = []; + foreach ($factories as $factory) { + $allowedFactories[$factory] = $factory; + } + + $this->allowedFactories = array_values($allowedFactories); + } + + /** @return array */ + public function getAllowedFactories(): array + { + return $this->allowedFactories; + } +} diff --git a/src/ForceFactoryRule.php b/src/ForceFactoryRule.php index 7afe65f..620c8cf 100644 --- a/src/ForceFactoryRule.php +++ b/src/ForceFactoryRule.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -14,6 +15,13 @@ */ class ForceFactoryRule implements Rule { + private ReflectionProvider $reflectionProvider; + + public function __construct(ReflectionProvider $reflectionProvider) + { + $this->reflectionProvider = $reflectionProvider; + } + public function getNodeType(): string { return \PhpParser\Node\Expr\New_::class; @@ -26,17 +34,18 @@ public function processNode(Node $node, Scope $scope): array if (!$isName) { continue; // newly instantiated class name couldn't be infered } + /** @var class-string $class → sadly Psalm cannot be convinced to return class-string for getClassNames in reasonable amount of time */ $allowedFactories = $this->getAllowedFactories($class); if (null === $allowedFactories) { - continue; // newly instantiated class dowsn't implement ForceFactory interface + continue; // newly instantiated class doesn't implement ForceFactory interface } - if (empty($allowedFactories)) { + if ([] === $allowedFactories) { $errors[] = RuleErrorBuilder::message( - ltrim($class, '\\') . ' cannot be instantiated by other classes; see ' . ForceFactoryInterface::class + ltrim($class, '\\') . ' has either no factories defined or a conflict between interface and attribute!' )->build(); - continue; + continue; // bogus configuration } /** @psalm-suppress PossiblyNullReference | sad that even phpstan cannot infer that from isInClass */ @@ -57,17 +66,78 @@ public function processNode(Node $node, Scope $scope): array } /** + * @phpstan-param class-string $className + * * @return null|string[] List of FQCNs * * @phpstan-return null|class-string[] */ private function getAllowedFactories(string $className): ?array { - if (!is_a($className, ForceFactoryInterface::class, true)) { + $allowedFactories = $this->getFactoriesFromAttribute($className); + if (is_a($className, ForceFactoryInterface::class, true)) { + /* phpstan-var class-string $className */ + $interfaceFactories = $className::getFactories(); + sort($interfaceFactories); + if (null === $allowedFactories) { + $allowedFactories = $interfaceFactories; + } elseif ($allowedFactories !== $interfaceFactories) { + $allowedFactories = []; // Will result in a bogus definition error + } + } + + return $allowedFactories; + } + + /** + * @phpstan-param class-string $className + * + * @return array + */ + private function getFactoriesFromAttribute(string $className): ?array + { + if (\PHP_VERSION_ID < 80000 && $this->reflectionProvider->hasClass($className)) { return null; } - return $className::getFactories(); + $reflection = $this->reflectionProvider->getClass($className); + /* psalm-suppress UndefinedClass */ + $allowedFactories = []; + do { + /** @psalm-suppress UndefinedClass */ + $allowedFactories = [...$allowedFactories, ...$this->getFactoriesFromAttributeByClass($reflection->getNativeReflection())]; + } while ($reflection = $reflection->getParentClass()); + + if (empty($allowedFactories)) { + return null; + } + $allowedFactories = array_filter($allowedFactories); + sort($allowedFactories); + + return $allowedFactories; + } + + /** + * @psalm-suppress UndefinedDocblockClass,MismatchingDocblockParamType + * + * @psalm-param \PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass|\PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum $reflection + * + * @return array + */ + private function getFactoriesFromAttributeByClass(\ReflectionClass $reflection): array + { + /** @psalm-suppress UndefinedClass */ + foreach ($reflection->getAttributes() as $attribute) { + if (ForceFactory::class === $attribute->getName()) { + /** @var ForceFactory $forceFactory */ + $forceFactory = $attribute->newInstance(); + $allowedFactories = $forceFactory->getAllowedFactories(); + + return empty($allowedFactories) ? [null] : $allowedFactories; + } + } + + return []; } /** diff --git a/tests/AttribForceFactoryRuleTest.php b/tests/AttribForceFactoryRuleTest.php new file mode 100644 index 0000000..018a56e --- /dev/null +++ b/tests/AttribForceFactoryRuleTest.php @@ -0,0 +1,73 @@ += 8.0 + * @extends RuleTestCase + */ +class AttribForceFactoryRuleTest extends RuleTestCase +{ + private const ERROR_MESSAGE = 'Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\ForcedFactoryProduct must be instantiated by Test\Ebln\PHPStan\EnforceFactory\dataAttrib\ForcedFactory or Test\Ebln\PHPStan\EnforceFactory\dataAttrib\TraitFactory!'; + + // Sadly this remains a vector, as phpstan fails to infer the created class name + public function testLoopholeFactory(): void + { + $this->analyse([__DIR__ . '/dataAttrib/LoopholeFactory.php'], []); + } + + public function testEmptyAllowedClasses(): void + { + $this->analyse([__DIR__ . '/dataAttrib/EmptyFactory.php'], [ + [ + 'Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\EmptyProduct has either no factories defined or a conflict between interface and attribute!', + 13, + ], + ]); + } + + public function testRogueFactory(): void + { + $this->analyse([__DIR__ . '/dataAttrib/RogueFactory.php'], [ + [self::ERROR_MESSAGE, 15], + [self::ERROR_MESSAGE, 22], + [self::ERROR_MESSAGE, 29], + [self::ERROR_MESSAGE, 40], + [self::ERROR_MESSAGE, 40], + [self::ERROR_MESSAGE, 51], + [self::ERROR_MESSAGE, 56], + ['Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\ExtendedProduct must be instantiated by Test\Ebln\PHPStan\EnforceFactory\dataAttrib\ForcedFactory or Test\Ebln\PHPStan\EnforceFactory\dataAttrib\TraitFactory!', 69], + [self::ERROR_MESSAGE, 95], + ]); + } + + public function testRogueFactoryAndTrait(): void + { + $this->analyse([__DIR__ . '/dataAttrib/RogueTraitFactory.php', __DIR__ . '/dataAttrib/FactoryTrait.php'], [ + [self::ERROR_MESSAGE, 13], + [self::ERROR_MESSAGE, 20], + [self::ERROR_MESSAGE, 27], + ]); + } + + public function testTraitedFactory(): void + { + $this->analyse([__DIR__ . '/dataAttrib/TraitFactory.php', __DIR__ . '/dataAttrib/FactoryTrait.php'], []); + } + + public function testAllowedFactory(): void + { + $this->analyse([__DIR__ . '/dataAttrib/ForcedFactory.php'], []); + } + + protected function getRule(): Rule + { + return new ForceFactoryRule($this->createReflectionProvider()); + } +} diff --git a/tests/ForceFactoryRuleTest.php b/tests/ForceFactoryRuleTest.php index 5520bd8..3bfdb1f 100644 --- a/tests/ForceFactoryRuleTest.php +++ b/tests/ForceFactoryRuleTest.php @@ -25,7 +25,7 @@ public function testEmptyAllowedClasses(): void { $this->analyse([__DIR__ . '/data/EmptyFactory.php'], [ [ - 'Test\Ebln\PHPStan\EnforceFactory\data\code\EmptyProduct cannot be instantiated by other classes; see Ebln\PHPStan\EnforceFactory\ForceFactoryInterface', + 'Test\Ebln\PHPStan\EnforceFactory\data\code\EmptyProduct has either no factories defined or a conflict between interface and attribute!', 13, ], ]); @@ -67,6 +67,6 @@ public function testAllowedFactory(): void protected function getRule(): Rule { - return new ForceFactoryRule(); + return new ForceFactoryRule($this->createReflectionProvider()); } } diff --git a/tests/dataAttrib/EmptyFactory.php b/tests/dataAttrib/EmptyFactory.php new file mode 100644 index 0000000..1ccd65b --- /dev/null +++ b/tests/dataAttrib/EmptyFactory.php @@ -0,0 +1,15 @@ +foo(); + } + + public function anonymousExtendingSquare(): void + { + $x = new class() extends ExtendedProduct + { + public function foo(): string + { + return 'bar'; + } + }; + + $bar = $x->foo(); + } + + public function anonymousPassing(): void + { + $x = new class() + { + public function foo(): string + { + return 'bar'; + } + }; + + $bar = $x->foo(); + } + + public static function staticClass(): ForcedFactoryProduct + { + return new ForcedFactoryProduct(); + } +} diff --git a/tests/dataAttrib/LoopholeFactory.php b/tests/dataAttrib/LoopholeFactory.php new file mode 100644 index 0000000..13b707f --- /dev/null +++ b/tests/dataAttrib/LoopholeFactory.php @@ -0,0 +1,19 @@ +foo(); + } + + public function anonymousExtendingSquare(): void + { + $x = new class() extends ExtendedProduct + { + public function foo(): string + { + return 'bar'; + } + }; + + $bar = $x->foo(); + } + + public function anonymousPassing(): void + { + $x = new class() + { + public function foo(): string + { + return 'bar'; + } + }; + + $bar = $x->foo(); + } + + public static function staticClass(): ForcedFactoryProduct + { + return new ForcedFactoryProduct(); + } +} diff --git a/tests/dataAttrib/RogueTraitFactory.php b/tests/dataAttrib/RogueTraitFactory.php new file mode 100644 index 0000000..083ea54 --- /dev/null +++ b/tests/dataAttrib/RogueTraitFactory.php @@ -0,0 +1,10 @@ + Date: Mon, 3 Jun 2024 00:28:36 +0200 Subject: [PATCH 03/13] Bump GH actions --- .github/workflows/buildTest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/buildTest.yml b/.github/workflows/buildTest.yml index b19ee40..15081a1 100644 --- a/.github/workflows/buildTest.yml +++ b/.github/workflows/buildTest.yml @@ -12,9 +12,9 @@ jobs: name: Build and test on ${{ matrix.php }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Force PHP ${{ matrix.php }} - uses: nanasess/setup-php@master + uses: nanasess/setup-php@v4 with: php-version: ${{ matrix.php }} - name: Validate composer.json and composer.lock From 87664e2972bae2ff1f2c786fc5946a7f3277f9da Mon Sep 17 00:00:00 2001 From: ebln <34722048+ebln@users.noreply.github.com> Date: Mon, 3 Jun 2024 01:16:03 +0200 Subject: [PATCH 04/13] Trick psalm & phpstan --- phpstan-baseline-7.4.neon | 13 +++++++++++++ phpstan-ignore-by-version.neon.php | 14 ++++++++++++++ phpstan.neon | 3 +++ 3 files changed, 30 insertions(+) create mode 100644 phpstan-baseline-7.4.neon create mode 100644 phpstan-ignore-by-version.neon.php diff --git a/phpstan-baseline-7.4.neon b/phpstan-baseline-7.4.neon new file mode 100644 index 0000000..e2a3c34 --- /dev/null +++ b/phpstan-baseline-7.4.neon @@ -0,0 +1,13 @@ +parameters: + ignoreErrors: + - + message: "#^Call to an undefined method ReflectionClass\\:\\:getAttributes\\(\\)\\.$#" + path: src/ForceFactoryRule.php + + - + message: "#^Method Ebln\\\\PHPStan\\\\EnforceFactory\\\\ForceFactoryRule\\:\\:getFactoriesFromAttributeByClass\\(\\) has parameter \\$reflection with generic class ReflectionClass but does not specify its types\\: T$#" + path: src/ForceFactoryRule.php + + - + message: "#^PHPDoc tag @param for parameter \\$reflection with type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum is not subtype of native type ReflectionClass\\.$#" + path: src/ForceFactoryRule.php diff --git a/phpstan-ignore-by-version.neon.php b/phpstan-ignore-by-version.neon.php new file mode 100644 index 0000000..49d2af2 --- /dev/null +++ b/phpstan-ignore-by-version.neon.php @@ -0,0 +1,14 @@ + Date: Wed, 5 Jun 2024 00:25:50 +0200 Subject: [PATCH 05/13] Add tests for mixed regime --- CHANGELOG.md | 6 ++++ phpstan.neon | 1 + tests/MixedForceFactoryRuleTest.php | 42 ++++++++++++++++++++++ tests/dataMixed/AttributeFactory.php | 27 ++++++++++++++ tests/dataMixed/ForcedFactory.php | 10 ++++++ tests/dataMixed/RogueAttributeFactory.php | 21 +++++++++++ tests/dataMixed/code/AttributeProduct.php | 13 +++++++ tests/dataMixed/code/MismatchedProduct.php | 19 ++++++++++ tests/dataMixed/code/MixedProduct.php | 20 +++++++++++ 9 files changed, 159 insertions(+) create mode 100644 tests/MixedForceFactoryRuleTest.php create mode 100644 tests/dataMixed/AttributeFactory.php create mode 100644 tests/dataMixed/ForcedFactory.php create mode 100644 tests/dataMixed/RogueAttributeFactory.php create mode 100644 tests/dataMixed/code/AttributeProduct.php create mode 100644 tests/dataMixed/code/MismatchedProduct.php create mode 100644 tests/dataMixed/code/MixedProduct.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a34c8d0..ca17bb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,13 @@ Intended to follow [«Keep a Changelog»](https://keepachangelog.com/en/) ## Upcoming +### Added - Support for attributes + * missing stand-alone attribute ??? + +### Removed +* Support for PHP < 7.4 +* Support for PHPStan < 1.11 ---- diff --git a/phpstan.neon b/phpstan.neon index 3676646..2e7645a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,6 +11,7 @@ parameters: analyseAndScan: - tests/data/* - tests/dataAttrib/* + - tests/dataMixed/* rules: - \Ebln\PHPStan\EnforceFactory\ForceFactoryRule diff --git a/tests/MixedForceFactoryRuleTest.php b/tests/MixedForceFactoryRuleTest.php new file mode 100644 index 0000000..a7709f2 --- /dev/null +++ b/tests/MixedForceFactoryRuleTest.php @@ -0,0 +1,42 @@ += 8.0 + * @extends RuleTestCase + */ +class MixedForceFactoryRuleTest extends RuleTestCase +{ + public function testAttributeFactory(): void + { + $this->analyse( + [__DIR__ . '/dataMixed/AttributeFactory.php'], + [ + ['Test\Ebln\PHPStan\EnforceFactory\dataMixed\code\MismatchedProduct has either no factories defined or a conflict between interface and attribute!', 25], + ] + ); + } + + public function testRogueAttributeFactory(): void + { + $this->analyse([__DIR__ . '/dataMixed/RogueAttributeFactory.php'], [ + ['Test\Ebln\PHPStan\EnforceFactory\dataMixed\code\AttributeProduct must be instantiated by Test\Ebln\PHPStan\EnforceFactory\dataMixed\AttributeFactory!', 14], + ['Test\Ebln\PHPStan\EnforceFactory\dataMixed\code\MixedProduct must be instantiated by Test\Ebln\PHPStan\EnforceFactory\dataMixed\AttributeFactory or Test\Ebln\PHPStan\EnforceFactory\dataMixed\ForcedFactory!', 19], + + ]); + } + + protected function getRule(): Rule + { + return new ForceFactoryRule(self::getContainer()->getByType(ReflectionProvider::class)); + } +} + diff --git a/tests/dataMixed/AttributeFactory.php b/tests/dataMixed/AttributeFactory.php new file mode 100644 index 0000000..200a6cc --- /dev/null +++ b/tests/dataMixed/AttributeFactory.php @@ -0,0 +1,27 @@ + Date: Wed, 5 Jun 2024 00:36:25 +0200 Subject: [PATCH 06/13] Add extended test for loophole --- CHANGELOG.md | 4 ++++ tests/AttribForceFactoryRuleTest.php | 6 ++++++ tests/dataAttrib/LoopholeInvoker.php | 27 +++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 tests/dataAttrib/LoopholeInvoker.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ca17bb9..791899b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,15 @@ Intended to follow [«Keep a Changelog»](https://keepachangelog.com/en/) ---- +## TODO +- TODO: remove variableUnpredictable` in favour of `variableUninferable` + ## Upcoming ### Added - Support for attributes * missing stand-alone attribute ??? +- Extended test case for Loophole (still failing) ### Removed * Support for PHP < 7.4 diff --git a/tests/AttribForceFactoryRuleTest.php b/tests/AttribForceFactoryRuleTest.php index 018a56e..379c5c2 100644 --- a/tests/AttribForceFactoryRuleTest.php +++ b/tests/AttribForceFactoryRuleTest.php @@ -22,6 +22,12 @@ public function testLoopholeFactory(): void $this->analyse([__DIR__ . '/dataAttrib/LoopholeFactory.php'], []); } + // Sadly this remains a vector, as phpstan fails to infer the created class name + public function testLoopholeInvoker(): void + { + $this->analyse([__DIR__ . '/dataAttrib/LoopholeFactory.php', __DIR__ . '/dataAttrib/LoopholeInvoker.php'], []); + } + public function testEmptyAllowedClasses(): void { $this->analyse([__DIR__ . '/dataAttrib/EmptyFactory.php'], [ diff --git a/tests/dataAttrib/LoopholeInvoker.php b/tests/dataAttrib/LoopholeInvoker.php new file mode 100644 index 0000000..f4d708c --- /dev/null +++ b/tests/dataAttrib/LoopholeInvoker.php @@ -0,0 +1,27 @@ +loopholeFactory = new LoopholeFactory(); + } + + public function expectedFailingLoophole(): object + { + $loophole = $this->loopholeFactory->variableUninferable(true); + + return $loophole; + } + + public function expectedMissingClass() + { + return $this->loopholeFactory->variableUninferable(false); + } +} From dc71bd4835fcc4703289f20809c757deead5686d Mon Sep 17 00:00:00 2001 From: ebln <34722048+ebln@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:03:37 +0200 Subject: [PATCH 07/13] Add designated LoopholeProduct --- tests/dataAttrib/LoopholeFactory.php | 2 +- tests/dataAttrib/code/LoopholeProduct.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/dataAttrib/code/LoopholeProduct.php diff --git a/tests/dataAttrib/LoopholeFactory.php b/tests/dataAttrib/LoopholeFactory.php index 13b707f..912c0cf 100644 --- a/tests/dataAttrib/LoopholeFactory.php +++ b/tests/dataAttrib/LoopholeFactory.php @@ -9,7 +9,7 @@ class LoopholeFactory public function variableUninferable(bool $toggle): void { if ($toggle) { - $class = '\Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\ForcedFactoryProduct'; + $class = '\Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\LoopholeProduct'; } else { $class = 'Hello world-' . random_int(10, 99); } diff --git a/tests/dataAttrib/code/LoopholeProduct.php b/tests/dataAttrib/code/LoopholeProduct.php new file mode 100644 index 0000000..c6825e2 --- /dev/null +++ b/tests/dataAttrib/code/LoopholeProduct.php @@ -0,0 +1,14 @@ + Date: Wed, 5 Jun 2024 23:05:07 +0200 Subject: [PATCH 08/13] Update Changelog --- CHANGELOG.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 791899b..27f7217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,22 +5,17 @@ Intended to follow [«Keep a Changelog»](https://keepachangelog.com/en/) ---- -## TODO -- TODO: remove variableUnpredictable` in favour of `variableUninferable` +---- -## Upcoming +## [1.0.0] ### Added - Support for attributes - * missing stand-alone attribute ??? -- Extended test case for Loophole (still failing) ### Removed * Support for PHP < 7.4 * Support for PHPStan < 1.11 ----- - ## [0.0.2] ### Added From 3fd76c6d943ffcb76368da3b064f6aa5a1773bcd Mon Sep 17 00:00:00 2001 From: ebln <34722048+ebln@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:20:43 +0200 Subject: [PATCH 09/13] Follow the 1.11 version of InstantiationRule --- src/ForceFactoryRule.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ForceFactoryRule.php b/src/ForceFactoryRule.php index 620c8cf..46a2980 100644 --- a/src/ForceFactoryRule.php +++ b/src/ForceFactoryRule.php @@ -151,8 +151,8 @@ private function getFactoriesFromAttributeByClass(\ReflectionClass $reflection): * * @psalm-return array * - * @license https://github.com/phpstan/phpstan/blob/1.1.2/LICENSE - * @author Ondřej Mirtes et al. https://github.com/phpstan/phpstan-src/blob/0.12.x/src/Rules/Classes/InstantiationRule.php#blob_contributors_box + * @license https://github.com/phpstan/phpstan-src/blob/1.11.x/LICENSE + * @author Ondřej Mirtes et al. https://github.com/phpstan/phpstan-src/blame/1.11.x/src/Rules/Classes/InstantiationRule.php * @author ebln * * @see \PHPStan\Rules\Classes\InstantiationRule::getClassNames @@ -163,8 +163,8 @@ private function getClassNames(\PhpParser\Node $node, \PHPStan\Analyser\Scope $s return [[(string)$node->class, \true]]; } if ($node->class instanceof \PhpParser\Node\Stmt\Class_) { - $anonymousClassType = $scope->getType($node); - if (!$anonymousClassType instanceof \PHPStan\Type\TypeWithClassName) { + $classNames = $scope->getType($node)->getObjectClassNames(); + if ([] === $classNames) { throw new \PHPStan\ShouldNotHappenException(); } // Report back extended class! @@ -172,8 +172,10 @@ private function getClassNames(\PhpParser\Node $node, \PHPStan\Analyser\Scope $s return [[$node->class->extends->toString(), \true]]; } - // we don't care about the anonymous class' name and abort processing early - return [[$anonymousClassType->getClassName(), \false]]; + return array_map( + static fn (string $className) => [$className, \false], + $classNames, + ); } $type = $scope->getType($node->class); From 79ebd459df4d151e0013057b9f18e4f071298103 Mon Sep 17 00:00:00 2001 From: ebln <34722048+ebln@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:34:53 +0200 Subject: [PATCH 10/13] Add test for production environment i.e. where on hasn't installed this rule --- tests/AttribForceFactoryRuleTest.php | 27 ++++++++++++------- tests/dataAttrib/IndependentFactory.php | 15 +++++++++++ tests/dataAttrib/RogueFactory.php | 7 +++++ .../code/IndependentForcedFactoryProduct.php | 13 +++++++++ 4 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 tests/dataAttrib/IndependentFactory.php create mode 100644 tests/dataAttrib/code/IndependentForcedFactoryProduct.php diff --git a/tests/AttribForceFactoryRuleTest.php b/tests/AttribForceFactoryRuleTest.php index 379c5c2..d57f165 100644 --- a/tests/AttribForceFactoryRuleTest.php +++ b/tests/AttribForceFactoryRuleTest.php @@ -40,16 +40,18 @@ public function testEmptyAllowedClasses(): void public function testRogueFactory(): void { + $offset = 1; $this->analyse([__DIR__ . '/dataAttrib/RogueFactory.php'], [ - [self::ERROR_MESSAGE, 15], - [self::ERROR_MESSAGE, 22], - [self::ERROR_MESSAGE, 29], - [self::ERROR_MESSAGE, 40], - [self::ERROR_MESSAGE, 40], - [self::ERROR_MESSAGE, 51], - [self::ERROR_MESSAGE, 56], - ['Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\ExtendedProduct must be instantiated by Test\Ebln\PHPStan\EnforceFactory\dataAttrib\ForcedFactory or Test\Ebln\PHPStan\EnforceFactory\dataAttrib\TraitFactory!', 69], - [self::ERROR_MESSAGE, 95], + [self::ERROR_MESSAGE, 15 + $offset], + [self::ERROR_MESSAGE, 22 + $offset], + [self::ERROR_MESSAGE, 29 + $offset], + [self::ERROR_MESSAGE, 40 + $offset], + [self::ERROR_MESSAGE, 40 + $offset], + [self::ERROR_MESSAGE, 51 + $offset], + [self::ERROR_MESSAGE, 56 + $offset], + ['Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\ExtendedProduct must be instantiated by Test\Ebln\PHPStan\EnforceFactory\dataAttrib\ForcedFactory or Test\Ebln\PHPStan\EnforceFactory\dataAttrib\TraitFactory!', 69 + $offset], + [self::ERROR_MESSAGE, 95 + $offset], + ['Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\IndependentForcedFactoryProduct must be instantiated by Test\Ebln\PHPStan\EnforceFactory\dataAttrib\IndependentFactory!', 100 + $offset], ]); } @@ -72,6 +74,13 @@ public function testAllowedFactory(): void $this->analyse([__DIR__ . '/dataAttrib/ForcedFactory.php'], []); } + public function testIndependentFactory(): void + { + $offset = 1; + $this->analyse([__DIR__ . '/dataAttrib/IndependentFactory.php'], [ + ]); + } + protected function getRule(): Rule { return new ForceFactoryRule($this->createReflectionProvider()); diff --git a/tests/dataAttrib/IndependentFactory.php b/tests/dataAttrib/IndependentFactory.php new file mode 100644 index 0000000..ba6a3dd --- /dev/null +++ b/tests/dataAttrib/IndependentFactory.php @@ -0,0 +1,15 @@ + Date: Fri, 7 Jun 2024 12:48:30 +0200 Subject: [PATCH 11/13] Move Attribute to shorter Namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit → \Ebln\Attrib\ForceFactory --- {src => attrib}/ForceFactory.php | 2 +- composer.json | 1 + src/ForceFactoryRule.php | 1 + tests/dataAttrib/code/EmptyProduct.php | 2 +- tests/dataAttrib/code/ForcedFactoryProduct.php | 2 +- tests/dataAttrib/code/IndependentForcedFactoryProduct.php | 2 +- tests/dataAttrib/code/LoopholeProduct.php | 2 +- tests/dataMixed/code/AttributeProduct.php | 2 +- tests/dataMixed/code/MismatchedProduct.php | 2 +- tests/dataMixed/code/MixedProduct.php | 2 +- 10 files changed, 10 insertions(+), 8 deletions(-) rename {src => attrib}/ForceFactory.php (95%) diff --git a/src/ForceFactory.php b/attrib/ForceFactory.php similarity index 95% rename from src/ForceFactory.php rename to attrib/ForceFactory.php index 324fda4..5f78fae 100644 --- a/src/ForceFactory.php +++ b/attrib/ForceFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Ebln\PHPStan\EnforceFactory; +namespace Ebln\Attrib; /** * Marks classes to be instanciated by certain factories diff --git a/composer.json b/composer.json index 710dd71..243c884 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ }, "autoload": { "psr-4": { + "Ebln\\Attrib\\": "attrib/", "Ebln\\PHPStan\\EnforceFactory\\": "src/" } }, diff --git a/src/ForceFactoryRule.php b/src/ForceFactoryRule.php index 46a2980..6a60767 100644 --- a/src/ForceFactoryRule.php +++ b/src/ForceFactoryRule.php @@ -4,6 +4,7 @@ namespace Ebln\PHPStan\EnforceFactory; +use Ebln\Attrib\ForceFactory; use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; diff --git a/tests/dataAttrib/code/EmptyProduct.php b/tests/dataAttrib/code/EmptyProduct.php index d59ab2b..b79f5a4 100644 --- a/tests/dataAttrib/code/EmptyProduct.php +++ b/tests/dataAttrib/code/EmptyProduct.php @@ -4,7 +4,7 @@ namespace Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code; -use Ebln\PHPStan\EnforceFactory\ForceFactory; +use Ebln\Attrib\ForceFactory; #[ForceFactory()] class EmptyProduct diff --git a/tests/dataAttrib/code/ForcedFactoryProduct.php b/tests/dataAttrib/code/ForcedFactoryProduct.php index db177b0..41e294e 100644 --- a/tests/dataAttrib/code/ForcedFactoryProduct.php +++ b/tests/dataAttrib/code/ForcedFactoryProduct.php @@ -4,7 +4,7 @@ namespace Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code; -use Ebln\PHPStan\EnforceFactory\ForceFactory; +use Ebln\Attrib\ForceFactory; use Test\Ebln\PHPStan\EnforceFactory\dataAttrib\ForcedFactory; use Test\Ebln\PHPStan\EnforceFactory\dataAttrib\TraitFactory; diff --git a/tests/dataAttrib/code/IndependentForcedFactoryProduct.php b/tests/dataAttrib/code/IndependentForcedFactoryProduct.php index d87104a..9a7acf7 100644 --- a/tests/dataAttrib/code/IndependentForcedFactoryProduct.php +++ b/tests/dataAttrib/code/IndependentForcedFactoryProduct.php @@ -6,7 +6,7 @@ use Test\Ebln\PHPStan\EnforceFactory\dataAttrib\IndependentFactory; -#[\Ebln\PHPStan\EnforceFactory\ForceFactory(IndependentFactory::class)] +#[\Ebln\Attrib\ForceFactory(IndependentFactory::class)] #[\INVALID\NOT\FOUND\ATTRIBUTE(IndependentFactory::class)] class IndependentForcedFactoryProduct { diff --git a/tests/dataAttrib/code/LoopholeProduct.php b/tests/dataAttrib/code/LoopholeProduct.php index c6825e2..0783421 100644 --- a/tests/dataAttrib/code/LoopholeProduct.php +++ b/tests/dataAttrib/code/LoopholeProduct.php @@ -4,7 +4,7 @@ namespace Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code; -use Ebln\PHPStan\EnforceFactory\ForceFactory; +use Ebln\Attrib\ForceFactory; use Test\Ebln\PHPStan\EnforceFactory\dataAttrib\ForcedFactory; use Test\Ebln\PHPStan\EnforceFactory\dataAttrib\TraitFactory; diff --git a/tests/dataMixed/code/AttributeProduct.php b/tests/dataMixed/code/AttributeProduct.php index fcaca3a..9a2cfe7 100644 --- a/tests/dataMixed/code/AttributeProduct.php +++ b/tests/dataMixed/code/AttributeProduct.php @@ -4,7 +4,7 @@ namespace Test\Ebln\PHPStan\EnforceFactory\dataMixed\code; -use Ebln\PHPStan\EnforceFactory\ForceFactory; +use Ebln\Attrib\ForceFactory; use Test\Ebln\PHPStan\EnforceFactory\dataMixed\AttributeFactory; #[ForceFactory(AttributeFactory::class)] diff --git a/tests/dataMixed/code/MismatchedProduct.php b/tests/dataMixed/code/MismatchedProduct.php index 434a2cf..c344ab6 100644 --- a/tests/dataMixed/code/MismatchedProduct.php +++ b/tests/dataMixed/code/MismatchedProduct.php @@ -4,7 +4,7 @@ namespace Test\Ebln\PHPStan\EnforceFactory\dataMixed\code; -use Ebln\PHPStan\EnforceFactory\ForceFactory; +use Ebln\Attrib\ForceFactory; use Ebln\PHPStan\EnforceFactory\ForceFactoryInterface; use Test\Ebln\PHPStan\EnforceFactory\dataMixed\AttributeFactory; use Test\Ebln\PHPStan\EnforceFactory\dataMixed\ForcedFactory; diff --git a/tests/dataMixed/code/MixedProduct.php b/tests/dataMixed/code/MixedProduct.php index c315a86..3052436 100644 --- a/tests/dataMixed/code/MixedProduct.php +++ b/tests/dataMixed/code/MixedProduct.php @@ -4,7 +4,7 @@ namespace Test\Ebln\PHPStan\EnforceFactory\dataMixed\code; -use Ebln\PHPStan\EnforceFactory\ForceFactory; +use Ebln\Attrib\ForceFactory; use Ebln\PHPStan\EnforceFactory\ForceFactoryInterface; use Test\Ebln\PHPStan\EnforceFactory\dataMixed\AttributeFactory; use Test\Ebln\PHPStan\EnforceFactory\dataMixed\ForcedFactory; From fbcb7050f77c0aca236fdb58dcf9d2505d743dcd Mon Sep 17 00:00:00 2001 From: ebln <34722048+ebln@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:21:41 +0200 Subject: [PATCH 12/13] Update Readme & Todo list --- CHANGELOG.md | 13 +++++++++++++ README.md | 45 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27f7217..fd9787c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ Intended to follow [«Keep a Changelog»](https://keepachangelog.com/en/) ---- +## Upcomming + +- Remove support for the interface +- Deprecate (abandon) the interface package +- create conflict with interface for version 2 + ```json + { + "conflict": { + "ebln/ebln/phpstan-factory-mark": "*" + } + } + ``` + ---- ## [1.0.0] diff --git a/README.md b/README.md index 78a5b06..6034745 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,44 @@ ebln/phpstan-factory-rule Enforce that your classes get only instantiated by the factories you define! -## Usage +## Usage with support for attributes +Require this package: `composer require --dev ebln/phpstan-factory-rule` -Install this package and the marking package alongside with PHPStan. +Add the `ForceFactory` attribute to your class, and supply all class names as arguments, +which shall be allowed to instanciate your object. +```php + Date: Fri, 7 Jun 2024 13:32:00 +0200 Subject: [PATCH 13/13] Remove debug artifacts and fix code PHPStan yells at `Attribute class INVALID\NOT\FOUND\ATTRIBUTE does not exist.` --- phpstan.neon | 9 --------- tests/dataAttrib/LoopholeFactory.php | 4 ++-- tests/dataAttrib/LoopholeInvoker.php | 4 ++-- .../dataAttrib/code/IndependentForcedFactoryProduct.php | 1 - 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 2e7645a..6699c65 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,12 +6,3 @@ parameters: paths: - src - tests - - excludePaths: - analyseAndScan: - - tests/data/* - - tests/dataAttrib/* - - tests/dataMixed/* - -rules: - - \Ebln\PHPStan\EnforceFactory\ForceFactoryRule diff --git a/tests/dataAttrib/LoopholeFactory.php b/tests/dataAttrib/LoopholeFactory.php index 912c0cf..b41f714 100644 --- a/tests/dataAttrib/LoopholeFactory.php +++ b/tests/dataAttrib/LoopholeFactory.php @@ -6,7 +6,7 @@ class LoopholeFactory { - public function variableUninferable(bool $toggle): void + public function variableUninferable(bool $toggle): object { if ($toggle) { $class = '\Test\Ebln\PHPStan\EnforceFactory\dataAttrib\code\LoopholeProduct'; @@ -14,6 +14,6 @@ public function variableUninferable(bool $toggle): void $class = 'Hello world-' . random_int(10, 99); } - $new = new $class(); + return new $class(); } } diff --git a/tests/dataAttrib/LoopholeInvoker.php b/tests/dataAttrib/LoopholeInvoker.php index f4d708c..fff9c91 100644 --- a/tests/dataAttrib/LoopholeInvoker.php +++ b/tests/dataAttrib/LoopholeInvoker.php @@ -20,8 +20,8 @@ public function expectedFailingLoophole(): object return $loophole; } - public function expectedMissingClass() + public function expectedMissingClass(): void { - return $this->loopholeFactory->variableUninferable(false); + $this->loopholeFactory->variableUninferable(false); } } diff --git a/tests/dataAttrib/code/IndependentForcedFactoryProduct.php b/tests/dataAttrib/code/IndependentForcedFactoryProduct.php index 9a7acf7..a125fa8 100644 --- a/tests/dataAttrib/code/IndependentForcedFactoryProduct.php +++ b/tests/dataAttrib/code/IndependentForcedFactoryProduct.php @@ -7,7 +7,6 @@ use Test\Ebln\PHPStan\EnforceFactory\dataAttrib\IndependentFactory; #[\Ebln\Attrib\ForceFactory(IndependentFactory::class)] -#[\INVALID\NOT\FOUND\ATTRIBUTE(IndependentFactory::class)] class IndependentForcedFactoryProduct { }