PHPat (PHP Architecture Tester)
repository·main·Indexed 22 days ago
https://github.com/carlosas/phpatA PHPStan extension that allows developers to define and enforce architectural rules using a natural language abstraction. PHPat provides a fluent assertion API to verify class types, properties, inheritance, and dependencies, enabling the enforcement of layered architectures, MVC decoupling, and restricted vendor coupling through the use of Selectors and Rules.
What's inside PHPat
- PHPat is a PHPStan extension designed for architectural testing. It provides a natural language abstraction that allows developers to define custom architectural rules and verify that the codebase complies with them during static analysis.
What is PHP Architecture Tester (phpat)?
mainPHP Architecture Tester (phpat) is a PHPStan extension designed for static analysis to verify architectural requirements in PHP projects. It provides a natural language abstraction that allows developers to define custom architectural rules and assess whether the codebase complies with them.Leverage PHPStan features with PHP Architecture Tester
mainBecause PHP Architecture Tester is a PHPStan extension, it inherits all of PHPStan's core capabilities. You can use standard PHPStan features to manage your architecture testing workflow, including:
- Baseline files: Generate a baseline to ignore existing architecture violations and only report new ones introduced in your code.
- Output formats: Use various error formatters to customize how architecture violations are displayed.
- Ignoring errors: Configure specific rules or patterns to be ignored in your analysis.
- Parallel analysis: Speed up your architecture checks by analyzing multiple files simultaneously.
- Result Cache: Enable caching to significantly reduce the time required for subsequent analysis runs.
How PHPat works and how to use it
mainPHPat (PHP Architecture Tester) is a PHPStan extension used to define and enforce architectural rules in PHP codebases.
Core Workflow
- Define Rules: You create test classes containing methods that define architectural constraints using a fluent API.
- Register Tests: You register these test classes in your
phpstan.neonconfiguration file using thephpat.testtag. - Run Analysis: When you run PHPStan, PHPat hooks into the analysis process. It discovers your tests, parses the fluent builder calls into
Statementobjects, and executes corresponding assertion rules against the AST (Abstract Syntax Tree) nodes of your codebase.
Assertion Categories
- Relation assertions: Check relationships between classes (e.g.,
dependOn,extend,implement,include). - Declaration assertions: Check properties of classes themselves (e.g.,
isAbstract,isFinal,isReadonly).
How Selectors work in PHPat
mainSelectors are the mechanism used to define the scope of a PHPat rule. They tell the engine which classes should be evaluated against a specific architectural constraint.
Selectors can be simple (targeting a single class name) or complex (using logical composition to target groups of classes based on inheritance, namespace, or file structure). By using logical operators like
AllOf,AnyOf, andNot, you can build highly granular rules that target specific layers or architectural patterns in your application.How rule identifiers work for ignoring errors
mainWhen a rule is violated, PHPat generates an error message containing a unique identifier. This identifier is currently derived from the name of the rule's method. You can use this identifier to ignore specific rule violations in your configuration.Run PHPat development commands
mainUse these commands for local development and maintaining the PHPat repository:
# Fix coding standards vendor/bin/php-cs-fixer fix --config ./ci/php-cs-fixer.php # Run PHPStan with PHPat architecture tests (validates src/ + tests/architecture/) vendor/bin/phpstan analyse -c ci/phpstan-phpat.neon # Run Psalm static analysis vendor/bin/psalm -c ci/psalm.xml # Run tests vendor/bin/phpunit tests/unit/ tests/integration/ # Run a single test file vendor/bin/phpunit tests/unit/rules/SomeTest.phpRestrict vendor coupling
mainYou can restrict a specific namespace to only allow dependencies within itself, effectively isolating it from the rest of the application or third-party vendors.
>Classes in namespace App\Domain can only depend on classes in namespace App\DomainRun PHPat architecture tests
mainPHPat tests are executed as part of your standard PHPStan analysis. Run PHPStan as usual:
php vendor/bin/phpstan analyse -c phpstan.neonEnforce interface implementation
mainYou can mandate that classes within a specific namespace must implement a particular interface. This is a common way to ensure that all Entities or Value Objects adhere to a specific contract.
>Classes in namespace App\Domain\Entity should implement class with name App\Domain\Entity\EntityInterfaceDefine architecture rules in PHPat
mainA rule is a statement composed of Selectors and Assertions that must be true for a test to pass. In a test class, rules must be defined as
publicmethods that either:- Start with the prefix
test_. - Are decorated with the
#[TestRule]attribute.
Rules are constructed using the
\PHPat\Test\PHPat::rule()method, which provides a fluent builder interface to select classes and assert their properties or dependencies.namespace App ests\Architecture; use PHPat\Selector\Selector; use PHPat\Test\Attributes\TestRule; use PHPat\Test\Builder\Rule; use PHPat\Test\PHPat; final class ConfigurationTest { public function test_domain_independence(): Rule { return PHPat::rule() ->classes(Selector::inNamespace('App\Domain')) ->canOnlyDependOn() ->classes(Selector::inNamespace('App\Domain')); } #[TestRule] public function entities_are_final(): Rule { return PHPat::rule() ->classes(Selector::extends(Entity::class)) ->shouldBeFinal(); } }- Start with the prefix
Enforce layered architecture dependencies
mainYou can use PHPAT to ensure that architectural layers remain decoupled. For example, in an onion or layered architecture, you can write rules to prevent inner layers (like
App\Domain) from depending on outer layers (likeApp\ApplicationorApp\Infrastructure).>Classes in namespace App\Domain should not depend on classes in namespace App\Application and classes in namespace App\Infrastructure >Classes in namespace App\Application should not depend on classes in namespace App\Infrastructure