From fe6e4032d03aab726d457a34c8fdf56638ed6451 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 18 Dec 2022 09:09:18 +0100 Subject: [PATCH] Refactor to use TomasVotruba/unused-public (#264) --- composer.json | 3 +- composer.lock | 45 ++++++++- config/dead-code.neon | 32 ++---- lib/RexStan.php | 8 +- vendor/composer/autoload_psr4.php | 1 + vendor/composer/autoload_static.php | 8 ++ vendor/composer/installed.json | 46 +++++++++ vendor/composer/installed.php | 13 ++- .../tomasvotruba/unused-public/.editorconfig | 9 ++ vendor/tomasvotruba/unused-public/.gitignore | 4 + vendor/tomasvotruba/unused-public/README.md | 94 ++++++++++++++++++ .../tomasvotruba/unused-public/composer.json | 23 +++++ .../unused-public/config/extension.neon | 76 ++++++++++++++ vendor/tomasvotruba/unused-public/ecs.php | 17 ++++ vendor/tomasvotruba/unused-public/phpunit.xml | 12 +++ vendor/tomasvotruba/unused-public/rector.php | 37 +++++++ .../unused-public/src/ApiDocStmtAnalyzer.php | 30 ++++++ .../src/ClassMethodCallReferenceResolver.php | 44 +++++++++ .../Collectors/ClassConstFetchCollector.php | 69 +++++++++++++ .../src/Collectors/MethodCallCollector.php | 96 ++++++++++++++++++ .../PublicClassLikeConstCollector.php | 70 +++++++++++++ .../Collectors/PublicClassMethodCollector.php | 92 +++++++++++++++++ .../Collectors/PublicPropertyCollector.php | 86 ++++++++++++++++ .../PublicPropertyFetchCollector.php | 72 ++++++++++++++ .../PublicStaticPropertyCollector.php | 89 +++++++++++++++++ .../PublicStaticPropertyFetchCollector.php | 63 ++++++++++++ .../unused-public/src/Configuration.php | 44 +++++++++ .../src/PublicClassMethodMatcher.php | 79 +++++++++++++++ .../src/Rules/UnusedPublicClassConstRule.php | 95 ++++++++++++++++++ .../src/Rules/UnusedPublicClassMethodRule.php | 96 ++++++++++++++++++ .../src/Rules/UnusedPublicPropertyRule.php | 95 ++++++++++++++++++ .../Rules/UnusedPublicStaticPropertyRule.php | 99 +++++++++++++++++++ .../src/ValueObject/MethodCallReference.php | 34 +++++++ 33 files changed, 1650 insertions(+), 31 deletions(-) create mode 100644 vendor/tomasvotruba/unused-public/.editorconfig create mode 100644 vendor/tomasvotruba/unused-public/.gitignore create mode 100644 vendor/tomasvotruba/unused-public/README.md create mode 100644 vendor/tomasvotruba/unused-public/composer.json create mode 100644 vendor/tomasvotruba/unused-public/config/extension.neon create mode 100644 vendor/tomasvotruba/unused-public/ecs.php create mode 100644 vendor/tomasvotruba/unused-public/phpunit.xml create mode 100644 vendor/tomasvotruba/unused-public/rector.php create mode 100644 vendor/tomasvotruba/unused-public/src/ApiDocStmtAnalyzer.php create mode 100644 vendor/tomasvotruba/unused-public/src/ClassMethodCallReferenceResolver.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/ClassConstFetchCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/MethodCallCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/PublicClassLikeConstCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/PublicClassMethodCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyFetchCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyFetchCollector.php create mode 100644 vendor/tomasvotruba/unused-public/src/Configuration.php create mode 100644 vendor/tomasvotruba/unused-public/src/PublicClassMethodMatcher.php create mode 100644 vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassConstRule.php create mode 100644 vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassMethodRule.php create mode 100644 vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicPropertyRule.php create mode 100644 vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicStaticPropertyRule.php create mode 100644 vendor/tomasvotruba/unused-public/src/ValueObject/MethodCallReference.php diff --git a/composer.json b/composer.json index bff77c9d5..b405c226c 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "staabm/phpstan-dba": "^0.2.53", "spaze/phpstan-disallowed-calls": "^2.11", "symplify/phpstan-rules": "^11.1", - "staabm/phpstan-baseline-analysis": "^0.9" + "staabm/phpstan-baseline-analysis": "^0.9", + "tomasvotruba/unused-public": "0.0.10.72" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 946f5a1fc..67e5756f1 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": "2a1152e039f9a9cf920d733efb3172b7", + "content-hash": "78b67d88175d57e260ea37731e62e492", "packages": [ { "name": "composer/semver", @@ -1074,6 +1074,49 @@ }, "time": "2020-10-28T17:51:34+00:00" }, + { + "name": "tomasvotruba/unused-public", + "version": "0.0.10.72", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/unused-public.git", + "reference": "6c425fcb79c11d6e2124f2db5fa56b69580b164f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/unused-public/zipball/6c425fcb79c11d6e2124f2db5fa56b69580b164f", + "reference": "6c425fcb79c11d6e2124f2db5fa56b69580b164f", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2", + "php": "^7.2|8.0", + "phpstan/phpstan": "^1.9.3" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "TomasVotruba\\UnusedPublic\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Detect unused public properties, constants and methods in your code", + "support": { + "issues": "https://github.com/TomasVotruba/unused-public/issues", + "source": "https://github.com/TomasVotruba/unused-public/tree/0.0.10.72" + }, + "time": "2022-12-16T10:34:15+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/config/dead-code.neon b/config/dead-code.neon index c0fb4bb96..018b3caaa 100644 --- a/config/dead-code.neon +++ b/config/dead-code.neon @@ -1,23 +1,9 @@ -# these rule focus on the whole-project analysis, see https://phpstan.org/developing-extensions/collectors -rules: - - Symplify\PHPStanRules\Rules\DeadCode\UnusedPublicClassConstRule - - Symplify\PHPStanRules\Rules\DeadCode\UnusedPublicClassMethodRule - -services: - # for UnusedPublicClassConstRule - - - class: Symplify\PHPStanRules\Collector\ClassConst\ClassConstFetchCollector - tags: [phpstan.collector] - - - - class: Symplify\PHPStanRules\Collector\ClassConst\PublicClassLikeConstCollector - tags: [phpstan.collector] - - # for UnusedPublicClassMethodRule - - - class: Symplify\PHPStanRules\Collector\ClassMethod\PublicClassMethodCollector - tags: [phpstan.collector] - - - - class: Symplify\PHPStanRules\Collector\ClassMethod\MethodCallCollector - tags: [phpstan.collector] +includes: + - ../vendor/tomasvotruba/unused-public/config/extension.neon + +parameters: + unused_public: + methods: true + properties: true + constants: true + static_properties: true diff --git a/lib/RexStan.php b/lib/RexStan.php index 10060fd09..ee5439138 100644 --- a/lib/RexStan.php +++ b/lib/RexStan.php @@ -21,7 +21,7 @@ public static function phpExecutable(): string if ('Windows' !== PHP_OS_FAMILY) { $executable = 'php'; $path = '$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'; - + if ('Darwin' === PHP_OS_FAMILY) { $customConfig = '/Library/Application Support/appsolute/MAMP PRO/conf/php'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.PHP_RELEASE_VERSION.'.ini'; if (is_file($customConfig)) { @@ -31,12 +31,12 @@ public static function phpExecutable(): string $mampPhp = '/Applications/MAMP/bin/php/php'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.PHP_RELEASE_VERSION.'/bin/'; if (is_executable($mampPhp.'php')) { $path .= ':'.$mampPhp; - } + } } - + return 'PATH="'. $path .'" '.$executable; } - + return 'php'; } diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 54433e5a4..97587d60f 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -8,6 +8,7 @@ return array( 'staabm\\PHPStanDba\\' => array($vendorDir . '/staabm/phpstan-dba/src'), 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), + 'TomasVotruba\\UnusedPublic\\' => array($vendorDir . '/tomasvotruba/unused-public/src'), 'Symplify\\PHPStanRules\\' => array($vendorDir . '/symplify/phpstan-rules/src', $vendorDir . '/symplify/phpstan-rules/packages'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Spaze\\PHPStan\\Rules\\Disallowed\\' => array($vendorDir . '/spaze/phpstan-disallowed-calls/src'), diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 754ceabb0..cc46fef75 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -109,6 +109,10 @@ class ComposerStaticInit9cf8af24a7a084f114b4553be2a1ff9f array ( 'Webmozart\\Assert\\' => 17, ), + 'T' => + array ( + 'TomasVotruba\\UnusedPublic\\' => 26, + ), 'S' => array ( 'Symplify\\PHPStanRules\\' => 22, @@ -137,6 +141,10 @@ class ComposerStaticInit9cf8af24a7a084f114b4553be2a1ff9f array ( 0 => __DIR__ . '/..' . '/webmozart/assert/src', ), + 'TomasVotruba\\UnusedPublic\\' => + array ( + 0 => __DIR__ . '/..' . '/tomasvotruba/unused-public/src', + ), 'Symplify\\PHPStanRules\\' => array ( 0 => __DIR__ . '/..' . '/symplify/phpstan-rules/src', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index c53cd31e3..b7be80436 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1116,6 +1116,52 @@ }, "install-path": "../thecodingmachine/safe" }, + { + "name": "tomasvotruba/unused-public", + "version": "0.0.10.72", + "version_normalized": "0.0.10.72", + "source": { + "type": "git", + "url": "https://github.com/TomasVotruba/unused-public.git", + "reference": "6c425fcb79c11d6e2124f2db5fa56b69580b164f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TomasVotruba/unused-public/zipball/6c425fcb79c11d6e2124f2db5fa56b69580b164f", + "reference": "6c425fcb79c11d6e2124f2db5fa56b69580b164f", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2", + "php": "^7.2|8.0", + "phpstan/phpstan": "^1.9.3" + }, + "time": "2022-12-16T10:34:15+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "TomasVotruba\\UnusedPublic\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Detect unused public properties, constants and methods in your code", + "support": { + "issues": "https://github.com/TomasVotruba/unused-public/issues", + "source": "https://github.com/TomasVotruba/unused-public/tree/0.0.10.72" + }, + "install-path": "../tomasvotruba/unused-public" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 9f91a88ad..66a6e0148 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'dd7a6bd3db81a766233bbf42d089ecd4dbc6ce27', + 'reference' => '2cbc96f0a498327e40d98a9a40ea6c845e3f12ce', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'dd7a6bd3db81a766233bbf42d089ecd4dbc6ce27', + 'reference' => '2cbc96f0a498327e40d98a9a40ea6c845e3f12ce', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -163,6 +163,15 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'tomasvotruba/unused-public' => array( + 'pretty_version' => '0.0.10.72', + 'version' => '0.0.10.72', + 'reference' => '6c425fcb79c11d6e2124f2db5fa56b69580b164f', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../tomasvotruba/unused-public', + 'aliases' => array(), + 'dev_requirement' => false, + ), 'webmozart/assert' => array( 'pretty_version' => '1.11.0', 'version' => '1.11.0.0', diff --git a/vendor/tomasvotruba/unused-public/.editorconfig b/vendor/tomasvotruba/unused-public/.editorconfig new file mode 100644 index 000000000..bec95c449 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 diff --git a/vendor/tomasvotruba/unused-public/.gitignore b/vendor/tomasvotruba/unused-public/.gitignore new file mode 100644 index 000000000..0e9930283 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/.gitignore @@ -0,0 +1,4 @@ +/vendor +composer.lock + +.phpunit.result.cache diff --git a/vendor/tomasvotruba/unused-public/README.md b/vendor/tomasvotruba/unused-public/README.md new file mode 100644 index 000000000..d879b5fa8 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/README.md @@ -0,0 +1,94 @@ +# Find Unused Public Constants, Properties and Methods in Your Code + +It's easy to find unused private class elements, because they're not used in the class itself: + +```diff + final class Book + { + public function getTitle(): string + { + // ... + } + +- private function getSubtitle(): string +- { +- // ... +- } +} +``` + +But what about public class elements? + +
+ +**How can we detect such element?** + +* find a e.g. public method +* find all public method calls +* compare those in simple diff +* if the public method is not found, it probably unused + +That's exactly what this package does. + +
+ +This technique is very useful for private projects and to detect accidentally open public API that should be used only locally. + +
+ +## Install + +```bash +composer require tomasvotruba/unused-public --dev +``` + +The package is available on PHP 7.2-8.1 versions in tagged releases. + +
+ +## Usage + +With PHPStan extension installer, everything is ready to run. + +Enable each item on their own with simple configuration: + +```neon +# phpstan.neon +parameters: + unused_public: + methods: true + properties: true + constants: true + static_properties: true +``` + +
+ +## Known Limitations + +In some cases, the method reports false positives: + +* it's not possible to detect unused public method that are called only in Twig templates +* following cases are skipped + * public function in Twig extensions - those are functions/filters callable + +
+ +## Skip False Positives + +Is element reported as unused, but it's actually used? + +Mark the class or element wit `@api` to declare it as public API and skip it: + +```php +final class Book +{ + /** + * @api + */ + public function getName() + { + return $this->name; + } +} +``` diff --git a/vendor/tomasvotruba/unused-public/composer.json b/vendor/tomasvotruba/unused-public/composer.json new file mode 100644 index 000000000..dc79576a7 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/composer.json @@ -0,0 +1,23 @@ +{ + "name": "tomasvotruba/unused-public", + "type": "phpstan-extension", + "description": "Detect unused public properties, constants and methods in your code", + "license": "MIT", + "require": { + "php": "^7.2|8.0", + "phpstan/phpstan": "^1.9.3", + "nette/utils": "^3.2" + }, + "autoload": { + "psr-4": { + "TomasVotruba\\UnusedPublic\\": "src" + } + }, + "extra": { + "phpstan": { + "includes": [ + "config/extension.neon" + ] + } + } +} diff --git a/vendor/tomasvotruba/unused-public/config/extension.neon b/vendor/tomasvotruba/unused-public/config/extension.neon new file mode 100644 index 000000000..054b52f6b --- /dev/null +++ b/vendor/tomasvotruba/unused-public/config/extension.neon @@ -0,0 +1,76 @@ +parametersSchema: + unused_public: structure([ + methods: bool() + properties: bool() + constants: bool() + static_properties: bool() + ]) + +# default parameters +parameters: + unused_public: + methods: false + properties: false + constants: false + static_properties: false + +services: + - TomasVotruba\UnusedPublic\PublicClassMethodMatcher + - TomasVotruba\UnusedPublic\ApiDocStmtAnalyzer + - TomasVotruba\UnusedPublic\ClassMethodCallReferenceResolver + + - + factory: TomasVotruba\UnusedPublic\Configuration + arguments: + - %unused_public% + + # collectors + + # methods + - + class: TomasVotruba\UnusedPublic\Collectors\PublicClassMethodCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\UnusedPublic\Collectors\MethodCallCollector + tags: + - phpstan.collector + + # static_properties + - + class: TomasVotruba\UnusedPublic\Collectors\PublicStaticPropertyCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\UnusedPublic\Collectors\PublicStaticPropertyFetchCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\UnusedPublic\Collectors\PublicClassLikeConstCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\UnusedPublic\Collectors\ClassConstFetchCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\UnusedPublic\Collectors\PublicPropertyCollector + tags: + - phpstan.collector + + - + class: TomasVotruba\UnusedPublic\Collectors\PublicPropertyFetchCollector + tags: + - phpstan.collector + +# rules +rules: + - TomasVotruba\UnusedPublic\Rules\UnusedPublicClassMethodRule + - TomasVotruba\UnusedPublic\Rules\UnusedPublicStaticPropertyRule + - TomasVotruba\UnusedPublic\Rules\UnusedPublicClassConstRule + - TomasVotruba\UnusedPublic\Rules\UnusedPublicPropertyRule diff --git a/vendor/tomasvotruba/unused-public/ecs.php b/vendor/tomasvotruba/unused-public/ecs.php new file mode 100644 index 000000000..053ecdb61 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/ecs.php @@ -0,0 +1,17 @@ +paths([__DIR__ . '/src', __DIR__ . '/tests']); + + $ecsConfig->sets([ + SetList::COMMON, + SetList::PSR_12, + SetList::CLEAN_CODE, + SetList::SYMPLIFY, + ]); +}; diff --git a/vendor/tomasvotruba/unused-public/phpunit.xml b/vendor/tomasvotruba/unused-public/phpunit.xml new file mode 100644 index 000000000..b35a9c38f --- /dev/null +++ b/vendor/tomasvotruba/unused-public/phpunit.xml @@ -0,0 +1,12 @@ + + + + tests + + diff --git a/vendor/tomasvotruba/unused-public/rector.php b/vendor/tomasvotruba/unused-public/rector.php new file mode 100644 index 000000000..bdfe22297 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/rector.php @@ -0,0 +1,37 @@ +paths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]); + + $rectorConfig->importNames(); + + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_81, + SetList::TYPE_DECLARATION, + SetList::PRIVATIZATION, + SetList::NAMING, + SetList::DEAD_CODE, + SetList::CODE_QUALITY, + SetList::CODING_STYLE, + ]); + + $rectorConfig->skip([ + '*/Fixture/*', + '*/Source/*', + + VarConstantCommentRector::class => [ + __DIR__ . '/src/PublicClassMethodMatcher.php', + ], + ]); +}; diff --git a/vendor/tomasvotruba/unused-public/src/ApiDocStmtAnalyzer.php b/vendor/tomasvotruba/unused-public/src/ApiDocStmtAnalyzer.php new file mode 100644 index 000000000..b8348125d --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/ApiDocStmtAnalyzer.php @@ -0,0 +1,30 @@ +getResolvedPhpDoc() instanceof ResolvedPhpDocBlock) { + $resolvedPhpDoc = $classReflection->getResolvedPhpDoc(); + if (strpos($resolvedPhpDoc->getPhpDocString(), '@api') !== false) { + return true; + } + } + + $docComment = $stmt->getDocComment(); + if (! $docComment instanceof Doc) { + return false; + } + + return strpos($docComment->getText(), '@api') !== false; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/ClassMethodCallReferenceResolver.php b/vendor/tomasvotruba/unused-public/src/ClassMethodCallReferenceResolver.php new file mode 100644 index 000000000..20c4af22a --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/ClassMethodCallReferenceResolver.php @@ -0,0 +1,44 @@ +name instanceof Expr) { + return null; + } + + $callerType = $scope->getType($methodCall->var); + + // remove optional nullable type + if (TypeCombinator::containsNull($callerType)) { + $callerType = TypeCombinator::removeNull($callerType); + } + + if (! $allowThisType && $callerType instanceof ThisType) { + return null; + } + + if (! $callerType instanceof TypeWithClassName) { + return null; + } + + // move to the class where method is defined, e.g. parent class defines the method, so it should be checked there + $className = $callerType->getClassName(); + $methodName = $methodCall->name->toString(); + + return new MethodCallReference($className, $methodName); + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/ClassConstFetchCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/ClassConstFetchCollector.php new file mode 100644 index 000000000..a6de74335 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/ClassConstFetchCollector.php @@ -0,0 +1,69 @@ + + */ +final class ClassConstFetchCollector implements Collector +{ + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + } + + public function getNodeType(): string + { + return ClassConstFetch::class; + } + + /** + * @param ClassConstFetch $node + * @return string[]|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedConstantsEnabled()) { + return []; + } + + if (! $node->class instanceof Name) { + return null; + } + + if (! $node->name instanceof Identifier) { + return null; + } + + $className = $node->class->toString(); + $constantName = $node->name->toString(); + + $classReflection = $scope->getClassReflection(); + if ($classReflection !== null && $classReflection->hasConstant($constantName)) { + $constantReflection = $classReflection->getConstant($constantName); + $declaringClass = $constantReflection->getDeclaringClass(); + if ($declaringClass->getFileName() !== $classReflection->getFileName()) { + return [$declaringClass->getName() . '::' . $constantName]; + } + + return null; + } + + return [$className . '::' . $constantName]; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/MethodCallCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/MethodCallCollector.php new file mode 100644 index 000000000..ad30f9d5f --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/MethodCallCollector.php @@ -0,0 +1,96 @@ +|null> + */ +final class MethodCallCollector implements Collector +{ + /** + * @readonly + * @var \PHPStan\Reflection\ReflectionProvider + */ + private $reflectionProvider; + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\ClassMethodCallReferenceResolver + */ + private $classMethodCallReferenceResolver; + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(ReflectionProvider $reflectionProvider, ClassMethodCallReferenceResolver $classMethodCallReferenceResolver, Configuration $configuration) + { + $this->reflectionProvider = $reflectionProvider; + $this->classMethodCallReferenceResolver = $classMethodCallReferenceResolver; + $this->configuration = $configuration; + } + public function getNodeType(): string + { + return MethodCall::class; + } + + /** + * @param MethodCall $node + * @return string[]|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedMethodEnabled()) { + return null; + } + + if ($node->name instanceof Expr) { + return null; + } + + $classMethodCallReference = $this->classMethodCallReferenceResolver->resolve($node, $scope, false); + if (! $classMethodCallReference instanceof MethodCallReference) { + return null; + } + + $className = $classMethodCallReference->getClass(); + $methodName = $classMethodCallReference->getMethod(); + + $classMethodReferences = $this->findParentClassMethodReferences($className, $methodName); + $classMethodReferences[] = $className . '::' . $methodName; + + return $classMethodReferences; + } + + /** + * @return string[] + */ + private function findParentClassMethodReferences(string $className, string $methodName): array + { + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } + + $classReflection = $this->reflectionProvider->getClass($className); + + $classMethodReferences = []; + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasNativeMethod($methodName)) { + $classMethodReferences[] = $parentClassReflection->getName() . '::' . $methodName; + } + } + + return $classMethodReferences; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/PublicClassLikeConstCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/PublicClassLikeConstCollector.php new file mode 100644 index 000000000..d4d85e162 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/PublicClassLikeConstCollector.php @@ -0,0 +1,70 @@ +> + */ +final class PublicClassLikeConstCollector implements Collector +{ + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\ApiDocStmtAnalyzer + */ + private $apiDocStmtAnalyzer; + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(ApiDocStmtAnalyzer $apiDocStmtAnalyzer, Configuration $configuration) + { + $this->apiDocStmtAnalyzer = $apiDocStmtAnalyzer; + $this->configuration = $configuration; + } + public function getNodeType(): string + { + return ClassConst::class; + } + + /** + * @param ClassConst $node + * @return array|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedConstantsEnabled()) { + return []; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + if (! $node->isPublic()) { + return null; + } + + if ($this->apiDocStmtAnalyzer->isApiDoc($node, $classReflection)) { + return null; + } + + $constantNames = []; + foreach ($node->consts as $constConst) { + $constantNames[] = [$classReflection->getName(), $constConst->name->toString(), $node->getLine()]; + } + + return $constantNames; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/PublicClassMethodCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/PublicClassMethodCollector.php new file mode 100644 index 000000000..7be02b80c --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/PublicClassMethodCollector.php @@ -0,0 +1,92 @@ + + */ +final class PublicClassMethodCollector implements Collector +{ + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\ApiDocStmtAnalyzer + */ + private $apiDocStmtAnalyzer; + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\PublicClassMethodMatcher + */ + private $publicClassMethodMatcher; + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(ApiDocStmtAnalyzer $apiDocStmtAnalyzer, PublicClassMethodMatcher $publicClassMethodMatcher, Configuration $configuration) + { + $this->apiDocStmtAnalyzer = $apiDocStmtAnalyzer; + $this->publicClassMethodMatcher = $publicClassMethodMatcher; + $this->configuration = $configuration; + } + public function getNodeType(): string + { + return ClassMethod::class; + } + + /** + * @param ClassMethod $node + * @return array|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedMethodEnabled()) { + return null; + } + + $classReflection = $scope->getClassReflection(); + + // skip + if ($classReflection instanceof ClassReflection && $classReflection->isSubclassOf(ExtensionInterface::class)) { + return null; + } + + if ($this->publicClassMethodMatcher->shouldSkipClassMethod($node)) { + return null; + } + + // only if the class has no parents/implementers, to avoid class method required by contracts + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + if ($this->apiDocStmtAnalyzer->isApiDoc($node, $classReflection)) { + return null; + } + + if ($this->publicClassMethodMatcher->shouldSkipClassReflection($classReflection)) { + return null; + } + + $methodName = $node->name->toString(); + + // is this method required by parent contract? skip it + if ($this->publicClassMethodMatcher->isUsedByParentClassOrInterface($classReflection, $methodName)) { + return null; + } + + return [$classReflection->getName(), $methodName, $node->getLine()]; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyCollector.php new file mode 100644 index 000000000..88029dd39 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyCollector.php @@ -0,0 +1,86 @@ +> + */ +final class PublicPropertyCollector implements Collector +{ + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\ApiDocStmtAnalyzer + */ + private $apiDocStmtAnalyzer; + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(ApiDocStmtAnalyzer $apiDocStmtAnalyzer, Configuration $configuration) + { + $this->apiDocStmtAnalyzer = $apiDocStmtAnalyzer; + $this->configuration = $configuration; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return InClassNode::class; + } + + /** + * @param InClassNode $node + * @return array|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedPropertyEnabled()) { + return null; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + if ($this->apiDocStmtAnalyzer->isApiDoc($node, $classReflection)) { + return null; + } + + $classLike = $node->getOriginalNode(); + if (! $classLike instanceof Class_) { + return null; + } + + $publicPropertyNames = []; + foreach ($classLike->getProperties() as $property) { + if (! $property->isPublic()) { + continue; + } + + foreach ($property->props as $propertyProperty) { + $publicPropertyNames[] = [ + $classReflection->getName(), + $propertyProperty->name->toString(), + $node->getLine(), + ]; + } + } + + return $publicPropertyNames; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyFetchCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyFetchCollector.php new file mode 100644 index 000000000..7be1fc687 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/PublicPropertyFetchCollector.php @@ -0,0 +1,72 @@ + + */ +final class PublicPropertyFetchCollector implements Collector +{ + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + } + + /** + * @return class-string + */ + public function getNodeType(): string + { + return PropertyFetch::class; + } + + /** + * @param PropertyFetch $node + * @return string[]|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedPropertyEnabled()) { + return null; + } + + if (! $node->var instanceof Variable) { + return null; + } + + // skip local + if ($node->var->name === 'this') { + return null; + } + + if (! $node->name instanceof Identifier) { + return null; + } + + $propertyFetcherType = $scope->getType($node->var); + if (! $propertyFetcherType instanceof TypeWithClassName) { + return null; + } + + $className = $propertyFetcherType->getClassName(); + $propertyName = $node->name->toString(); + + return [$className . '::' . $propertyName]; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyCollector.php new file mode 100644 index 000000000..60dd020cf --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyCollector.php @@ -0,0 +1,89 @@ +> + */ +final class PublicStaticPropertyCollector implements Collector +{ + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\ApiDocStmtAnalyzer + */ + private $apiDocStmtAnalyzer; + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(ApiDocStmtAnalyzer $apiDocStmtAnalyzer, Configuration $configuration) + { + $this->apiDocStmtAnalyzer = $apiDocStmtAnalyzer; + $this->configuration = $configuration; + } + /** + * @return class-string + */ + public function getNodeType(): string + { + return InClassNode::class; + } + + /** + * @param InClassNode $node + * @return array|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedStaticPropertyEnabled()) { + return null; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection instanceof ClassReflection) { + return null; + } + + if ($this->apiDocStmtAnalyzer->isApiDoc($node, $classReflection)) { + return null; + } + + $classLike = $node->getOriginalNode(); + if (! $classLike instanceof Class_) { + return null; + } + + $staticPropertyNames = []; + foreach ($classLike->getProperties() as $property) { + if (! $property->isPublic()) { + continue; + } + + if (! $property->isStatic()) { + continue; + } + + foreach ($property->props as $propertyProperty) { + $staticPropertyNames[] = [ + $classReflection->getName(), + $propertyProperty->name->toString(), + $node->getLine(), + ]; + } + } + + return $staticPropertyNames; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyFetchCollector.php b/vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyFetchCollector.php new file mode 100644 index 000000000..e927b37ca --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Collectors/PublicStaticPropertyFetchCollector.php @@ -0,0 +1,63 @@ + + */ +final class PublicStaticPropertyFetchCollector implements Collector +{ + /** + * @readonly + * @var \TomasVotruba\UnusedPublic\Configuration + */ + private $configuration; + public function __construct(Configuration $configuration) + { + $this->configuration = $configuration; + } + + public function getNodeType(): string + { + return StaticPropertyFetch::class; + } + + /** + * @param StaticPropertyFetch $node + * @return string[]|null + */ + public function processNode(Node $node, Scope $scope): ?array + { + if (! $this->configuration->isUnusedStaticPropertyEnabled()) { + return null; + } + + if (! $node->class instanceof Name) { + return null; + } + + if (! $node->name instanceof Identifier) { + return null; + } + + if ($node->class->toString() === 'self') { + // self fetch is allowed + return null; + } + + $className = $node->class->toString(); + $propertyName = $node->name->toString(); + + return [$className . '::' . $propertyName]; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Configuration.php b/vendor/tomasvotruba/unused-public/src/Configuration.php new file mode 100644 index 000000000..a6192a18c --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Configuration.php @@ -0,0 +1,44 @@ + + * @readonly + */ + private $parameters; + /** + * @param array $parameters + */ + public function __construct(array $parameters) + { + $this->parameters = $parameters; + } + + public function isUnusedMethodEnabled(): bool + { + return $this->parameters['methods'] ?? false; + } + + public function isUnusedStaticPropertyEnabled(): bool + { + return $this->parameters['static_properties'] ?? false; + } + + public function isUnusedPropertyEnabled(): bool + { + return $this->parameters['properties'] ?? false; + } + + public function isUnusedConstantsEnabled(): bool + { + return $this->parameters['constants'] ?? false; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/PublicClassMethodMatcher.php b/vendor/tomasvotruba/unused-public/src/PublicClassMethodMatcher.php new file mode 100644 index 000000000..6cfbff766 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/PublicClassMethodMatcher.php @@ -0,0 +1,79 @@ +isClass()) { + return true; + } + + foreach (self::SKIPPED_TYPES as $skippedType) { + if ($classReflection->isSubclassOf($skippedType)) { + return true; + } + } + + return false; + } + + public function isUsedByParentClassOrInterface(ClassReflection $classReflection, string $methodName): bool + { + // is this method required by parent contract? skip it + foreach ($classReflection->getInterfaces() as $parentInterfaceReflection) { + if ($parentInterfaceReflection->hasMethod($methodName)) { + return true; + } + } + + foreach ($classReflection->getParents() as $parentClassReflection) { + if ($parentClassReflection->hasMethod($methodName)) { + return true; + } + } + + return false; + } + + public function shouldSkipClassMethod(ClassMethod $classMethod): bool + { + if ($classMethod->isMagic()) { + return true; + } + + if ($classMethod->isStatic()) { + return true; + } + + // skip attributes + if ($classMethod->attrGroups !== []) { + return true; + } + + if (! $classMethod->isPublic()) { + return true; + } + + $doc = $classMethod->getDocComment(); + + // skip symfony action + return $doc instanceof Doc && strpos($doc->getText(), '@Route') !== false; + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassConstRule.php b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassConstRule.php new file mode 100644 index 000000000..3cdff0642 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassConstRule.php @@ -0,0 +1,95 @@ +configuration = $configuration; + } + + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + if (! $this->configuration->isUnusedConstantsEnabled()) { + return []; + } + + $classConstFetchCollector = $node->get(ClassConstFetchCollector::class); + $publicClassLikeConstCollector = $node->get(PublicClassLikeConstCollector::class); + + $ruleErrors = []; + + foreach ($publicClassLikeConstCollector as $filePath => $declarationsGroups) { + foreach ($declarationsGroups as $declarationGroup) { + foreach ($declarationGroup as [$className, $constantName, $line]) { + if ($this->isClassConstantUsed($className, $constantName, $classConstFetchCollector)) { + continue; + } + + /** @var string $constantName */ + $errorMessage = sprintf(self::ERROR_MESSAGE, $constantName); + + $ruleErrors[] = RuleErrorBuilder::message($errorMessage) + ->file($filePath) + ->line($line) + ->tip(self::TIP_MESSAGE) + ->build(); + } + } + } + + return $ruleErrors; + } + + /** + * @param mixed[] $usedConstFetches + */ + private function isClassConstantUsed(string $className, string $constantName, array $usedConstFetches): bool + { + $publicConstantReference = $className . '::' . $constantName; + + $usedConstFetches = Arrays::flatten($usedConstFetches); + return in_array($publicConstantReference, $usedConstFetches, true); + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassMethodRule.php b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassMethodRule.php new file mode 100644 index 000000000..8f0411ad7 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicClassMethodRule.php @@ -0,0 +1,96 @@ +configuration = $configuration; + } + + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + if (! $this->configuration->isUnusedMethodEnabled()) { + return []; + } + + $methodCallCollector = $node->get(MethodCallCollector::class); + $publicClassMethodCollector = $node->get(PublicClassMethodCollector::class); + + $ruleErrors = []; + + foreach ($publicClassMethodCollector as $filePath => $declarations) { + foreach ($declarations as [$className, $methodName, $line]) { + if ($this->isClassMethod($className, $methodName, $methodCallCollector)) { + continue; + } + + /** @var string $methodName */ + $errorMessage = sprintf(self::ERROR_MESSAGE, $methodName); + + $ruleErrors[] = RuleErrorBuilder::message($errorMessage) + ->file($filePath) + ->line($line) + ->tip(self::TIP_MESSAGE) + ->build(); + } + } + + return $ruleErrors; + } + + /** + * @param mixed[] $usedClassMethods + */ + private function isClassMethod(string $className, string $constantName, array $usedClassMethods): bool + { + $publicMethodReference = $className . '::' . $constantName; + $usedClassMethods = Arrays::flatten($usedClassMethods); + + return in_array($publicMethodReference, $usedClassMethods, true); + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicPropertyRule.php b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicPropertyRule.php new file mode 100644 index 000000000..230ee6c7d --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicPropertyRule.php @@ -0,0 +1,95 @@ +configuration = $configuration; + } + + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + if (! $this->configuration->isUnusedPropertyEnabled()) { + return []; + } + + $publicPropertyCollector = $node->get(PublicPropertyCollector::class); + $publicPropertyFetchCollector = $node->get(PublicPropertyFetchCollector::class); + + $ruleErrors = []; + + foreach ($publicPropertyCollector as $filePath => $declarationsGroups) { + foreach ($declarationsGroups as $declarationGroup) { + foreach ($declarationGroup as [$className, $propertyName, $line]) { + if ($this->isPropertyUsed($className, $propertyName, $publicPropertyFetchCollector)) { + continue; + } + + /** @var string $propertyName */ + $errorMessage = sprintf(self::ERROR_MESSAGE, $propertyName); + + $ruleErrors[] = RuleErrorBuilder::message($errorMessage) + ->file($filePath) + ->line($line) + ->tip(self::TIP_MESSAGE) + ->build(); + } + } + } + + return $ruleErrors; + } + + /** + * @param mixed[] $usedProperties + */ + private function isPropertyUsed(string $className, string $constantName, array $usedProperties): bool + { + $publicPropertyReference = $className . '::' . $constantName; + $usedProperties = Arrays::flatten($usedProperties); + + return in_array($publicPropertyReference, $usedProperties, true); + } +} diff --git a/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicStaticPropertyRule.php b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicStaticPropertyRule.php new file mode 100644 index 000000000..36af24bfe --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/Rules/UnusedPublicStaticPropertyRule.php @@ -0,0 +1,99 @@ +configuration = $configuration; + } + + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + /** + * @param CollectedDataNode $node + * @return RuleError[] + */ + public function processNode(Node $node, Scope $scope): array + { + if (! $this->configuration->isUnusedStaticPropertyEnabled()) { + return []; + } + + $staticStaticPropertyFetchCollector = $node->get(PublicStaticPropertyFetchCollector::class); + $publicStaticPropertyCollector = $node->get(PublicStaticPropertyCollector::class); + + $ruleErrors = []; + + foreach ($publicStaticPropertyCollector as $filePath => $declarationsGroups) { + foreach ($declarationsGroups as $declarationGroup) { + foreach ($declarationGroup as [$className, $propertyName, $line]) { + if ($this->isPublicStaticPropertyUsed( + $className, + $propertyName, + $staticStaticPropertyFetchCollector + )) { + continue; + } + + /** @var string $propertyName */ + $errorMessage = sprintf(self::ERROR_MESSAGE, $propertyName); + + $ruleErrors[] = RuleErrorBuilder::message($errorMessage) + ->file($filePath) + ->line($line) + ->tip(self::TIP_MESSAGE) + ->build(); + } + } + } + + return $ruleErrors; + } + + /** + * @param mixed[] $usedProperties + */ + private function isPublicStaticPropertyUsed(string $className, string $propertyName, array $usedProperties): bool + { + $publicPropertyReference = $className . '::' . $propertyName; + + $usedProperties = Arrays::flatten($usedProperties); + return in_array($publicPropertyReference, $usedProperties, true); + } +} diff --git a/vendor/tomasvotruba/unused-public/src/ValueObject/MethodCallReference.php b/vendor/tomasvotruba/unused-public/src/ValueObject/MethodCallReference.php new file mode 100644 index 000000000..f10c4f822 --- /dev/null +++ b/vendor/tomasvotruba/unused-public/src/ValueObject/MethodCallReference.php @@ -0,0 +1,34 @@ +class = $class; + $this->method = $method; + } + + public function getClass(): string + { + return $this->class; + } + + public function getMethod(): string + { + return $this->method; + } +}