Skip to content

Commit 921226b

Browse files
Adding Signature and Return Type checking Rules (#4)
* Adding rules to check return type and method signature * Adding a rule for naming methods that return booleans
1 parent 2ea6ba5 commit 921226b

15 files changed

+897
-32
lines changed

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Additional rules for PHPStan, mostly focused on Clean Code and architecture conventions.
44

5+
The rules help with enforcing certain method signatures, return types and dependency constraints in your codebase.
6+
7+
All controllers in your application should be `readonly`, no method should have more than 3 arguments, and no class should have more than 2 nested control structures.
8+
59
## Usage
610

711
```bash
@@ -12,13 +16,16 @@ composer require phauthentic/phpstan-rules --dev
1216

1317
See [Rules documentation](docs/Rules.md) for a list of available rules and configuration examples.
1418

15-
**Available Rules:**
16-
- [Control Structure Nesting Rule](docs/Rules.md#control-structure-nesting-rule)
17-
- [Too Many Arguments Rule](docs/Rules.md#too-many-arguments-rule)
18-
- [Readonly Class Rule](docs/Rules.md#readonly-class-rule)
19-
- [Dependency Constraints Rule](docs/Rules.md#dependency-constraints-rule)
20-
- [Final Class Rule](docs/Rules.md#final-class-rule)
21-
- [Namespace Class Pattern Rule](docs/Rules.md#namespace-class-pattern-rule)
19+
- Architecture Rules:
20+
- [Dependency Constraints Rule](docs/Rules.md#dependency-constraints-rule)
21+
- [Readonly Class Rule](docs/Rules.md#readonly-class-rule)
22+
- [Final Class Rule](docs/Rules.md#final-class-rule)
23+
- [Namespace Class Pattern Rule](docs/Rules.md#namespace-class-pattern-rule)
24+
- [Method Signature Must Match Rule](docs/Rules.md#method-signature-must-match-rule)
25+
- [Method Must Return Type Rule](docs/Rules.md#method-must-return-type-rule)
26+
- Clean Code Rules:
27+
- [Control Structure Nesting Rule](docs/Rules.md#control-structure-nesting-rule)
28+
- [Too Many Arguments Rule](docs/Rules.md#too-many-arguments-rule)
2229

2330
### Using Regex in Rules
2431

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
class ReturnTypeTestClass
4+
{
5+
public function mustReturnInt(): void {}
6+
public function mustReturnNullableString(): string {}
7+
public function mustReturnVoid(): int {}
8+
public function mustReturnSpecificObject(): OtherObject {}
9+
}
10+
11+
class SomeObject {}
12+
class OtherObject {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
class TestClass
4+
{
5+
public function testMethod(int $a)
6+
{
7+
}
8+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
class TestClass
4+
{
5+
public function flag(): bool
6+
{
7+
return true;
8+
}
9+
10+
public function isActive(): bool
11+
{
12+
return true;
13+
}
14+
15+
public function hasPermission(): bool
16+
{
17+
return false;
18+
}
19+
}

docs/Rules.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Rules
32

43
Add them to your `phpstan.neon` configuration file under the section `services`.
@@ -99,3 +98,68 @@ Ensures that classes inside namespaces matching a given regex must have names ma
9998
tags:
10099
- phpstan.rules.rule
101100
```
101+
102+
## Method Signature Must Match Rule
103+
104+
Ensures that methods matching a class and method name pattern have a specific signature, including parameter types, names, and count.
105+
106+
**Configuration Example:**
107+
```neon
108+
-
109+
class: Phauthentic\PhpstanRules\Architecture\MethodSignatureMustMatchRule
110+
arguments:
111+
signaturePatterns:
112+
-
113+
pattern: '/^MyClass::myMethod$/'
114+
minParameters: 2
115+
maxParameters: 2
116+
signature:
117+
-
118+
type: 'int'
119+
pattern: '/^id$/'
120+
-
121+
type: 'string'
122+
pattern: '/^name$/'
123+
tags:
124+
- phpstan.rules.rule
125+
```
126+
- `pattern`: Regex for `ClassName::methodName`.
127+
- `minParameters`/`maxParameters`: Minimum/maximum number of parameters.
128+
- `signature`: List of expected parameter types and (optionally) name patterns.
129+
130+
## Method Must Return Type Rule
131+
132+
Ensures that methods matching a class and method name pattern have a specific return type, nullability, or are void.
133+
134+
**Configuration Example:**
135+
```neon
136+
-
137+
class: Phauthentic\PhpstanRules\Architecture\MethodMustReturnTypeRule
138+
arguments:
139+
returnTypePatterns:
140+
-
141+
pattern: '/^MyClass::getId$/'
142+
type: 'int'
143+
nullable: false
144+
void: false
145+
objectTypePattern: null
146+
-
147+
pattern: '/^MyClass::findUser$/'
148+
type: 'object'
149+
nullable: true
150+
void: false
151+
objectTypePattern: '/^App\\\\Entity\\\\User$/'
152+
-
153+
pattern: '/^MyClass::reset$/'
154+
type: 'void'
155+
nullable: false
156+
void: true
157+
objectTypePattern: null
158+
tags:
159+
- phpstan.rules.rule
160+
```
161+
- `pattern`: Regex for `ClassName::methodName`.
162+
- `type`: Expected return type (`int`, `string`, `object`, etc.).
163+
- `nullable`: Whether the return type must be nullable.
164+
- `void`: Whether the method must return void.
165+
- `objectTypePattern`: Regex for object return types (if `type` is `object`).

src/Architecture/ClassMustBeFinalRule.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
use PHPStan\ShouldNotHappenException;
1313

1414
/**
15+
* Specification:
16+
* - Checks if a class matches a given regex pattern.
17+
* - Checks if the class is declared as final.
18+
*
1519
* @implements Rule<Class_>
1620
*/
1721
class ClassMustBeFinalRule implements Rule
@@ -20,19 +24,13 @@ class ClassMustBeFinalRule implements Rule
2024

2125
private const IDENTIFIER = 'phauthentic.architecture.classMustBeFinal';
2226

23-
/**
24-
* @var array<string> An array of regex patterns to match against class names.
25-
* e.g., ['#^App\\Domain\\.*#', '#^App\\Service\\.*#']
26-
*/
27-
protected array $patterns;
28-
2927
/**
3028
* @param array<string> $patterns An array of regex patterns to match against class names.
3129
* Each pattern should be a valid PCRE regex.
3230
*/
33-
public function __construct(array $patterns)
34-
{
35-
$this->patterns = $patterns;
31+
public function __construct(
32+
protected array $patterns
33+
) {
3634
}
3735

3836
public function getNodeType(): string
@@ -55,14 +53,17 @@ public function processNode(Node $node, Scope $scope): array
5553

5654
foreach ($this->patterns as $pattern) {
5755
if (preg_match($pattern, $fullClassName) && !$node->isFinal()) {
58-
return [
59-
RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $fullClassName))
60-
->identifier(self::IDENTIFIER)
61-
->build(),
62-
];
56+
return [$this->buildRuleError($fullClassName)];
6357
}
6458
}
6559

