Skip to content

Commit f74c087

Browse files
authored
Re-allow in class with attributes (#296)
Added new allow directives (aliases not listed): - `allowInClassWithAttributes` + `allowExceptInClassWithAttributes` - works for `DisallowedCall`, `DisallowedAttribute`, `DisallowedNamespace` (+ `allowInUse: true/false`) - `allowInMethodsWithAttributes` + `allowExceptInMethodsWithAttributes` (+ `...InFunctions...` aliases and a relevant discussion about phpstan/phpstan#12510) - `allowInClassWithMethodAttributes` + `allowExceptInClassWithMethodAttributes` Close #180
2 parents b388693 + 1d65ba3 commit f74c087

30 files changed

+1569
-218
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ Let's say you have disallowed `foo()` with custom rules. But you want to re-allo
5454
- [Allow in methods or functions](docs/allow-in-methods.md)
5555
- [Allow with specified parameters](docs/allow-with-parameters.md)
5656
- [Allow with specified flags](docs/allow-with-flags.md)
57+
- [Allow in class with given attributes](docs/allow-in-class-with-attributes.md)
58+
- [Allow in methods or functions with given attributes](docs/allow-in-methods.md)
59+
- [Allow in class with given attributes on any method](docs/allow-in-class-with-method-attributes.md)
5760

5861
[Re-allowing attributes](docs/allow-attributes.md) uses a similar [configuration](docs/allow-attributes.md).
5962

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
## Allow in class with given attributes
2+
3+
It is possible to allow a previously disallowed item when done in a class with specified attributes.
4+
You can use the `allowInClassWithAttributes` configuration option.
5+
6+
This is supported for the following items:
7+
- function calls
8+
- method calls
9+
- attribute usages
10+
- namespace usages
11+
- classname usages
12+
13+
For example, if you'd have a configuration like this:
14+
15+
```neon
16+
parameters:
17+
disallowedMethodCalls:
18+
-
19+
method: 'PotentiallyDangerous\Logger::log()'
20+
allowInClassWithAttributes:
21+
- MyAttribute
22+
```
23+
24+
Then the `log()` call would be allowed in a class that would look like this, note the attribute added on the class:
25+
26+
```php
27+
#[MyAttribute]
28+
class Foo
29+
{
30+
public function bar(): void
31+
{
32+
$this->dangerousLogger->log('something');
33+
}
34+
}
35+
```
36+
37+
On the other hand, if you need to disallow an item only when present in a method from a class with a given attribute,
38+
use `allowExceptInClassWithAttributes` (or the `disallowInClassWithAttributes` alias):
39+
40+
```neon
41+
parameters:
42+
disallowedMethodCalls:
43+
-
44+
method: 'PotentiallyDangerous\Logger::log()'
45+
allowExceptInClassWithAttributes:
46+
- SomeAttribute
47+
```
48+
49+
The `log()` method call would be allowed in the following class:
50+
51+
```php
52+
class Foo
53+
{
54+
public function bar(): void
55+
{
56+
$this->dangerousLogger->log('something');
57+
}
58+
}
59+
```
60+
61+
It would be disallowed in this class and in this class only because it has the `SomeAttribute` attribute:
62+
63+
```php
64+
#[SomeAttribute]
65+
class Foo
66+
{
67+
public function bar(): void
68+
{
69+
$this->dangerousLogger->log('something');
70+
}
71+
}
72+
```
73+
74+
The attribute names in the _allow_ directives support [fnmatch()](https://www.php.net/function.fnmatch) patterns, and only one needs to match if multiple are specified.
75+
76+
### Allow namespace or classname use in `use` imports
77+
78+
You can allow a namespace or a classname to be used in `use` imports with `allowInUse: true`.
79+
This can be useful when you want to disallow a namespace usage in a class with an attribute (with `allowExceptInClassWithAttributes` or `disallowInClassWithAttributes`),
80+
but don't want the error to be reported on line with the `use` statement.
81+
82+
Let's have a class like this:
83+
84+
```php
85+
use Foo\Bar\DisallowedClass; // line 1
86+
87+
#[MyAttribute]
88+
class Waldo
89+
{
90+
91+
public function fred(DisallowedClass $param) // line 7
92+
{
93+
}
94+
95+
}
96+
```
97+
98+
Then with a configuration like this:
99+
100+
```neon
101+
parameters:
102+
disallowedNamespace:
103+
-
104+
namespace: 'Foo\Bar\DisallowedClass'
105+
allowExceptInClassWithAttributes:
106+
- MyAttribute
107+
```
108+
109+
the error would be reported both on line 1, because `use Foo\Bar\DisallowedClass;` uses a disallowed namespace, and on line 7 because `$param` has the disallowed type.
110+
But maybe you'd expect the error to be reported only on line 7, because _that_ is a disallowed class used in a class with the `MyAttribute` attribute.
111+
112+
To omit the `use` finding, you can add the `allowInUse` line, like this:
113+
114+
```neon
115+
parameters:
116+
disallowedNamespace:
117+
-
118+
namespace: 'Foo\Bar\DisallowedClass'
119+
allowExceptInClassWithAttributes:
120+
- MyAttribute
121+
allowInUse: true
122+
```
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Allow in class with given attributes on any method
2+
3+
You can allow or disallow a function or a method, an attribute, a namespace or a classname in a class where any method, including the method the call is done in, but not the class itself, has the specified attribute.
4+
This is done with `allowInClassWithMethodAttributes` and `allowExceptInClassWithMethodAttributes` (or `disallowInClassWithMethodAttributes` which is an alias).
5+
6+
```neon
7+
parameters:
8+
disallowedNamespace:
9+
-
10+
class: 'Foo\Bar\DisallowedClass'
11+
allowInClassWithMethodAttributes:
12+
- MyAttribute
13+
```
14+
15+
Given the configuration above, no error would be reported in the following source code even though `MyAttribute` is not on the method the DisallowedClass is used in, but on some other method:
16+
17+
```php
18+
class Waldo
19+
{
20+
21+
public function function1(): void
22+
{
23+
Foo\Bar\DisallowedClass::method();
24+
}
25+
26+
27+
#[MyAttribute]
28+
private function checkThis(): void
29+
{
30+
}
31+
32+
}
33+
```
34+
35+
The attribute names support [fnmatch()](https://www.php.net/function.fnmatch) patterns, and only one needs to match if multiple are specified.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Allow in methods or functions with given attributes
2+
3+
Similar to [allowing items in methods or functions by name](allow-in-methods.md), you can allow or disallow items like functions and method calls, attributes, namespace and classname usage in methods and functions with given attributes.
4+
5+
You can use `allowInMethodsWithAttributes` (or the `allowInFunctionsWithAttributes` alias) for that:
6+
7+
```neon
8+
parameters:
9+
disallowedMethodCalls:
10+
-
11+
method: 'PotentiallyDangerous\Logger::log()'
12+
allowInMethodsWithAttributes:
13+
- MyAttribute
14+
```
15+
16+
And vice versa, if you need to disallow an item in a method or a function with given attribute, use `allowExceptInMethodsWithAttributes` (with aliases `allowExceptInFunctionsWithAttributes`, `disallowInMethodsWithAttributes`, `disallowInFunctionsWithAttributes`):
17+
18+
```neon
19+
parameters:
20+
disallowedMethodCalls:
21+
-
22+
method: 'Controller::redirect()'
23+
disallowInFunctionsWithAttributes:
24+
- YourAttribute
25+
```
26+
27+
The attribute names support [fnmatch()](https://www.php.net/function.fnmatch) patterns. If you specify multiple attributes, the method or the function in which the item should be allowed or disallowed, needs to have just one of them.

extension.neon

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ parametersSchema:
2424
?allowIn: listOf(string()),
2525
?allowExceptIn: listOf(string()),
2626
?disallowIn: listOf(string()),
27+
?allowInClassWithAttributes: listOf(string()),
28+
?allowExceptInClassWithAttributes: listOf(string()),
29+
?disallowInClassWithAttributes: listOf(string()),
30+
?allowInFunctionsWithAttributes: listOf(string()),
31+
?allowInMethodsWithAttributes: listOf(string()),
32+
?allowExceptInFunctionsWithAttributes: listOf(string()),
33+
?allowExceptInMethodsWithAttributes: listOf(string()),
34+
?disallowInFunctionsWithAttributes: listOf(string()),
35+
?disallowInMethodsWithAttributes: listOf(string()),
36+
?allowInClassWithMethodAttributes: listOf(string()),
37+
?allowExceptInClassWithMethodAttributes: listOf(string()),
38+
?disallowInClassWithMethodAttributes: listOf(string()),
39+
?allowInUse: bool(),
2740
?errorIdentifier: string(),
2841
?errorTip: string(),
2942
])
@@ -37,6 +50,19 @@ parametersSchema:
3750
?allowIn: listOf(string()),
3851
?allowExceptIn: listOf(string()),
3952
?disallowIn: listOf(string()),
53+
?allowInClassWithAttributes: listOf(string()),
54+
?allowExceptInClassWithAttributes: listOf(string()),
55+
?disallowInClassWithAttributes: listOf(string()),
56+
?allowInFunctionsWithAttributes: listOf(string()),
57+
?allowInMethodsWithAttributes: listOf(string()),
58+
?allowExceptInFunctionsWithAttributes: listOf(string()),
59+
?allowExceptInMethodsWithAttributes: listOf(string()),
60+
?disallowInFunctionsWithAttributes: listOf(string()),
61+
?disallowInMethodsWithAttributes: listOf(string()),
62+
?allowInClassWithMethodAttributes: listOf(string()),
63+
?allowExceptInClassWithMethodAttributes: listOf(string()),
64+
?disallowInClassWithMethodAttributes: listOf(string()),
65+
?allowInUse: bool(),
4066
?errorIdentifier: string(),
4167
?errorTip: string(),
4268
])
@@ -75,6 +101,18 @@ parametersSchema:
75101
?disallowParamFlags: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
76102
?allowExceptCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
77103
?disallowCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
104+
?allowInClassWithAttributes: listOf(string()),
105+
?allowExceptInClassWithAttributes: listOf(string()),
106+
?disallowInClassWithAttributes: listOf(string()),
107+
?allowInFunctionsWithAttributes: listOf(string()),
108+
?allowInMethodsWithAttributes: listOf(string()),
109+
?allowExceptInFunctionsWithAttributes: listOf(string()),
110+
?allowExceptInMethodsWithAttributes: listOf(string()),
111+
?disallowInFunctionsWithAttributes: listOf(string()),
112+
?disallowInMethodsWithAttributes: listOf(string()),
113+
?allowInClassWithMethodAttributes: listOf(string()),
114+
?allowExceptInClassWithMethodAttributes: listOf(string()),
115+
?disallowInClassWithMethodAttributes: listOf(string()),
78116
?errorIdentifier: string(),
79117
?errorTip: string(),
80118
])
@@ -113,6 +151,18 @@ parametersSchema:
113151
?disallowParamFlags: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
114152
?allowExceptCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
115153
?disallowCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
154+
?allowInClassWithAttributes: listOf(string()),
155+
?allowExceptInClassWithAttributes: listOf(string()),
156+
?disallowInClassWithAttributes: listOf(string()),
157+
?allowInFunctionsWithAttributes: listOf(string()),
158+
?allowInMethodsWithAttributes: listOf(string()),
159+
?allowExceptInFunctionsWithAttributes: listOf(string()),
160+
?allowExceptInMethodsWithAttributes: listOf(string()),
161+
?disallowInFunctionsWithAttributes: listOf(string()),
162+
?disallowInMethodsWithAttributes: listOf(string()),
163+
?allowInClassWithMethodAttributes: listOf(string()),
164+
?allowExceptInClassWithMethodAttributes: listOf(string()),
165+
?disallowInClassWithMethodAttributes: listOf(string()),
116166
?errorIdentifier: string(),
117167
?errorTip: string(),
118168
])
@@ -151,6 +201,18 @@ parametersSchema:
151201
?disallowParamFlags: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
152202
?allowExceptCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
153203
?disallowCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
204+
?allowInClassWithAttributes: listOf(string()),
205+
?allowExceptInClassWithAttributes: listOf(string()),
206+
?disallowInClassWithAttributes: listOf(string()),
207+
?allowInFunctionsWithAttributes: listOf(string()),
208+
?allowInMethodsWithAttributes: listOf(string()),
209+
?allowExceptInFunctionsWithAttributes: listOf(string()),
210+
?allowExceptInMethodsWithAttributes: listOf(string()),
211+
?disallowInFunctionsWithAttributes: listOf(string()),
212+
?disallowInMethodsWithAttributes: listOf(string()),
213+
?allowInClassWithMethodAttributes: listOf(string()),
214+
?allowExceptInClassWithMethodAttributes: listOf(string()),
215+
?disallowInClassWithMethodAttributes: listOf(string()),
154216
?errorIdentifier: string(),
155217
?errorTip: string(),
156218
])
@@ -221,6 +283,18 @@ parametersSchema:
221283
?disallowParamFlags: arrayOf(anyOf(int(), structure([position: int(), ?value: int(), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
222284
?allowExceptCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
223285
?disallowCaseInsensitiveParams: arrayOf(anyOf(int(), string(), bool(), structure([position: int(), ?value: anyOf(int(), string(), bool()), ?typeString: string(), ?name: string()])), anyOf(int(), string())),
286+
?allowInClassWithAttributes: listOf(string()),
287+
?allowExceptInClassWithAttributes: listOf(string()),
288+
?disallowInClassWithAttributes: listOf(string()),
289+
?allowInFunctionsWithAttributes: listOf(string()),
290+
?allowInMethodsWithAttributes: listOf(string()),
291+
?allowExceptInFunctionsWithAttributes: listOf(string()),
292+
?allowExceptInMethodsWithAttributes: listOf(string()),
293+
?disallowInFunctionsWithAttributes: listOf(string()),
294+
?disallowInMethodsWithAttributes: listOf(string()),
295+
?allowInClassWithMethodAttributes: listOf(string()),
296+
?allowExceptInClassWithMethodAttributes: listOf(string()),
297+
?disallowInClassWithMethodAttributes: listOf(string()),
224298
?errorIdentifier: string(),
225299
?errorTip: string(),
226300
])
@@ -240,6 +314,7 @@ parametersSchema:
240314

241315
services:
242316
- Spaze\PHPStan\Rules\Disallowed\Allowed\Allowed
317+
- Spaze\PHPStan\Rules\Disallowed\Allowed\AllowedConfigFactory
243318
- Spaze\PHPStan\Rules\Disallowed\Allowed\AllowedPath
244319
- Spaze\PHPStan\Rules\Disallowed\DisallowedAttributeFactory
245320
- Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory
@@ -261,6 +336,7 @@ services:
261336
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedCallsRuleErrors
262337
- Spaze\PHPStan\Rules\Disallowed\RuleErrors\DisallowedVariableRuleErrors
263338
- Spaze\PHPStan\Rules\Disallowed\Type\TypeResolver
339+
- Spaze\PHPStan\Rules\Disallowed\Usages\NamespaceUsageFactory
264340
-
265341
factory: Spaze\PHPStan\Rules\Disallowed\Usages\NamespaceUsages(forbiddenNamespaces: %disallowedNamespaces%)
266342
tags:

phpstan.neon

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ parameters:
66
CallParamConfig: 'array<int|string, int|bool|string|array{position:int, value?:int|bool|string, name?:string}>'
77
CallParamAnyValueConfig: 'array<int|string, int|array{position:int, value?:int|bool|string, name?:string}>'
88
CallParamFlagAnyValueConfig: 'array<int|string, int|array{position:int, value?:int, name?:string}>'
9-
AllowDirectives: 'allowIn?:list<string>, allowExceptIn?:list<string>, disallowIn?:list<string>, allowInFunctions?:list<string>, allowInMethods?:list<string>, allowExceptInFunctions?:list<string>, allowExceptInMethods?:list<string>, disallowInFunctions?:list<string>, disallowInMethods?:list<string>, allowParamsInAllowed?:CallParamConfig, allowParamsInAllowedAnyValue?:CallParamAnyValueConfig, allowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, allowParamsAnywhere?:CallParamConfig, allowParamsAnywhereAnyValue?:CallParamAnyValueConfig, allowParamFlagsAnywhere?:CallParamFlagAnyValueConfig, allowExceptParamsInAllowed?:CallParamConfig, allowExceptParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamsInAllowed?:CallParamConfig, allowExceptParams?:CallParamConfig, disallowParams?:CallParamConfig, allowExceptParamsAnyValue?:CallParamAnyValueConfig, disallowParamsAnyValue?:CallParamAnyValueConfig, allowExceptParamFlags?:CallParamFlagAnyValueConfig, disallowParamFlags?:CallParamFlagAnyValueConfig, allowExceptCaseInsensitiveParams?:CallParamConfig, disallowCaseInsensitiveParams?:CallParamConfig'
9+
AllowParamDirectives: 'allowParamsInAllowed?:CallParamConfig, allowParamsInAllowedAnyValue?:CallParamAnyValueConfig, allowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, allowParamsAnywhere?:CallParamConfig, allowParamsAnywhereAnyValue?:CallParamAnyValueConfig, allowParamFlagsAnywhere?:CallParamFlagAnyValueConfig, allowExceptParamsInAllowed?:CallParamConfig, allowExceptParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamFlagsInAllowed?:CallParamFlagAnyValueConfig, disallowParamsInAllowed?:CallParamConfig, allowExceptParams?:CallParamConfig, disallowParams?:CallParamConfig, allowExceptParamsAnyValue?:CallParamAnyValueConfig, disallowParamsAnyValue?:CallParamAnyValueConfig, allowExceptParamFlags?:CallParamFlagAnyValueConfig, disallowParamFlags?:CallParamFlagAnyValueConfig, allowExceptCaseInsensitiveParams?:CallParamConfig, disallowCaseInsensitiveParams?:CallParamConfig'
10+
AllowAttributesDirectives: 'allowInClassWithAttributes?:list<string>, allowExceptInClassWithAttributes?:list<string>, disallowInClassWithAttributes?:list<string>, allowInFunctionsWithAttributes?:list<string>, allowInMethodsWithAttributes?:list<string>, allowExceptInFunctionsWithAttributes?:list<string>, allowExceptInMethodsWithAttributes?:list<string>, disallowInFunctionsWithAttributes?:list<string>, disallowInMethodsWithAttributes?:list<string>, allowInClassWithMethodAttributes?:list<string>, allowExceptInClassWithMethodAttributes?:list<string>, disallowInClassWithMethodAttributes?:list<string>'
11+
AllowDirectives: 'allowIn?:list<string>, allowExceptIn?:list<string>, disallowIn?:list<string>, allowInFunctions?:list<string>, allowInMethods?:list<string>, allowExceptInFunctions?:list<string>, allowExceptInMethods?:list<string>, disallowInFunctions?:list<string>, disallowInMethods?:list<string>, %typeAliases.AllowParamDirectives%, %typeAliases.AllowAttributesDirectives%'
1012
ForbiddenCallsConfig: 'array<array{function?:string|list<string>, method?:string|list<string>, exclude?:string|list<string>, definedIn?:string|list<string>, message?:string, %typeAliases.AllowDirectives%, errorIdentifier?:string, errorTip?:string}>'
1113
DisallowedAttributesConfig: 'array<array{attribute:string|list<string>, exclude?:string|list<string>, message?:string, %typeAliases.AllowDirectives%, errorIdentifier?:string, errorTip?:string}>'
1214
AllowDirectivesConfig: 'array{%typeAliases.AllowDirectives%}'

0 commit comments

Comments
 (0)