Skip to content

Commit 89bfe5f

Browse files
hgracaHerberto Graca
and
Herberto Graca
authored
Create a IsA expression (#396)
* Create a IsA expression This will allow for testing if a class extends or implements another code unit, anywhere in the inheritance tree. --------- Co-authored-by: Herberto Graca <[email protected]>
1 parent 55b9163 commit 89bfe5f

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

src/Expression/ForClasses/IsA.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Expression\ForClasses;
6+
7+
use Arkitect\Analyzer\ClassDescription;
8+
use Arkitect\Expression\Description;
9+
use Arkitect\Expression\Expression;
10+
use Arkitect\Rules\Violation;
11+
use Arkitect\Rules\ViolationMessage;
12+
use Arkitect\Rules\Violations;
13+
14+
final class IsA implements Expression
15+
{
16+
/** @var class-string */
17+
private string $allowedFqcn;
18+
19+
/**
20+
* @param class-string $allowedFqcn
21+
*/
22+
public function __construct(string $allowedFqcn)
23+
{
24+
$this->allowedFqcn = $allowedFqcn;
25+
}
26+
27+
public function describe(ClassDescription $theClass, string $because = ''): Description
28+
{
29+
return new Description("should inherit from: $this->allowedFqcn", $because);
30+
}
31+
32+
public function evaluate(ClassDescription $theClass, Violations $violations, string $because = ''): void
33+
{
34+
if (!is_a($theClass->getFQCN(), $this->allowedFqcn, true)) {
35+
$violation = Violation::create(
36+
$theClass->getFQCN(),
37+
ViolationMessage::selfExplanatory($this->describe($theClass, $because)),
38+
$theClass->getFilePath()
39+
);
40+
41+
$violations->add($violation);
42+
}
43+
}
44+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses;
6+
7+
use Arkitect\Analyzer\ClassDescriptionBuilder;
8+
use Arkitect\Expression\ForClasses\IsA;
9+
use Arkitect\Rules\Violations;
10+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Animal\Dog;
11+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\Banana;
12+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\CavendishBanana;
13+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\DwarfCavendishBanana;
14+
use Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit\FruitInterface;
15+
use PHPUnit\Framework\TestCase;
16+
17+
final class IsATest extends TestCase
18+
{
19+
public function test_it_should_have_no_violation_when_it_implements(): void
20+
{
21+
$interface = FruitInterface::class;
22+
$isA = new IsA($interface);
23+
$classDescription = (new ClassDescriptionBuilder())
24+
->setFilePath('src/Foo.php')
25+
->setClassName(CavendishBanana::class)
26+
->addInterface($interface, 10)
27+
->build();
28+
29+
$violations = new Violations();
30+
$isA->evaluate($classDescription, $violations, '');
31+
32+
self::assertEquals(0, $violations->count());
33+
}
34+
35+
public function test_it_should_have_no_violation_when_it_extends(): void
36+
{
37+
$class = Banana::class;
38+
$isA = new IsA($class);
39+
$classDescription = (new ClassDescriptionBuilder())
40+
->setFilePath('src/Foo.php')
41+
->setClassName(DwarfCavendishBanana::class)
42+
->addExtends($class, 10)
43+
->build();
44+
45+
$violations = new Violations();
46+
$isA->evaluate($classDescription, $violations, '');
47+
48+
self::assertEquals(0, $violations->count());
49+
}
50+
51+
public function test_it_should_have_violation_if_it_doesnt_extend(): void
52+
{
53+
$interface = FruitInterface::class;
54+
$isA = new IsA($interface);
55+
$classDescription = (new ClassDescriptionBuilder())
56+
->setFilePath('src/Foo.php')
57+
->setClassName(Dog::class)
58+
->build();
59+
60+
$violations = new Violations();
61+
$isA->evaluate($classDescription, $violations, '');
62+
63+
self::assertEquals(1, $violations->count());
64+
self::assertEquals(
65+
"should inherit from: $interface",
66+
$isA->describe($classDescription, '')->toString()
67+
);
68+
}
69+
70+
public function test_it_should_have_violation_if_it_doesnt_implement(): void
71+
{
72+
$class = Banana::class;
73+
$isA = new IsA($class);
74+
$classDescription = (new ClassDescriptionBuilder())
75+
->setFilePath('src/Foo.php')
76+
->setClassName(Dog::class)
77+
->build();
78+
79+
$violations = new Violations();
80+
$isA->evaluate($classDescription, $violations, '');
81+
82+
self::assertEquals(1, $violations->count());
83+
self::assertEquals(
84+
"should inherit from: $class",
85+
$isA->describe($classDescription, '')->toString()
86+
);
87+
}
88+
}
89+
90+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Animal;
91+
92+
final class Dog
93+
{
94+
}
95+
96+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\IsOneOfTest\Fruit;
97+
98+
interface FruitInterface
99+
{
100+
}
101+
102+
class Banana implements FruitInterface
103+
{
104+
}
105+
106+
class CavendishBanana extends Banana
107+
{
108+
}
109+
110+
final class DwarfCavendishBanana extends CavendishBanana
111+
{
112+
}

0 commit comments

Comments
 (0)