6660
return [];
6761
}
62+
63+
private function buildRuleError(string $fullClassName)
64+
{
65+
return RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $fullClassName))
66+
->identifier(self::IDENTIFIER)
67+
->build();
68+
}
6869
}

src/Architecture/ClassMustBeReadonlyRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use PHPStan\Rules\RuleErrorBuilder;
1212

1313
/**
14+
* Specification:
15+
* - A matching class must be declared as readonly.
1416
* @implements Rule<Class_>
1517
*/
1618
class ClassMustBeReadonlyRule implements Rule

src/Architecture/ClassnameMustMatchPatternRule.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\ShouldNotHappenException;
1414

1515
/**
16+
* Specification:
1617
* PHPStan rule to ensure that classes inside namespaces matching a given regex
1718
* must have names matching at least one of the provided patterns.
1819
*
@@ -24,17 +25,12 @@ class ClassnameMustMatchPatternRule implements Rule
2425

2526
private const IDENTIFIER = 'phauthentic.architecture.classnameMustMatchPattern';
2627

27-
/**
28-
* @var array{namespace: string, classPatterns: string[]}[]
29-
*/
30-
private array $namespaceClassPatterns;
31-
3228
/**
3329
* @param array{namespace: string, classPatterns: string[]}[] $namespaceClassPatterns
3430
*/
35-
public function __construct(array $namespaceClassPatterns)
36-
{
37-
$this->namespaceClassPatterns = $namespaceClassPatterns;
31+
public function __construct(
32+
protected array $namespaceClassPatterns
33+
) {
3834
}
3935

4036
public function getNodeType(): string
@@ -124,8 +120,8 @@ public function buildRuleError(
124120
): array {
125121
$fqcn = $namespaceName ? $namespaceName . '\\' . $className : $className;
126122
$errors[] = RuleErrorBuilder::message(
127-
$this->buildErrorMessage($fqcn, $namespaceName, $classPatterns)
128-
)
123+
$this->buildErrorMessage($fqcn, $namespaceName, $classPatterns)
124+
)
129125
->line($stmt->getLine())
130126
->identifier(self::IDENTIFIER)
131127
->build();

src/Architecture/DependencyConstraintsRule.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
* This rule checks the `use` statements in your PHP code and ensures that
1919
* certain namespaces do not depend on other namespaces as specified in the
2020
* configuration.
21+
*
22+
* Specification:
23+
* - A class in a namespace matching a given regex is not allowed to depend on any namespace defined by a set of
24+
* other regexes.
2125
*/
2226
class DependencyConstraintsRule implements Rule
2327
{

0 commit comments

Comments
 (0)