Skip to content
This repository was archived by the owner on Oct 15, 2025. It is now read-only.

Commit dd785bd

Browse files
authored
Merge pull request #10 from Lendable/merge-upstream
2 parents c882099 + 55dc839 commit dd785bd

File tree

62 files changed

+878
-121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+878
-121
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Architectural test
2+
3+
on:
4+
push:
5+
6+
jobs:
7+
phparkitect:
8+
name: PHPArkitect
9+
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: "Checkout"
14+
uses: actions/checkout@v2
15+
16+
- name: PHPArkitect
17+
uses: docker://phparkitect/arkitect-github-actions:latest
18+
with:
19+
args: check

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ jobs:
1111
build:
1212
runs-on: "ubuntu-22.04"
1313
strategy:
14+
fail-fast: false
1415
matrix:
15-
php-versions: [ '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
16+
php-versions: [ '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
1617
coverage-driver: [ 'pcov' ]
1718

1819
steps:
@@ -55,4 +56,3 @@ jobs:
5556
uses: codecov/codecov-action@v1
5657
with:
5758
token: ${{ secrets.CODECOV_TOKEN }}
58-

CONTRIBUTORS.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,30 @@ Shout out to our top contributors!
44

55
- [AlessandroMinoccheri](https://api.github.com/users/AlessandroMinoccheri)
66
- [fain182](https://api.github.com/users/fain182)
7-
- [micheleorselli](https://api.github.com/users/micheleorselli)
87
- [pfazzi](https://api.github.com/users/pfazzi)
98
- [github-actions[bot]](https://api.github.com/users/github-actions%5Bbot%5D)
10-
- [ricfio](https://api.github.com/users/ricfio)
9+
- [micheleorselli](https://api.github.com/users/micheleorselli)
1110
- [sebastianstucke87](https://api.github.com/users/sebastianstucke87)
11+
- [ricfio](https://api.github.com/users/ricfio)
1212
- [szepeviktor](https://api.github.com/users/szepeviktor)
1313
- [JulienRAVIA](https://api.github.com/users/JulienRAVIA)
14+
- [helyakin](https://api.github.com/users/helyakin)
15+
- [ben-challis](https://api.github.com/users/ben-challis)
1416
- [LuigiCardamone](https://api.github.com/users/LuigiCardamone)
1517
- [annervisser](https://api.github.com/users/annervisser)
1618
- [simivar](https://api.github.com/users/simivar)
17-
- [stephpy](https://api.github.com/users/stephpy)
18-
- [dbu](https://api.github.com/users/dbu)
1919
- [jdomenechbLendable](https://api.github.com/users/jdomenechbLendable)
20+
- [dbu](https://api.github.com/users/dbu)
21+
- [stephpy](https://api.github.com/users/stephpy)
22+
- [marmichalski](https://api.github.com/users/marmichalski)
23+
- [hgraca](https://api.github.com/users/hgraca)
2024
- [frankverhoeven](https://api.github.com/users/frankverhoeven)
2125
- [hectorespert](https://api.github.com/users/hectorespert)
2226
- [jerowork](https://api.github.com/users/jerowork)
2327
- [jkrzefski](https://api.github.com/users/jkrzefski)
2428
- [mloru](https://api.github.com/users/mloru)
29+
- [nikow13](https://api.github.com/users/nikow13)
2530
- [OskarStark](https://api.github.com/users/OskarStark)
2631
- [smortexa](https://api.github.com/users/smortexa)
2732
- [DonCallisto](https://api.github.com/users/DonCallisto)
33+
- [notcgi](https://api.github.com/users/notcgi)

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ $rules[] = Rule::allClasses()
236236
->that(new ResideInOneOfTheseNamespaces('App\Controller'))
237237
->should(new Extend('App\Controller\AbstractController'))
238238
->because('we want to be sure that all controllers extend AbstractController');
239+
240+
You can add multiple parameters, the violation will happen when none of them match
239241
```
240242

241243
### Has an attribute (requires PHP >= 8.0)
@@ -301,6 +303,15 @@ $rules[] = Rule::allClasses()
301303
->because('we want to be sure that aggregates are final classes');
302304
```
303305

306+
### Is readonly
307+
308+
```php
309+
$rules[] = Rule::allClasses()
310+
->that(new ResideInOneOfTheseNamespaces('App\Domain\ValueObjects'))
311+
->should(new IsReadonly())
312+
->because('we want to be sure that value objects are readonly classes');
313+
```
314+
304315
### Is interface
305316

306317
```php
@@ -346,6 +357,15 @@ $rules[] = Rule::allClasses()
346357
->because('we want to be sure that our adapters are not final classes');
347358
```
348359

360+
### Is not readonly
361+
362+
```php
363+
$rules[] = Rule::allClasses()
364+
->that(new ResideInOneOfTheseNamespaces('App\Domain\Entity'))
365+
->should(new IsNotReadonly())
366+
->because('we want to be sure that there are no readonly entities');
367+
```
368+
349369
### Is not interface
350370

351371
```php
@@ -380,14 +400,16 @@ $rules[] = Rule::allClasses()
380400
->that(new ResideInOneOfTheseNamespaces('App\Controller\Admin'))
381401
->should(new NotExtend('App\Controller\AbstractController'))
382402
->because('we want to be sure that all admin controllers not extend AbstractController for security reasons');
403+
404+
You can add multiple parameters, the violation will happen when one of them match
383405
```
384406

385407
### Don't have dependency outside a namespace
386408

387409
```php
388410
$rules[] = Rule::allClasses()
389411
->that(new ResideInOneOfTheseNamespaces('App\Domain'))
390-
->should(new NotHaveDependencyOutsideNamespace('App\Domain', ['Ramsey\Uuid']))
412+
->should(new NotHaveDependencyOutsideNamespace('App\Domain', ['Ramsey\Uuid'], true))
391413
->because('we want protect our domain except for Ramsey\Uuid');
392414
```
393415

composer.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"description": "Enforce architectural constraints in your PHP applications",
44
"type": "library",
55
"license": "MIT",
6+
"version": "0.3.33",
67
"authors": [
78
{
89
"name": "Herberto Graca",
@@ -27,9 +28,9 @@
2728
],
2829
"require": {
2930
"php": "^7.1|^8",
30-
"symfony/finder": "^3.0|^4.0|^5.0|^6.0",
31-
"symfony/event-dispatcher": "^3.0|^4.0|^5.0|^6.0",
32-
"symfony/console": "^3.0|^4.0|^5.0|^6.0",
31+
"symfony/finder": "^3.0|^4.0|^5.0|^6.0|^7.0",
32+
"symfony/event-dispatcher": "^3.0|^4.0|^5.0|^6.0|^7.0",
33+
"symfony/console": "^3.0|^4.0|^5.0|^6.0|^7.0",
3334
"symfony/polyfill-php80": "^1.20",
3435
"nikic/php-parser": "~4",
3536
"webmozart/assert": "^1.9",
@@ -39,7 +40,7 @@
3940
},
4041
"require-dev": {
4142
"roave/security-advisories": "dev-master",
42-
"symfony/var-dumper": "^3.0|^4.0|^5.0|^6.0",
43+
"symfony/var-dumper": "^3.0|^4.0|^5.0|^6.0|^7.0",
4344
"vimeo/psalm": "^4.6",
4445
"friendsofphp/php-cs-fixer": "^3.0",
4546
"phpunit/phpunit": "^7.5|^9.0|^10.0",

src/Analyzer/ClassDescription.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class ClassDescription
2323
/** @var bool */
2424
private $final;
2525

26+
/** @var bool */
27+
private $readonly;
28+
2629
/** @var bool */
2730
private $abstract;
2831

@@ -53,6 +56,7 @@ public function __construct(
5356
array $interfaces,
5457
?FullyQualifiedClassName $extends,
5558
bool $final,
59+
bool $readonly,
5660
bool $abstract,
5761
bool $interface,
5862
bool $trait,
@@ -65,6 +69,7 @@ public function __construct(
6569
$this->interfaces = $interfaces;
6670
$this->extends = $extends;
6771
$this->final = $final;
72+
$this->readonly = $readonly;
6873
$this->abstract = $abstract;
6974
$this->docBlock = $docBlock;
7075
$this->attributes = $attributes;
@@ -151,6 +156,11 @@ public function isFinal(): bool
151156
return $this->final;
152157
}
153158

159+
public function isReadonly(): bool
160+
{
161+
return $this->readonly;
162+
}
163+
154164
public function isAbstract(): bool
155165
{
156166
return $this->abstract;

src/Analyzer/ClassDescriptionBuilder.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class ClassDescriptionBuilder
2222
/** @var bool */
2323
private $final = false;
2424

25+
/** @var bool */
26+
private $readonly = false;
27+
2528
/** @var bool */
2629
private $abstract = false;
2730

@@ -47,6 +50,7 @@ public function clear(): void
4750
$this->interfaces = [];
4851
$this->extend = null;
4952
$this->final = false;
53+
$this->readonly = false;
5054
$this->abstract = false;
5155
$this->docBlock = [];
5256
$this->attributes = [];
@@ -92,6 +96,13 @@ public function setFinal(bool $final): self
9296
return $this;
9397
}
9498

99+
public function setReadonly(bool $readonly): self
100+
{
101+
$this->readonly = $readonly;
102+
103+
return $this;
104+
}
105+
95106
public function setAbstract(bool $abstract): self
96107
{
97108
$this->abstract = $abstract;
@@ -145,6 +156,7 @@ public function build(): ClassDescription
145156
$this->interfaces,
146157
$this->extend,
147158
$this->final,
159+
$this->readonly,
148160
$this->abstract,
149161
$this->interface,
150162
$this->trait,

src/Analyzer/FileVisitor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public function enterNode(Node $node): void
4242
$this->classDescriptionBuilder->setFinal(true);
4343
}
4444

45+
if ($node->isReadonly()) {
46+
$this->classDescriptionBuilder->setReadonly(true);
47+
}
48+
4549
if ($node->isAbstract()) {
4650
$this->classDescriptionBuilder->setAbstract(true);
4751
}

src/Analyzer/NameResolver.php

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use PhpParser\Node\Name;
1313
use PhpParser\Node\Name\FullyQualified;
1414
use PhpParser\Node\Stmt;
15-
use PhpParser\Node\Stmt\Use_;
1615
use PhpParser\NodeAbstract;
1716
use PhpParser\NodeVisitorAbstract;
1817
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
@@ -91,7 +90,7 @@ public function enterNode(Node $node)
9190
{
9291
if ($node instanceof Stmt\Namespace_) {
9392
$this->nameContext->startNamespace($node->name);
94-
} elseif ($node instanceof Use_) {
93+
} elseif ($node instanceof Stmt\Use_) {
9594
foreach ($node->uses as $use) {
9695
$this->addAlias($use, $node->type, null);
9796
}
@@ -161,14 +160,14 @@ public function enterNode(Node $node)
161160
}
162161

163162
if (null !== $arrayItemType) {
164-
$node->type = $this->resolveName(new Name($arrayItemType), Use_::TYPE_NORMAL);
163+
$node->type = $this->resolveName(new Name($arrayItemType), Stmt\Use_::TYPE_NORMAL);
165164

166165
return;
167166
}
168167
}
169168

170169
foreach ($phpDocNode->getVarTagValues() as $tagValue) {
171-
$type = $this->resolveName(new Name((string) $tagValue->type), Use_::TYPE_NORMAL);
170+
$type = $this->resolveName(new Name((string) $tagValue->type), Stmt\Use_::TYPE_NORMAL);
172171
$node->type = $type;
173172
break;
174173
}
@@ -177,7 +176,7 @@ public function enterNode(Node $node)
177176
foreach ($phpDocNode->getTags() as $tagValue) {
178177
if ('@' === $tagValue->name[0] && !str_contains($tagValue->name, '@var')) {
179178
$customTag = str_replace('@', '', $tagValue->name);
180-
$type = $this->resolveName(new Name($customTag), Use_::TYPE_NORMAL);
179+
$type = $this->resolveName(new Name($customTag), Stmt\Use_::TYPE_NORMAL);
181180
$node->type = $type;
182181

183182
break;
@@ -207,10 +206,10 @@ public function enterNode(Node $node)
207206
}
208207
} elseif ($node instanceof Expr\FuncCall) {
209208
if ($node->name instanceof Name) {
210-
$node->name = $this->resolveName($node->name, Use_::TYPE_FUNCTION);
209+
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
211210
}
212211
} elseif ($node instanceof Expr\ConstFetch) {
213-
$node->name = $this->resolveName($node->name, Use_::TYPE_CONSTANT);
212+
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
214213
} elseif ($node instanceof Stmt\TraitUse) {
215214
foreach ($node->traits as &$trait) {
216215
$trait = $this->resolveClassName($trait);
@@ -282,7 +281,7 @@ protected function resolveName(Name $name, int $type): Name
282281

283282
protected function resolveClassName(Name $name): Name
284283
{
285-
return $this->resolveName($name, Use_::TYPE_NORMAL);
284+
return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
286285
}
287286

288287
/**
@@ -339,7 +338,7 @@ private function resolveSignature($node): void
339338
$arrayItemType = $this->getArrayItemType($phpDocParam->type);
340339

341340
if (null !== $arrayItemType) {
342-
$param->type = $this->resolveName(new Name($arrayItemType), Use_::TYPE_NORMAL);
341+
$param->type = $this->resolveName(new Name($arrayItemType), Stmt\Use_::TYPE_NORMAL);
343342
}
344343
}
345344
}
@@ -356,7 +355,7 @@ private function resolveSignature($node): void
356355
}
357356

358357
if (null !== $arrayItemType) {
359-
$node->returnType = $this->resolveName(new Name($arrayItemType), Use_::TYPE_NORMAL);
358+
$node->returnType = $this->resolveName(new Name($arrayItemType), Stmt\Use_::TYPE_NORMAL);
360359
}
361360
}
362361
}

src/CLI/Command/DebugExpression.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
class DebugExpression extends Command
1818
{
19-
/** @var string|null */
20-
public static $defaultName = 'debug:expression';
21-
2219
/** @var string|null */
2320
public static $defaultDescription = <<< 'EOT'
2421
Check which classes respect an expression
@@ -29,6 +26,11 @@ class DebugExpression extends Command
2926
Check which classes respect an expression
3027
EOT;
3128

29+
public function __construct()
30+
{
31+
parent::__construct('debug:expression');
32+
}
33+
3234
protected function configure(): void
3335
{
3436
$this

0 commit comments

Comments
 (0)