Skip to content

Commit cafad1a

Browse files
Herberto Gracahgraca
Herberto Graca
authored andcommitted
Create a IsA expression
This will allow for testing if a class extends or implements another code unit, anywhere in the inheritance tree.
1 parent 976c200 commit cafad1a

File tree

7 files changed

+188
-0
lines changed

7 files changed

+188
-0
lines changed

src/Expression/ForClasses/IsA.php

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 string[] */
17+
private $allowedFqcnList;
18+
19+
public function __construct(string ...$allowedFqcnList)
20+
{
21+
$this->allowedFqcnList = $allowedFqcnList;
22+
}
23+
24+
public function describe(ClassDescription $theClass, string $because): Description
25+
{
26+
$allowedFqcnList = implode(', ', $this->allowedFqcnList);
27+
28+
return new Description("should inherit from one of: $allowedFqcnList", $because);
29+
}
30+
31+
public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void
32+
{
33+
if (!$this->isA($theClass, ...$this->allowedFqcnList)) {
34+
$violation = Violation::create(
35+
$theClass->getFQCN(),
36+
ViolationMessage::selfExplanatory($this->describe($theClass, $because))
37+
);
38+
39+
$violations->add($violation);
40+
}
41+
}
42+
43+
private function isA(ClassDescription $theClass, string ...$allowedFqcnList): bool
44+
{
45+
foreach ($allowedFqcnList as $allowedFqcn) {
46+
if (is_a($theClass->getFQCN(), $allowedFqcn, true)) {
47+
return true;
48+
}
49+
}
50+
51+
return false;
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
class Banana implements FruitInterface
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
class CavendishBanana extends Banana
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final class Dog
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final class DwarfCavendishBanana extends CavendishBanana
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
interface FruitInterface
8+
{
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses;
6+
7+
use Arkitect\Analyzer\ClassDescription;
8+
use Arkitect\Analyzer\FullyQualifiedClassName;
9+
use Arkitect\Expression\ForClasses\IsA;
10+
use Arkitect\Rules\Violations;
11+
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\Banana;
12+
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\CavendishBanana;
13+
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\Dog;
14+
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\DwarfCavendishBanana;
15+
use Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses\FruitInterface;
16+
use PHPUnit\Framework\TestCase;
17+
18+
final class IsATest extends TestCase
19+
{
20+
public function test_it_should_have_no_violation_when_it_implements(): void
21+
{
22+
$interface = FruitInterface::class;
23+
$isA = new IsA($interface);
24+
$classDescription = new ClassDescription(
25+
FullyQualifiedClassName::fromString(CavendishBanana::class),
26+
[],
27+
[FullyQualifiedClassName::fromString($interface)],
28+
null,
29+
false,
30+
false,
31+
false,
32+
false,
33+
false
34+
);
35+
36+
$violations = new Violations();
37+
$isA->evaluate($classDescription, $violations, '');
38+
39+
self::assertEquals(0, $violations->count());
40+
}
41+
42+
public function test_it_should_have_no_violation_when_it_extends(): void
43+
{
44+
$class = Banana::class;
45+
$isA = new IsA($class);
46+
$classDescription = new ClassDescription(
47+
FullyQualifiedClassName::fromString(DwarfCavendishBanana::class),
48+
[],
49+
[],
50+
FullyQualifiedClassName::fromString($class),
51+
false,
52+
false,
53+
false,
54+
false,
55+
false
56+
);
57+
58+
$violations = new Violations();
59+
$isA->evaluate($classDescription, $violations, '');
60+
61+
self::assertEquals(0, $violations->count());
62+
}
63+
64+
public function test_it_should_have_violation_if_it_doesnt_extend_nor_implement(): void
65+
{
66+
$interface = FruitInterface::class;
67+
$class = Banana::class;
68+
$isA = new IsA($class, $interface);
69+
$classDescription = new ClassDescription(
70+
FullyQualifiedClassName::fromString(Dog::class),
71+
[],
72+
[],
73+
null,
74+
false,
75+
false,
76+
false,
77+
false,
78+
false
79+
);
80+
81+
$violations = new Violations();
82+
$isA->evaluate($classDescription, $violations, '');
83+
84+
self::assertEquals(1, $violations->count());
85+
self::assertEquals(
86+
"should inherit from one of: $class, $interface",
87+
$isA->describe($classDescription, '')->toString()
88+
);
89+
}
90+
}

0 commit comments

Comments
 (0)