1.9.0 New rules, increased test coverage and bug fixes
π New Features
β¨ New Rule: Property Must Match Rule
Enforce Property Conventions on Classes
- Class:
Phauthentic\PHPStanRules\Architecture\PropertyMustMatchRule - Purpose: Validates that classes matching specified patterns have properties with expected names, types, and visibility scopes
- Key Features:
- Required Properties: Enforce that matching classes must have certain properties
- Type Validation: Check property types including support for union types (
int|string) and intersection types (Foo&Bar) - Visibility Enforcement: Ensure properties use the correct visibility (
public,protected,private) - Nullable Support: Optionally accept both a type and its nullable variant (e.g.,
intand?int) - Flexible Matching: Uses regex patterns to target specific classes
- Configuration: Supports
propertyPatternsparameter withclassPatternandpropertiessub-configuration - Error Messages:
Class {class} must have property ${name}.(when required property is missing)Property {class}::${name} should be of type {expected}, {actual} given.(type mismatch)Property {class}::${name} must be {visibility}.(visibility mismatch)
Configuration Example:
services:
-
class: Phauthentic\PHPStanRules\Architecture\PropertyMustMatchRule
arguments:
propertyPatterns:
-
classPattern: '/^App\\Entity\\.*$/'
properties:
-
name: 'id'
type: 'int'
visibilityScope: 'private'
required: true
-
name: 'createdAt'
type: 'DateTimeImmutable'
visibilityScope: 'private'
required: true
nullable: true
tags:
- phpstan.rules.rule⨠New Rule: Forbidden Accessors Rule
Enforce Encapsulation and the "Tell, Don't Ask" Principle
- Class:
Phauthentic\PHPStanRules\Architecture\ForbiddenAccessorsRule - Purpose: Forbids public and/or protected getters (
getXxx()) and setters (setXxx()) on classes matching specified patterns - Key Features:
- Configurable Accessor Types: Independently control whether getters, setters, or both are forbidden
- Visibility Control: Choose which visibility levels to check (
public,protected) - Pattern Matching: Uses regex patterns against fully qualified class names
- Anonymous Class Safe: Properly skips anonymous classes
- Configuration: Supports
classPatterns,forbidGetters,forbidSetters, andvisibilityparameters - Error Messages:
Class {class} must not have a {visibility} getter method {method}().Class {class} must not have a {visibility} setter method {method}().
Configuration Example:
services:
-
class: Phauthentic\PHPStanRules\Architecture\ForbiddenAccessorsRule
arguments:
classPatterns:
- '/^App\\Domain\\.*Entity$/'
- '/^App\\Domain\\.*ValueObject$/'
forbidGetters: true
forbidSetters: true
visibility:
- public
tags:
- phpstan.rules.ruleUse Cases:
- Enforce immutability on domain entities by forbidding setters
- Apply the "Tell, Don't Ask" principle by forbidding both getters and setters
- Restrict only public accessors while allowing protected ones for inheritance
β¨ New Rule: Forbidden Static Methods Rule
Prevent Static Method Calls
- Class:
Phauthentic\PHPStanRules\Architecture\ForbiddenStaticMethodsRule - Purpose: Forbids specific static method calls matching regex patterns to encourage dependency injection and testability
- Key Features:
- Multi-Level Granularity: Supports namespace-level, class-level, and method-level patterns
- Keyword Resolution: Resolves
self,static, andparentkeywords to actual class names before matching - Dynamic Call Safe: Skips dynamic class names (
$class::method()) and dynamic method names (Class::$method())
- Configuration: Supports
forbiddenStaticMethodsparameter with an array of regex patterns matchingFQCN::methodName - Error Messages:
Static method call "{call}" is forbidden.
Configuration Example:
services:
-
class: Phauthentic\PHPStanRules\Architecture\ForbiddenStaticMethodsRule
arguments:
forbiddenStaticMethods:
- '/^App\\Legacy\\.*::.*/' # Namespace-level: all legacy static calls
- '/^App\\Utils\\StaticHelper::.*/' # Class-level: all methods on StaticHelper
- '/^DateTime::createFromFormat$/' # Method-level: specific method only
tags:
- phpstan.rules.rule⨠New: ClassNameResolver Trait
Reusable Utilities for FQCN Resolution
- Trait:
Phauthentic\PHPStanRules\Architecture\ClassNameResolver - Purpose: Extracts common fully qualified class name resolution and type conversion utilities into a reusable trait
- Key Methods:
resolveFullClassName()β Builds the FQCN from aClass_orInterface_node and scope; returnsnullfor anonymous classesgetTypeAsString()β Converts type nodes (Identifier,Name,NullableType,UnionType,IntersectionType) to their string representationmatchesAnyPattern()β Checks if a string matches any of the given regex patternsnormalizeClassName()β Removes the leading backslash from a class name
β¨ New: Interactive Rule Config Builder
- File:
rule-builder.html - Purpose: An interactive HTML tool for building PHPStan rule configurations visually, making it easier to create correct
phpstan.neonconfiguration without memorizing all parameters
π§ Code Quality Improvements
ποΈ Refactored Rules to Use ClassNameResolver Trait
Multiple existing rules were refactored to use the new ClassNameResolver trait, reducing code duplication and improving consistency:
- AttributeRule β Replaced inline FQCN resolution with
resolveFullClassName() - CatchExceptionOfTypeNotAllowedRule β Added class name normalization for robust comparison (leading backslash handling)
- ClassMustBeFinalRule β Simplified to use
resolveFullClassName()andmatchesAnyPattern() - ClassMustBeReadonlyRule β Simplified to use
resolveFullClassName()andmatchesAnyPattern() - ClassMustHaveSpecificationDocblockRule β Replaced private
matchesPatterns()with trait'smatchesAnyPattern() - MethodMustReturnTypeRule β Replaced private
getTypeAsString()with trait version; now uses FQCN for method matching - MethodSignatureMustMatchRule β Replaced private
getTypeAsString()with trait version; now uses FQCN for method matching
π Improved Anonymous Class Handling
All class-level rules now properly skip anonymous classes (classes without names) via the resolveFullClassName() method returning null, preventing false positives and potential errors.
β Constructor Parameter Validation
Both PropertyMustMatchRule and ForbiddenAccessorsRule validate constructor parameters at instantiation time, throwing InvalidArgumentException for invalid configurations (empty patterns, invalid visibility values).
π§ Regex Error Handling
PropertyMustMatchRule and ForbiddenAccessorsRule now properly handle preg_match errors, throwing InvalidArgumentException with descriptive messages when an invalid regex pattern is provided.
π¦ Dependency Updates
- Updated
phpunit/phpunitfrom^12.0to^12.5.8
π Documentation Improvements
π New Documentation Files
- Forbidden Accessors Rule: Complete documentation with examples for forbidding getters, setters, or both at configurable visibility levels
- Forbidden Dependencies Rule: Dedicated documentation for the renamed rule with FQCN checking, allowed dependencies, and a Mermaid flowchart explaining the allow/forbid logic
- Forbidden Static Methods Rule: Full documentation covering namespace-level, class-level, and method-level pattern granularity, including
self/static/parenthandling - Property Must Match Rule: Comprehensive documentation with examples for required properties, optional validation, and nullable types
π Updated Documentation
- README.md: Added Forbidden Accessors Rule, Forbidden Dependencies Rule, Forbidden Static Methods Rule, and Property Must Match Rule to the Architecture Rules list; marked Dependency Constraints Rule as deprecated
- docs/Rules.md: Added overview sections and full configuration examples for all new rules
β New Test Coverage
New Test Files
- PropertyMustMatchRuleTest: Core functionality tests for property name, type, and required validation
- PropertyMustMatchRuleNullableTest: Tests for nullable type matching
- PropertyMustMatchRuleIntersectionTypeTest: Tests for intersection type support
- PropertyMustMatchRuleInvalidVisibilityTest: Tests for invalid visibility validation
- PropertyMustMatchRuleInvalidRegexTest: Tests for invalid regex pattern handling
- PropertyMustMatchRuleExceptionsTest: Tests for constructor validation
- ForbiddenAccessorsRuleTest: Core functionality tests
- ForbiddenAccessorsRuleGettersOnlyTest: Tests for forbidding only getters
- ForbiddenAccessorsRuleSettersOnlyTest: Tests for forbidding only setters
- ForbiddenAccessorsRulePrivateVisibilityTest: Tests for private visibility checking
- ForbiddenAccessorsRuleProtectedVisibilityTest: Tests for protected visibility checking
- ForbiddenAccessorsRuleExceptionsTest: Tests for constructor validation
- ForbiddenStaticMethodsRuleTest: Core functionality tests
- ForbiddenStaticMethodsRuleSelfStaticParentTest: Tests for
self/static/parentkeyword resolution - MethodMustReturnTypeRuleEdgeCasesTest: Edge case tests for existing rule
- MethodsReturningBoolMustFollowNamingConventionEdgeCasesTest: Edge case tests for existing rule
Enhanced Existing Tests
- CircularModuleDependencyRuleTest: Added tests for non-modular and anonymous classes
- ClassMustBeFinalRuleTest: Added test for anonymous class handling
New Test Data Files
- 18 new test data files covering all new rules and edge cases
π Statistics
- 64 files changed with 4,752 lines added and 220 lines removed
- 3 new rules created
- 1 new trait extracted for shared utilities
- 16 new test files for comprehensive coverage
- 18 new test data files
π Migration Guide
For Existing Users
No Action Required! All existing configurations continue to work without any changes. The refactoring to use the ClassNameResolver trait is purely internal and does not affect configuration or behavior.
Note: Rules that previously matched against short class names (e.g., MethodMustReturnTypeRule, MethodSignatureMustMatchRule) now match against fully qualified class names. If your patterns relied on short class names, you may need to update them to include the namespace. For example:
Before:
pattern: '/^MyService::doSomething$/'
After:
pattern: '/^App\\Service\\MyService::doSomething$/'
Or use a wildcard to match any namespace:
pattern: '/.*MyService::doSomething$/'
For New Rules
All new rules are opt-in and require explicit configuration in your phpstan.neon. See the individual rule documentation for detailed configuration examples.
Full Changelog: 1.8.0...1.9.0