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', + ); + } +}