diff --git a/.github/workflows/php.code_quality.yaml b/.github/workflows/php.code_quality.yaml
index f7987dd..bf506e1 100644
--- a/.github/workflows/php.code_quality.yaml
+++ b/.github/workflows/php.code_quality.yaml
@@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- tool: [phpcs, phpmd, phpstan, phpunit]
+ tool: [phpcs, phpstan, phpunit]
steps:
- name: Setup PHP ${{ env.PHP_VERSION }} environment
uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401
diff --git a/README.md b/README.md
index a2bbaf3..7d0ebcb 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ This is the coding style tools config I use everyday on my projects.
it includes :
- PHPCS for code sniffing.
- PHPStan for static bug finding into code.
-- PHPMD for static performance analysis (complexity, unused expressions, optimizing).
+- PHPAt as phpstan extension for architecture validation
## Installation
@@ -49,26 +49,16 @@ Run it with :
$ ./vendor/bin/phpstan analyse --level=max src
```
-### PHPMD
+### PHPAT
-In your project, create a `phpmd.xml` file and fill it up with :
+Validate Architecture.
-```xml
-
-
-
- Arty ruleset
-
-
-
-
-```
+A minimal Clean Architecture validation is provided, activate it by adding this to your phpstan.neon:
-Run it with :
-```bash
-$ ./vendor/bin/phpmd src text phpmd.xml
+```neon
+services:
+ -
+ class: ArtyCodingStandard\PHPAt\CleanArchitectureTest
+ tags:
+ - phpat.test
```
diff --git a/composer.json b/composer.json
index b3a073b..b54aa6d 100644
--- a/composer.json
+++ b/composer.json
@@ -7,9 +7,9 @@
"php": "^8.4",
"squizlabs/php_codesniffer": "^4.0",
"slevomat/coding-standard": "^8.24",
- "phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^2.1",
- "phpstan/phpstan-symfony": "^2.0"
+ "phpstan/phpstan-symfony": "^2.0",
+ "phpat/phpat": "^0.12.3"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
diff --git a/lib/phpmd/phpmd.xml b/lib/phpmd/phpmd.xml
deleted file mode 100644
index f775a9b..0000000
--- a/lib/phpmd/phpmd.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
- Default configuration by arty
-
-
-
- tests/
- vendor/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/phpmd.xml b/phpmd.xml
deleted file mode 100644
index 86c8fc2..0000000
--- a/phpmd.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- Arty PHPMD ruleset
-
-
-
-
diff --git a/phpstan.neon b/phpstan.neon
index 81333c4..c3ece0f 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -2,6 +2,7 @@ includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- lib/phpstan/phpstan.neon
+ - vendor/phpat/phpat/extension.neon
parameters:
bootstrapFiles:
diff --git a/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php
new file mode 100644
index 0000000..deced0b
--- /dev/null
+++ b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php
@@ -0,0 +1,78 @@
+classes(Selector::inNamespace('App\Domain'))
+ ->shouldNotDependOn()
+ ->classes(Selector::inNamespace('App\Application'))
+ ->because('Domain is the core layer and must remain independent of outer layers');
+ }
+
+ public function testDomainDoesNotDependOnInfrastructure(): Rule
+ {
+ return PHPat::rule()
+ ->classes(Selector::inNamespace('App\Domain'))
+ ->shouldNotDependOn()
+ ->classes(Selector::inNamespace('App\Infrastructure'))
+ ->because('Domain must not know about infrastructure concerns (DB, HTTP, etc.)');
+ }
+
+ public function testDomainDoesNotDependOnSymfony(): Rule
+ {
+ return PHPat::rule()
+ ->classes(Selector::inNamespace('App\Domain'))
+ ->shouldNotDependOn()
+ ->classes(Selector::inNamespace('Symfony'))
+ ->because('Domain must be framework-agnostic');
+ }
+
+ public function testDomainDoesNotDependOnDoctrine(): Rule
+ {
+ return PHPat::rule()
+ ->classes(Selector::inNamespace('App\Domain'))
+ ->shouldNotDependOn()
+ ->classes(Selector::inNamespace('Doctrine'))
+ ->because('Domain must not depend on persistence concerns');
+ }
+
+ public function testApplicationDoesNotDependOnInfrastructure(): Rule
+ {
+ return PHPat::rule()
+ ->classes(Selector::inNamespace('App\Application'))
+ ->shouldNotDependOn()
+ ->classes(Selector::inNamespace('App\Infrastructure'))
+ ->because('Application layer must depend on Domain abstractions, not Infrastructure implementations');
+ }
+
+ public function testApplicationDoesNotDependOnSymfony(): Rule
+ {
+ return PHPat::rule()
+ ->classes(Selector::inNamespace('App\Application'))
+ ->shouldNotDependOn()
+ ->classes(Selector::inNamespace('Symfony'))
+ ->because('Application use-cases must be framework-agnostic');
+ }
+
+ public function testApplicationDoesNotDependOnDoctrine(): Rule
+ {
+ return PHPat::rule()
+ ->classes(Selector::inNamespace('App\Application'))
+ ->shouldNotDependOn()
+ ->classes(Selector::inNamespace('Doctrine'))
+ ->because(
+ 'Application layer must not depend on persistence concerns'
+ . ' — use Domain repository interfaces instead',
+ );
+ }
+}