Skip to content

Commit 0f70b68

Browse files
authored
Support defaultIndexMethod on AutowireLocator/AutowireIterator attribute (#161)
1 parent 7cf11e2 commit 0f70b68

File tree

6 files changed

+316
-3
lines changed

6 files changed

+316
-3
lines changed

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"phpstan/phpstan": "^2.1.7"
1717
},
1818
"require-dev": {
19+
"composer-runtime-api": "^2.0",
20+
"composer/semver": "^3.4",
1921
"doctrine/orm": "^2.19 || ^3.0",
2022
"editorconfig-checker/editorconfig-checker": "^10.6.0",
2123
"ergebnis/composer-normalize": "^2.45.0",
@@ -32,6 +34,7 @@
3234
"shipmonk/phpstan-rules": "^4.1.0",
3335
"slevomat/coding-standard": "^8.16.0",
3436
"symfony/contracts": "^2.5 || ^3.0",
37+
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
3538
"symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
3639
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
3740
"symfony/routing": "^5.4 || ^6.0 || ^7.0"

composer.lock

Lines changed: 165 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Provider/SymfonyUsageProvider.php

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
1515
use PHPStan\DependencyInjection\Container;
1616
use PHPStan\DependencyInjection\ParameterNotFoundException;
17+
use PHPStan\Node\InClassMethodNode;
1718
use PHPStan\Node\InClassNode;
1819
use PHPStan\Reflection\ClassReflection;
1920
use PHPStan\Reflection\ExtendedMethodReflection;
@@ -26,6 +27,7 @@
2627
use ShipMonk\PHPStan\DeadCode\Graph\ClassConstantUsage;
2728
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodRef;
2829
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodUsage;
30+
use ShipMonk\PHPStan\DeadCode\Graph\UsageOriginDetector;
2931
use SimpleXMLElement;
3032
use SplFileInfo;
3133
use UnexpectedValueException;
@@ -62,12 +64,16 @@ class SymfonyUsageProvider implements MemberUsageProvider
6264
*/
6365
private array $dicConstants = [];
6466

67+
private UsageOriginDetector $usageOriginDetector;
68+
6569
public function __construct(
6670
Container $container,
71+
UsageOriginDetector $usageOriginDetector,
6772
?bool $enabled,
6873
?string $configDir
6974
)
7075
{
76+
$this->usageOriginDetector = $usageOriginDetector;
7177
$this->enabled = $enabled ?? $this->isSymfonyInstalled();
7278
$resolvedConfigDir = $configDir ?? $this->autodetectConfigDir();
7379
$containerXmlPath = $this->getContainerXmlPath($container);
@@ -97,6 +103,13 @@ public function getUsages(Node $node, Scope $scope): array
97103
];
98104
}
99105

