Skip to content

Commit 7b00915

Browse files
author
Herberto Graca
committed
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 7b00915

File tree

7 files changed

+205
-0
lines changed

7 files changed

+205
-0
lines changed

src/Expression/ForClasses/IsA.php

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 (
47+
\is_a($theClass->getFQCN(), $allowedFqcn, true)
48+
|| \is_subclass_of($theClass->getFQCN(), $allowedFqcn)
49+
) {
50+
return true;
51+
}
52+
}
53+
54+
return false;
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
final class Dog
8+
{
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Unit\Expressions\ForClasses\DummyClasses;
6+
7+
interface FruitInterface
8+
{
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
/**
21+
* @test
22+
*/
23+
public function it_should_have_no_violation_when_it_implements(): void
24+
{
25+
$interface = FruitInterface::class;
26+
$isA = new IsA($interface);
27+
$classDescription = new ClassDescription(
28+
FullyQualifiedClassName::fromString(CavendishBanana::class),
29+
[],
30+
[FullyQualifiedClassName::fromString($interface)],
31+
null,
32+
false,
33+
false,
34+
false,
35+
false,
36+
false
37+
);
38+
39+
$violations = new Violations();
40+
$isA->evaluate($classDescription, $violations, '');
41+
42+
self::assertEquals(0, $violations->count());
43+
}
44+
45+
/**
46+
* @test
47+
*/
48+
public function it_should_have_no_violation_when_it_extends(): void
49+
{
50+
$class = Banana::class;
51+
$isA = new IsA($class);
52+
$classDescription = new ClassDescription(
53+
FullyQualifiedClassName::fromString(DwarfCavendishBanana::class),
54+
[],
55+
[],
56+
FullyQualifiedClassName::fromString($class),
57+
false,
58+
false,
59+
false,
60+
false,
61+
false
62+
);
63+
64+
$violations = new Violations();
65+
$isA->evaluate($classDescription, $violations, '');
66+
67+
self::assertEquals(0, $violations->count());
68+
}
69+
70+
/**
71+
* @test
72+
*/
73+
public function it_should_have_violation_if_it_doesnt_extend_nor_implement(): void
74+
{
75+
$interface = FruitInterface::class;
76+
$class = Banana::class;
77+
$isA = new IsA($class, $interface);
78+
$classDescription = new ClassDescription(
79+
FullyQualifiedClassName::fromString(Dog::class),
80+
[],
81+
[],
82+
null,
83+
false,
84+
false,
85+
false,
86+
false,
87+
false
88+
);
89+
90+
$violations = new Violations();
91+
$isA->evaluate($classDescription, $violations, '');
92+
93+
self::assertEquals(1, $violations->count());
94+
self::assertEquals(
95+
"should inherit from one of: $class, $interface",
96+
$isA->describe($classDescription, '')->toString()
97+
);
98+
}
99+
}

0 commit comments

Comments
 (0)