Skip to content

Commit d369678

Browse files
committed
Deduplicate double entries in a compound
When writing a compound type (i.e. `int|int|bool`) you can use duplicates. These duplicates have no function and will only add extra complexity for consuming applications. As such I have introduced deduplication into the compound type and improved type safety by means of a private `add` function. The new `add` function is private by design as we want our Value Objects immutable and this is only used internally for type checking and deduplication.
1 parent 4c18be4 commit d369678

File tree

2 files changed

+69
-14
lines changed

2 files changed

+69
-14
lines changed

src/Types/Compound.php

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,18 @@
2929
final class Compound implements Type, IteratorAggregate
3030
{
3131
/** @var Type[] */
32-
private $types;
32+
private $types = [];
3333

3434
/**
3535
* Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
3636
*
3737
* @param Type[] $types
38-
*
39-
* @throws InvalidArgumentException When types are not all instance of Type.
4038
*/
4139
public function __construct(array $types)
4240
{
4341
foreach ($types as $type) {
44-
/** @psalm-suppress RedundantConditionGivenDocblockType */
45-
if (!$type instanceof Type) {
46-
throw new InvalidArgumentException('A compound type can only have other types as elements');
47-
}
42+
$this->add($type);
4843
}
49-
50-
$this->types = $types;
5144
}
5245

5346
/**
@@ -70,6 +63,21 @@ public function has(int $index) : bool
7063
return isset($this->types[$index]);
7164
}
7265

66+
/**
67+
* Tests if this compound type contains the given type.
68+
*/
69+
public function contains(Type $type): bool
70+
{
71+
foreach ($this->types as $typePart) {
72+
// if the type is duplicate; do not add it
73+
if ((string) $typePart === (string) $type) {
74+
return true;
75+
}
76+
}
77+
78+
return false;
79+
}
80+
7381
/**
7482
* Returns a rendered output of the Type as it would be used in a DocBlock.
7583
*/
@@ -85,4 +93,14 @@ public function getIterator()
8593
{
8694
return new ArrayIterator($this->types);
8795
}
96+
97+
private function add(Type $type): void
98+
{
99+
// if the type is duplicate; do not add it
100+
if ($this->contains($type)) {
101+
return;
102+
}
103+
104+
$this->types[] = $type;
105+
}
88106
}

tests/unit/Types/CompoundTest.php

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,14 @@
1818
/**
1919
* @coversDefaultClass \phpDocumentor\Reflection\Types\Compound
2020
*/
21-
class CompoundTest extends TestCase
21+
final class CompoundTest extends TestCase
2222
{
2323
/**
2424
* @covers ::__construct
2525
*/
2626
public function testCompoundCannotBeConstructedFromType() : void
2727
{
28-
$this->expectException('InvalidArgumentException');
29-
$this->expectExceptionMessage('A compound type can only have other types as elements');
28+
$this->expectException(\TypeError::class);
3029
new Compound(['foo']);
3130
}
3231

@@ -61,7 +60,7 @@ public function testCompoundGetNotExistingType() : void
6160
*
6261
* @covers ::has
6362
*/
64-
public function testCompoundHasType() : void
63+
public function testCompoundHasIndex() : void
6564
{
6665
$this->assertTrue((new Compound([new Integer()]))->has(0));
6766
}
@@ -71,11 +70,34 @@ public function testCompoundHasType() : void
7170
*
7271
* @covers ::has
7372
*/
74-
public function testCompoundHasNotExistingType() : void
73+
public function testCompoundDoesNotHasIndex() : void
7574
{
7675
$this->assertFalse((new Compound([]))->has(0));
7776
}
7877

78+
/**
79+
* @uses \phpDocumentor\Reflection\Types\Compound::__construct
80+
* @uses \phpDocumentor\Reflection\Types\Integer
81+
*
82+
* @covers ::contains
83+
*/
84+
public function testCompoundContainsType() : void
85+
{
86+
$this->assertTrue((new Compound([new Integer()]))->contains(new Integer()));
87+
}
88+
89+
/**
90+
* @uses \phpDocumentor\Reflection\Types\Compound::__construct
91+
* @uses \phpDocumentor\Reflection\Types\Integer
92+
* @uses \phpDocumentor\Reflection\Types\String_
93+
*
94+
* @covers ::contains
95+
*/
96+
public function testCompoundDoesNotContainType() : void
97+
{
98+
$this->assertFalse((new Compound([new Integer()]))->contains(new String_()));
99+
}
100+
79101
/**
80102
* @uses \phpDocumentor\Reflection\Types\Integer
81103
* @uses \phpDocumentor\Reflection\Types\Boolean
@@ -88,6 +110,21 @@ public function testCompoundCanBeConstructedAndStringifiedCorrectly() : void
88110
$this->assertSame('int|bool', (string) (new Compound([new Integer(), new Boolean()])));
89111
}
90112

113+
/**
114+
* @uses \phpDocumentor\Reflection\Types\Integer
115+
* @uses \phpDocumentor\Reflection\Types\Boolean
116+
*
117+
* @covers ::__construct
118+
* @covers ::__toString
119+
*/
120+
public function testCompoundDoesNotContainDuplicates() : void
121+
{
122+
$compound = new Compound([new Integer(), new Integer(), new Boolean()]);
123+
124+
$this->assertCount(2, iterator_to_array($compound));
125+
$this->assertSame('int|bool', (string) $compound);
126+
}
127+
91128
/**
92129
* @uses \phpDocumentor\Reflection\Types\Compound::__construct
93130
* @uses \phpDocumentor\Reflection\Types\Integer

0 commit comments

Comments
 (0)