Skip to content

Commit 0f8a352

Browse files
kamil-zacekondrejmirtes
authored andcommitted
Introduce ForbiddenClassNameExtension for append additional forbidden class prefixes
1 parent 200641e commit 0f8a352

File tree

47 files changed

+205
-48
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+205
-48
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Classes;
4+
5+
/**
6+
* This is the extension interface to implement if you want to dynamically
7+
* add forbidden class prefixes to the ClassForbiddenNameCheck rule.
8+
*
9+
* The idea is that you want to report usages of classes that you're not supposed to use in application.
10+
* For example: Generated Doctrine proxies from their configured namespace.
11+
*
12+
* To register it in the configuration file use the `phpstan.forbiddenClassNamesExtension` service tag:
13+
*
14+
* ```
15+
* services:
16+
* -
17+
* class: App\PHPStan\MyExtension
18+
* tags:
19+
* - phpstan.forbiddenClassNamesExtension
20+
* ```
21+
*
22+
* @api
23+
*/
24+
interface ForbiddenClassNameExtension
25+
{
26+
27+
public const EXTENSION_TAG = 'phpstan.forbiddenClassNamesExtension';
28+
29+
/** @return array<string, string> */
30+
public function getClassPrefixes(): array;
31+
32+
}

src/Rules/ClassForbiddenNameCheck.php

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

33
namespace PHPStan\Rules;
44

5+
use PHPStan\Classes\ForbiddenClassNameExtension;
6+
use PHPStan\DependencyInjection\Container;
7+
use function array_map;
8+
use function array_merge;
59
use function sprintf;
610
use function str_starts_with;
11+
use function strlen;
712
use function strpos;
813
use function substr;
914

@@ -16,22 +21,45 @@ class ClassForbiddenNameCheck
1621
'PHP-Scoper' => '_PhpScoper',
1722
];
1823

24+
public function __construct(private Container $container)
25+
{
26+
}
27+
1928
/**
2029
* @param ClassNameNodePair[] $pairs
2130
* @return RuleError[]
2231
*/
2332
public function checkClassNames(array $pairs): array
2433
{
34+
$extensions = $this->container->getServicesByTag(ForbiddenClassNameExtension::EXTENSION_TAG);
35+
36+
$classPrefixes = array_merge(
37+
self::INTERNAL_CLASS_PREFIXES,
38+
...array_map(
39+
static fn (ForbiddenClassNameExtension $extension): array => $extension->getClassPrefixes(),
40+
$extensions,
41+
),
42+
);
43+
2544
$errors = [];
2645
foreach ($pairs as $pair) {
2746
$className = $pair->getClassName();
2847

2948
$projectName = null;
30-
foreach (self::INTERNAL_CLASS_PREFIXES as $project => $prefix) {
31-
if (str_starts_with($className, $prefix)) {
32-
$projectName = $project;
33-
break;
49+
$withoutPrefixClassName = null;
50+
foreach ($classPrefixes as $project => $prefix) {
51+
if (!str_starts_with($className, $prefix)) {
52+
continue;
3453
}
54+
55+
$projectName = $project;
56+
$withoutPrefixClassName = substr($className, strlen($prefix));
57+
58+
if (strpos($withoutPrefixClassName, '\\') === false) {
59+
continue;
60+
}
61+
62+
$withoutPrefixClassName = substr($withoutPrefixClassName, strpos($withoutPrefixClassName, '\\'));
3563
}
3664

3765
if ($projectName === null) {
@@ -44,10 +72,10 @@ public function checkClassNames(array $pairs): array
4472
$className,
4573
))->line($pair->getNode()->getLine())->nonIgnorable();
4674

47-
if (strpos($className, '\\') !== false) {
75+
if ($withoutPrefixClassName !== null) {
4876
$error->tip(sprintf(
4977
'This is most likely unintentional. Did you mean to type %s?',
50-
substr($className, strpos($className, '\\')),
78+
$withoutPrefixClassName,
5179
));
5280
}
5381

tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected function getRule(): Rule
4242
),
4343
new ClassNameCheck(
4444
new ClassCaseSensitivityCheck($reflectionProvider, false),
45-
new ClassForbiddenNameCheck(),
45+
new ClassForbiddenNameCheck(self::getContainer()),
4646
),
4747
true,
4848
),

tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected function getRule(): Rule
4141
),
4242
new ClassNameCheck(
4343
new ClassCaseSensitivityCheck($reflectionProvider, false),
44-
new ClassForbiddenNameCheck(),
44+
new ClassForbiddenNameCheck(self::getContainer()),
4545
),
4646
true,
4747
),

tests/PHPStan/Rules/Classes/ClassConstantRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ protected function getRule(): Rule
2727
new RuleLevelHelper($reflectionProvider, true, false, true, false, false, true, false),
2828
new ClassNameCheck(
2929
new ClassCaseSensitivityCheck($reflectionProvider, true),
30-
new ClassForbiddenNameCheck(),
30+
new ClassForbiddenNameCheck(self::getContainer()),
3131
),
3232
new PhpVersion($this->phpVersion),
3333
);

tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protected function getRule(): Rule
2121
return new ExistingClassInClassExtendsRule(
2222
new ClassNameCheck(
2323
new ClassCaseSensitivityCheck($reflectionProvider, true),
24-
new ClassForbiddenNameCheck(),
24+
new ClassForbiddenNameCheck(self::getContainer()),
2525
),
2626
$reflectionProvider,
2727
);

tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protected function getRule(): Rule
2121
$reflectionProvider,
2222
new ClassNameCheck(
2323
new ClassCaseSensitivityCheck($reflectionProvider, true),
24-
new ClassForbiddenNameCheck(),
24+
new ClassForbiddenNameCheck(self::getContainer()),
2525
),
2626
true,
2727
);

tests/PHPStan/Rules/Classes/ExistingClassInTraitUseRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protected function getRule(): Rule
2121
return new ExistingClassInTraitUseRule(
2222
new ClassNameCheck(
2323
new ClassCaseSensitivityCheck($reflectionProvider, true),
24-
new ClassForbiddenNameCheck(),
24+
new ClassForbiddenNameCheck(self::getContainer()),
2525
),
2626
$reflectionProvider,
2727
);

tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protected function getRule(): Rule
2121
return new ExistingClassesInClassImplementsRule(
2222
new ClassNameCheck(
2323
new ClassCaseSensitivityCheck($reflectionProvider, true),
24-
new ClassForbiddenNameCheck(),
24+
new ClassForbiddenNameCheck(self::getContainer()),
2525
),
2626
$reflectionProvider,
2727
);

tests/PHPStan/Rules/Classes/ExistingClassesInEnumImplementsRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ protected function getRule(): Rule
2222
return new ExistingClassesInEnumImplementsRule(
2323
new ClassNameCheck(
2424
new ClassCaseSensitivityCheck($reflectionProvider, true),
25-
new ClassForbiddenNameCheck(),
25+
new ClassForbiddenNameCheck(self::getContainer()),
2626
),
2727
$reflectionProvider,
2828
);

0 commit comments

Comments
 (0)