106+
if ($node instanceof InClassMethodNode) { // @phpstan-ignore phpstanApi.instanceofAssumption
107+
$usages = [
108+
...$usages,
109+
...$this->getMethodUsagesFromAttributeReflection($node, $scope),
110+
];
111+
}
112+
100113
if ($node instanceof Return_) {
101114
$usages = [
102115
...$usages,
@@ -216,6 +229,75 @@ private function getMethodUsagesFromReflection(InClassNode $node): array
216229
return $usages;
217230
}
218231

232+
/**
233+
* @return list<ClassMethodUsage>
234+
*/
235+
private function getMethodUsagesFromAttributeReflection(InClassMethodNode $node, Scope $scope): array
236+
{
237+
$usages = [];
238+
239+
foreach ($node->getMethodReflection()->getParameters() as $parameter) {
240+
foreach ($parameter->getAttributes() as $attributeReflection) {
241+
if ($attributeReflection->getName() === 'Symfony\Component\DependencyInjection\Attribute\AutowireLocator') {
242+
$arguments = $attributeReflection->getArgumentTypes();
243+
244+
if (!isset($arguments['services']) || !isset($arguments['defaultIndexMethod'])) {
245+
continue;
246+
}
247+
248+
if ($arguments['services']->isArray()->yes()) {
249+
$classNames = $arguments['services']->getIterableValueType()->getConstantStrings();
250+
} else {
251+
$classNames = $arguments['services']->getConstantStrings();
252+
}
253+
254+
$defaultIndexMethod = $arguments['defaultIndexMethod']->getConstantStrings();
255+
256+
if ($classNames === [] || !isset($defaultIndexMethod[0])) {
257+
continue;
258+
}
259+
260+
foreach ($classNames as $className) {
261+
$usages[] = new ClassMethodUsage(
262+
$this->usageOriginDetector->detectOrigin($scope),
263+
new ClassMethodRef(
264+
$className->getValue(),
265+
$defaultIndexMethod[0]->getValue(),
266+
true,
267+
),
268+
);
269+
}
270+
} elseif ($attributeReflection->getName() === 'Symfony\Component\DependencyInjection\Attribute\AutowireIterator') {
271+
$arguments = $attributeReflection->getArgumentTypes();
272+
273+
if (!isset($arguments['tag']) || !isset($arguments['defaultIndexMethod'])) {
274+
continue;
275+
}
276+
277+
$classNames = $arguments['tag']->getConstantStrings();
278+
$defaultIndexMethod = $arguments['defaultIndexMethod']->getConstantStrings();
279+
280+
if ($classNames === [] || !isset($defaultIndexMethod[0])) {
281+
continue;
282+
}
283+
284+
foreach ($classNames as $className) {
285+
$usages[] = new ClassMethodUsage(
286+
$this->usageOriginDetector->detectOrigin($scope),
287+
new ClassMethodRef(
288+
$className->getValue(),
289+
$defaultIndexMethod[0]->getValue(),
290+
true,
291+
),
292+
);
293+
}
294+
}
295+
}
296+
}
297+
298+
return $usages;
299+
}
300+
219301
protected function shouldMarkAsUsed(ReflectionMethod $method): bool
220302
{
221303
return $this->isBundleConstructor($method)
@@ -395,7 +477,8 @@ private function isSymfonyInstalled(): bool
395477
|| InstalledVersions::isInstalled('symfony/routing')
396478
|| InstalledVersions::isInstalled('symfony/contracts')
397479
|| InstalledVersions::isInstalled('symfony/console')
398-
|| InstalledVersions::isInstalled('symfony/http-kernel');
480+
|| InstalledVersions::isInstalled('symfony/http-kernel')
481+
|| InstalledVersions::isInstalled('symfony/dependency-injection');
399482
}
400483

401484
private function createUsage(ExtendedMethodReflection $methodReflection): ClassMethodUsage

tests/Rule/DeadCodeRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace ShipMonk\PHPStan\DeadCode\Rule;
44

5+
use Composer\InstalledVersions;
6+
use Composer\Semver\VersionParser;
57
use PhpParser\Node;
68
use PHPStan\Analyser\Error;
79
use PHPStan\Analyser\Scope;
@@ -391,6 +393,11 @@ public static function provideFiles(): iterable
391393
yield 'provider-vendor' => [__DIR__ . '/data/providers/vendor.php'];
392394
yield 'provider-reflection' => [__DIR__ . '/data/providers/reflection.php', 8_01_00];
393395
yield 'provider-symfony' => [__DIR__ . '/data/providers/symfony.php', 8_00_00];
396+
397+
if (InstalledVersions::satisfies(new VersionParser(), 'symfony/dependency-injection', '^7.1')) {
398+
yield 'provider-symfony-7.1' => [__DIR__ . '/data/providers/symfony-gte71.php', 8_00_00];
399+
}
400+
394401
yield 'provider-phpunit' => [__DIR__ . '/data/providers/phpunit.php', 8_00_00];
395402
yield 'provider-doctrine' => [__DIR__ . '/data/providers/doctrine.php', 8_00_00];
396403
yield 'provider-phpstan' => [__DIR__ . '/data/providers/phpstan.php'];
@@ -478,6 +485,7 @@ public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
478485
),
479486
new SymfonyUsageProvider(
480487
$this->createContainerMockWithSymfonyConfig(),
488+
new UsageOriginDetector(),
481489
true,
482490
__DIR__ . '/data/providers/symfony/',
483491
),

0 commit comments

Comments
 (0)