Skip to content

Commit

Permalink
Add support for asymmetric visibility for properties
Browse files Browse the repository at this point in the history
  • Loading branch information
thekid committed Aug 24, 2024
1 parent 90d4dd5 commit 68c0562
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 25 deletions.
92 changes: 67 additions & 25 deletions src/main/php/lang/reflection/Modifiers.class.php
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
<?php namespace lang\reflection;

use lang\Value;
use lang\{Value, IllegalArgumentException};

/**
* Type and member modifiers
*
* @test lang.reflection.unittest.ModifiersTest
*/
class Modifiers implements Value {
const IS_STATIC = MODIFIER_STATIC;
const IS_ABSTRACT = MODIFIER_ABSTRACT;
const IS_FINAL = MODIFIER_FINAL;
const IS_PUBLIC = MODIFIER_PUBLIC;
const IS_PROTECTED = MODIFIER_PROTECTED;
const IS_PRIVATE = MODIFIER_PRIVATE;
const IS_READONLY = 0x0080; // XP 10.13: MODIFIER_READONLY
const IS_NATIVE = 0xF000;
const IS_STATIC = MODIFIER_STATIC;
const IS_ABSTRACT = MODIFIER_ABSTRACT;
const IS_FINAL = MODIFIER_FINAL;
const IS_PUBLIC = MODIFIER_PUBLIC;
const IS_PROTECTED = MODIFIER_PROTECTED;
const IS_PRIVATE = MODIFIER_PRIVATE;
const IS_READONLY = MODIFIER_READONLY;
const IS_PRIVATE_SET = 0x0400;
const IS_PROTECTED_SET = 0x0800;
const IS_PUBLIC_SET = 0x1000;
const IS_NATIVE = 0x10000;

private static $names= [
'public' => self::IS_PUBLIC,
'protected' => self::IS_PROTECTED,
'private' => self::IS_PRIVATE,
'static' => self::IS_STATIC,
'final' => self::IS_FINAL,
'abstract' => self::IS_ABSTRACT,
'native' => self::IS_NATIVE,
'readonly' => self::IS_READONLY,
'public' => self::IS_PUBLIC,
'protected' => self::IS_PROTECTED,
'private' => self::IS_PRIVATE,
'static' => self::IS_STATIC,
'final' => self::IS_FINAL,
'abstract' => self::IS_ABSTRACT,
'native' => self::IS_NATIVE,
'readonly' => self::IS_READONLY,
'private(set)' => self::IS_PRIVATE_SET,
'protected(set)' => self::IS_PROTECTED_SET,
'public(set)' => self::IS_PUBLIC_SET,
];
private $bits;

Expand Down Expand Up @@ -103,19 +109,55 @@ public function isAbstract() { return 0 !== ($this->bits & self::IS_ABSTRACT); }
public function isFinal() { return 0 !== ($this->bits & self::IS_FINAL); }

/** @return bool */
public function isPublic() { return 0 !== ($this->bits & self::IS_PUBLIC); }
public function isNative() { return 0 !== ($this->bits & self::IS_NATIVE); }

/** @return bool */
public function isProtected() { return 0 !== ($this->bits & self::IS_PROTECTED); }
public function isReadonly() { return 0 !== ($this->bits & self::IS_READONLY); }

/** @return bool */
public function isPrivate() { return 0 !== ($this->bits & self::IS_PRIVATE); }
/**
* Gets whether these modifiers are public in regard to the specified hook
*
* @param ?string $hook
* @return bool
* @throws lang.IllegalArgumentException
*/
public function isPublic($hook= 'get') {
switch ($hook) {
case 'get': return 0 !== ($this->bits & self::IS_PUBLIC);
case 'set': return 0 !== ($this->bits & self::IS_PUBLIC_SET);
default: throw new IllegalArgumentException('Unknown hook '.$hook);
}
}

/** @return bool */
public function isNative() { return 0 !== ($this->bits & self::IS_NATIVE); }
/**
* Gets whether these modifiers are protected in regard to the specified hook
*
* @param ?string $hook
* @return bool
* @throws lang.IllegalArgumentException
*/
public function isProtected($hook= 'get') {
switch ($hook) {
case 'get': return 0 !== ($this->bits & self::IS_PROTECTED);
case 'set': return 0 !== ($this->bits & self::IS_PROTECTED_SET);
default: throw new IllegalArgumentException('Unknown hook '.$hook);
}
}

/** @return bool */
public function isReadonly() { return 0 !== ($this->bits & self::IS_READONLY); }
/**
* Gets whether these modifiers are private in regard to the specified hook
*
* @param ?string $hook
* @return bool
* @throws lang.IllegalArgumentException
*/
public function isPrivate($hook= 'get') {
switch ($hook) {
case 'get': return 0 !== ($this->bits & self::IS_PRIVATE);
case 'set': return 0 !== ($this->bits & self::IS_PRIVATE_SET);
default: throw new IllegalArgumentException('Unknown hook '.$hook);
}
}

/**
* Compares a given value to this modifiers instance
Expand Down
33 changes: 33 additions & 0 deletions src/test/php/lang/reflection/unittest/ModifiersTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ private function cases() {
yield [Modifiers::IS_PRIVATE, 'private'];
yield [Modifiers::IS_NATIVE, 'native'];
yield [Modifiers::IS_READONLY, 'readonly'];
yield [Modifiers::IS_PRIVATE_SET, 'private(set)'];
yield [Modifiers::IS_PROTECTED_SET, 'protected(set)'];
yield [Modifiers::IS_PUBLIC_SET, 'public(set)'];
yield [Modifiers::IS_FINAL | Modifiers::IS_PUBLIC, 'public final'];
yield [Modifiers::IS_ABSTRACT | Modifiers::IS_PUBLIC, 'public abstract'];
yield [Modifiers::IS_ABSTRACT | Modifiers::IS_PROTECTED, 'protected abstract'];
Expand Down Expand Up @@ -83,6 +86,36 @@ public function isReadonly($input, $expected) {
Assert::equals($expected, (new Modifiers($input))->isReadonly());
}

#[Test, Values([['public(set)', true], ['public', true]])]
public function isPublicGet($input, $expected) {
Assert::equals($expected, (new Modifiers($input))->isPublic('get'));
}

#[Test, Values([['protected(set)', false], ['protected', true]])]
public function isProtectedGet($input, $expected) {
Assert::equals($expected, (new Modifiers($input))->isProtected('get'));
}

#[Test, Values([['private(set)', false], ['private', true]])]
public function isPrivateGet($input, $expected) {
Assert::equals($expected, (new Modifiers($input))->isPrivate('get'));
}

#[Test, Values([['public(set)', true], ['public', false]])]
public function isPublicSet($input, $expected) {
Assert::equals($expected, (new Modifiers($input))->isPublic('set'));
}

#[Test, Values([['protected(set)', true], ['protected', false]])]
public function isProtectedSet($input, $expected) {
Assert::equals($expected, (new Modifiers($input))->isProtected('set'));
}

#[Test, Values([['private(set)', true], ['private', false]])]
public function isPrivateSet($input, $expected) {
Assert::equals($expected, (new Modifiers($input))->isPrivate('set'));
}

#[Test]
public function public_modifier_default_no_arg() {
Assert::true((new Modifiers())->isPublic());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,13 @@ public function set_accessing_failed_exceptions_target_member() {
Assert::equals($t->property('fixture'), $expected->target());
}
}

#[Test, Runtime(php: '>=8.4')]
public function asymmetric_visibility() {
$t= $this->declare('{ public private(set) string $fixture; }');
Assert::equals(
'public private(set) string $name',
$t->property('fixture')->toString()
);
}
}

0 comments on commit 68c0562

Please sign in to comment.