Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/php.code_quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 10 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
<?xml version="1.0"?>
<ruleset name="Arty ruleset"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>
Arty ruleset
</description>

<rule ref="./vendor/arty/php-coding-standard/lib/phpmd/phpmd.xml"/>
</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
```
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
84 changes: 0 additions & 84 deletions lib/phpmd/phpmd.xml

This file was deleted.

12 changes: 0 additions & 12 deletions phpmd.xml

This file was deleted.

1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
78 changes: 78 additions & 0 deletions src/ArtyCodingStandard/PHPAt/CleanArchitectureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace ArtyCodingStandard\PHPAt;

use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;

final class CleanArchitectureTest
{
public function testDomainDoesNotDependOnApplication(): Rule
{
return PHPat::rule()
->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',
);
}
}
Loading