Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 deletions src/Domain/Visibility.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,40 @@
/**
* Member visibility level.
*/
enum Visibility: int implements Formattable
enum Visibility: string implements Formattable
{
case Private = 0;
case Protected = 1;
case Public = 2;
case Private = 'private';
case Protected = 'protected';
case Public = 'public';

/**
* Raises this visibility to the given floor, returning whichever is more
* restrictive (more public).
*/
public function atLeast(self $floor): self
{
return $this->rank() >= $floor->rank() ? $this : $floor;
}

public function isAccessibleFrom(self $minimumRequired): bool
{
return $this->value >= $minimumRequired->value;
return $this->rank() >= $minimumRequired->rank();
}

public function format(): string
{
return match ($this) {
self::Private => 'private',
self::Protected => 'protected',
self::Public => 'public',
};
return $this->value;
}

public static function fromString(string $visibility): self
/**
* Ordinal ranking used for accessibility comparisons: more public is higher.
*/
private function rank(): int
{
return match (strtolower($visibility)) {
'private' => self::Private,
'protected' => self::Protected,
default => self::Public,
return match ($this) {
self::Private => 0,
self::Protected => 1,
self::Public => 2,
};
}
}
8 changes: 4 additions & 4 deletions src/Resolution/TextFallbackHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ private function getInheritedMembers(
// A subclass cannot access its parent's private members, so never query the
// parent below Protected visibility (while still honoring an external Public
// access level).
$inheritedVisibility = Visibility::from(max($minVisibility->value, Visibility::Protected->value));
$inheritedVisibility = $minVisibility->atLeast(Visibility::Protected);

$methods = $this->memberResolver->getMethods($parentClassName, $inheritedVisibility, $filter);
foreach ($methods as $methodInfo) {
Expand Down Expand Up @@ -695,7 +695,7 @@ private function extractMethods(
$pattern = '/^\s*(public|protected|private)\s+(static\s+)?function\s+(\w+)\s*\(/m';
if (preg_match_all($pattern, $classContent, $matches, PREG_SET_ORDER) > 0) {
foreach ($matches as $match) {
$visibility = Visibility::fromString($match[1]);
$visibility = Visibility::from($match[1]);
if (!$visibility->isAccessibleFrom($minVisibility)) {
continue;
}
Expand Down Expand Up @@ -740,7 +740,7 @@ private function extractProperties(
$pattern = '/^\s*(public|protected|private)\s+(static\s+)?(readonly\s+)?(?:[\w\\\\|?]+\s+)?\$(\w+)/m';
if (preg_match_all($pattern, $classContent, $matches, PREG_SET_ORDER) > 0) {
foreach ($matches as $match) {
$visibility = Visibility::fromString($match[1]);
$visibility = Visibility::from($match[1]);
if (!$visibility->isAccessibleFrom($minVisibility)) {
continue;
}
Expand Down Expand Up @@ -782,7 +782,7 @@ private function extractConstants(
$pattern = '/^\s*(public|protected|private)?\s*const\s+(?:[\w\\\\|?]+\s+)?(\w+)\s*=/m';
if (preg_match_all($pattern, $classContent, $matches, PREG_SET_ORDER) > 0) {
foreach ($matches as $match) {
$visibility = ($match[1] !== '') ? Visibility::fromString($match[1]) : Visibility::Public;
$visibility = ($match[1] !== '') ? Visibility::from($match[1]) : Visibility::Public;
if (!$visibility->isAccessibleFrom($minVisibility)) {
continue;
}
Expand Down
58 changes: 35 additions & 23 deletions tests/Domain/VisibilityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,46 +39,58 @@ public function testIsAccessibleFrom(
}

/**
* @return array<string, array{Visibility, string}>
* @return array<string, array{Visibility, Visibility, Visibility}>
* @codeCoverageIgnore
*/
public static function formatProvider(): array
public static function atLeastProvider(): array
{
return [
'private' => [Visibility::Private, 'private'],
'protected' => [Visibility::Protected, 'protected'],
'public' => [Visibility::Public, 'public'],
'private raised to protected' => [
Visibility::Private, Visibility::Protected, Visibility::Protected,
],
'protected floored at protected' => [
Visibility::Protected, Visibility::Protected, Visibility::Protected,
],
'public unaffected by protected floor' => [
Visibility::Public, Visibility::Protected, Visibility::Public,
],
'private raised to public' => [
Visibility::Private, Visibility::Public, Visibility::Public,
],
'private unaffected by private floor' => [
Visibility::Private, Visibility::Private, Visibility::Private,
],
'protected unaffected by private floor' => [
Visibility::Protected, Visibility::Private, Visibility::Protected,
],
];
}

#[DataProvider('formatProvider')]
public function testFormat(Visibility $visibility, string $expected): void
{
self::assertSame($expected, $visibility->format());
#[DataProvider('atLeastProvider')]
public function testAtLeast(
Visibility $visibility,
Visibility $floor,
Visibility $expected,
): void {
self::assertSame($expected, $visibility->atLeast($floor));
}

/**
* @return array<string, array{string, Visibility}>
* @return array<string, array{Visibility, string}>
* @codeCoverageIgnore
*/
public static function fromStringProvider(): array
public static function formatProvider(): array
{
return [
'private lowercase' => ['private', Visibility::Private],
'private uppercase' => ['PRIVATE', Visibility::Private],
'private mixed' => ['Private', Visibility::Private],
'protected lowercase' => ['protected', Visibility::Protected],
'protected uppercase' => ['PROTECTED', Visibility::Protected],
'public lowercase' => ['public', Visibility::Public],
'public uppercase' => ['PUBLIC', Visibility::Public],
'empty string defaults to public' => ['', Visibility::Public],
'unknown defaults to public' => ['unknown', Visibility::Public],
'private' => [Visibility::Private, 'private'],
'protected' => [Visibility::Protected, 'protected'],
'public' => [Visibility::Public, 'public'],
];
}

#[DataProvider('fromStringProvider')]
public function testFromString(string $input, Visibility $expected): void
#[DataProvider('formatProvider')]
public function testFormat(Visibility $visibility, string $expected): void
{
self::assertSame($expected, Visibility::fromString($input));
self::assertSame($expected, $visibility->format());
}
}