From d962443475194be766db405860e322ef890f7ed2 Mon Sep 17 00:00:00 2001 From: Arty Date: Fri, 6 Mar 2026 00:11:58 +0100 Subject: [PATCH 1/5] feat: add phpat and remove phpmd --- .github/workflows/php.code_quality.yaml | 2 +- README.md | 30 +++------ composer.json | 4 +- lib/phpmd/phpmd.xml | 84 ------------------------- phpmd.xml | 12 ---- phpstan.neon | 1 + 6 files changed, 14 insertions(+), 119 deletions(-) delete mode 100644 lib/phpmd/phpmd.xml delete mode 100644 phpmd.xml 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..6d55e95 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: From 2675f67e43a5a5aff3a9520018912d80511cb200 Mon Sep 17 00:00:00 2001 From: Arty Date: Sat, 7 Mar 2026 10:53:00 +0100 Subject: [PATCH 2/5] add phpat class --- .../PHPAt/CleanArchitectureTest.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php diff --git a/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php new file mode 100644 index 0000000..31bf874 --- /dev/null +++ b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php @@ -0,0 +1,75 @@ +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 test_domain_does_not_depend_on_infrastructure(): 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 test_domain_does_not_depend_on_symfony(): Rule + { + return PHPat::rule() + ->classes(Selector::inNamespace('App\Domain')) + ->shouldNotDependOn() + ->classes(Selector::inNamespace('Symfony')) + ->because('Domain must be framework-agnostic'); + } + + public function test_domain_does_not_depend_on_doctrine(): Rule + { + return PHPat::rule() + ->classes(Selector::inNamespace('App\Domain')) + ->shouldNotDependOn() + ->classes(Selector::inNamespace('Doctrine')) + ->because('Domain must not depend on persistence concerns'); + } + + public function test_application_does_not_depend_on_infrastructure(): 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 test_application_does_not_depend_on_symfony(): Rule + { + return PHPat::rule() + ->classes(Selector::inNamespace('App\Application')) + ->shouldNotDependOn() + ->classes(Selector::inNamespace('Symfony')) + ->because('Application use-cases must be framework-agnostic'); + } + + public function test_application_does_not_depend_on_doctrine(): 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'); + } +} From 87550f446021aa3893934c724e1b85ea66e6d00c Mon Sep 17 00:00:00 2001 From: Arty Date: Sat, 7 Mar 2026 11:01:11 +0100 Subject: [PATCH 3/5] fix phpstan neon formatting --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 6d55e95..c3ece0f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,7 @@ includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - lib/phpstan/phpstan.neon - - vendor/phpat/phpat/extension.neon + - vendor/phpat/phpat/extension.neon parameters: bootstrapFiles: From 69ebccc1bf50e431d2c1e4536ddd0390b42118f0 Mon Sep 17 00:00:00 2001 From: Arty Date: Sat, 7 Mar 2026 11:07:24 +0100 Subject: [PATCH 4/5] Claude/review pr changes y ymyi (#90) * fix: fix mixed indentation in phpstan.neon includes Replace tab with spaces on the phpat extension include line to match the rest of the file and avoid NEON parse errors. https://claude.ai/code/session_01VLpcvXiDPSvMJiqQWgWaeF * fix: fix phpcs violations in CleanArchitectureTest Rename methods to camelCase and split long line to comply with the coding standard rules. https://claude.ai/code/session_01VLpcvXiDPSvMJiqQWgWaeF --------- Co-authored-by: Claude --- .../PHPAt/CleanArchitectureTest.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php index 31bf874..a5a1f1e 100644 --- a/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php +++ b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php @@ -10,7 +10,7 @@ final class CleanArchitectureTest { - public function test_domain_does_not_depend_on_application(): Rule + public function testDomainDoesNotDependOnApplication(): Rule { return PHPat::rule() ->classes(Selector::inNamespace('App\Domain')) @@ -19,7 +19,7 @@ public function test_domain_does_not_depend_on_application(): Rule ->because('Domain is the core layer and must remain independent of outer layers'); } - public function test_domain_does_not_depend_on_infrastructure(): Rule + public function testDomainDoesNotDependOnInfrastructure(): Rule { return PHPat::rule() ->classes(Selector::inNamespace('App\Domain')) @@ -28,7 +28,7 @@ public function test_domain_does_not_depend_on_infrastructure(): Rule ->because('Domain must not know about infrastructure concerns (DB, HTTP, etc.)'); } - public function test_domain_does_not_depend_on_symfony(): Rule + public function testDomainDoesNotDependOnSymfony(): Rule { return PHPat::rule() ->classes(Selector::inNamespace('App\Domain')) @@ -37,7 +37,7 @@ public function test_domain_does_not_depend_on_symfony(): Rule ->because('Domain must be framework-agnostic'); } - public function test_domain_does_not_depend_on_doctrine(): Rule + public function testDomainDoesNotDependOnDoctrine(): Rule { return PHPat::rule() ->classes(Selector::inNamespace('App\Domain')) @@ -46,7 +46,7 @@ public function test_domain_does_not_depend_on_doctrine(): Rule ->because('Domain must not depend on persistence concerns'); } - public function test_application_does_not_depend_on_infrastructure(): Rule + public function testApplicationDoesNotDependOnInfrastructure(): Rule { return PHPat::rule() ->classes(Selector::inNamespace('App\Application')) @@ -55,7 +55,7 @@ public function test_application_does_not_depend_on_infrastructure(): Rule ->because('Application layer must depend on Domain abstractions, not Infrastructure implementations'); } - public function test_application_does_not_depend_on_symfony(): Rule + public function testApplicationDoesNotDependOnSymfony(): Rule { return PHPat::rule() ->classes(Selector::inNamespace('App\Application')) @@ -64,12 +64,15 @@ public function test_application_does_not_depend_on_symfony(): Rule ->because('Application use-cases must be framework-agnostic'); } - public function test_application_does_not_depend_on_doctrine(): Rule + 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'); + ->because( + 'Application layer must not depend on persistence concerns' + . ' — use Domain repository interfaces instead' + ); } } From 91d690fb56e0811fce72e4d8d71f5c19e50d8673 Mon Sep 17 00:00:00 2001 From: Arty Date: Sat, 7 Mar 2026 11:07:59 +0100 Subject: [PATCH 5/5] fixup! Claude/review pr changes y ymyi (#90) --- src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php index a5a1f1e..deced0b 100644 --- a/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php +++ b/src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php @@ -72,7 +72,7 @@ public function testApplicationDoesNotDependOnDoctrine(): Rule ->classes(Selector::inNamespace('Doctrine')) ->because( 'Application layer must not depend on persistence concerns' - . ' — use Domain repository interfaces instead' + . ' — use Domain repository interfaces instead', ); } }