Skip to content

Commit 68c0562

Browse files
committed
Add support for asymmetric visibility for properties
See xp-framework/compiler#183 (comment)
1 parent 90d4dd5 commit 68c0562

File tree

3 files changed

+109
-25
lines changed

3 files changed

+109
-25
lines changed

src/main/php/lang/reflection/Modifiers.class.php

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
11
<?php namespace lang\reflection;
22

3-
use lang\Value;
3+
use lang\{Value, IllegalArgumentException};
44

55
/**
66
* Type and member modifiers
77
*
88
* @test lang.reflection.unittest.ModifiersTest
99
*/
1010
class Modifiers implements Value {
11-
const IS_STATIC = MODIFIER_STATIC;
12-
const IS_ABSTRACT = MODIFIER_ABSTRACT;
13-
const IS_FINAL = MODIFIER_FINAL;
14-
const IS_PUBLIC = MODIFIER_PUBLIC;
15-
const IS_PROTECTED = MODIFIER_PROTECTED;
16-
const IS_PRIVATE = MODIFIER_PRIVATE;
17-
const IS_READONLY = 0x0080; // XP 10.13: MODIFIER_READONLY
18-
const IS_NATIVE = 0xF000;
11+
const IS_STATIC = MODIFIER_STATIC;
12+
const IS_ABSTRACT = MODIFIER_ABSTRACT;
13+
const IS_FINAL = MODIFIER_FINAL;
14+
const IS_PUBLIC = MODIFIER_PUBLIC;
15+
const IS_PROTECTED = MODIFIER_PROTECTED;
16+
const IS_PRIVATE = MODIFIER_PRIVATE;
17+
const IS_READONLY = MODIFIER_READONLY;
18+
const IS_PRIVATE_SET = 0x0400;
19+
const IS_PROTECTED_SET = 0x0800;
20+
const IS_PUBLIC_SET = 0x1000;
21+
const IS_NATIVE = 0x10000;
1922

2023
private static $names= [
21-
'public' => self::IS_PUBLIC,
22-
'protected' => self::IS_PROTECTED,
23-
'private' => self::IS_PRIVATE,
24-
'static' => self::IS_STATIC,
25-
'final' => self::IS_FINAL,
26-
'abstract' => self::IS_ABSTRACT,
27-
'native' => self::IS_NATIVE,
28-
'readonly' => self::IS_READONLY,
24+
'public' => self::IS_PUBLIC,
25+
'protected' => self::IS_PROTECTED,
26+
'private' => self::IS_PRIVATE,
27+
'static' => self::IS_STATIC,
28+
'final' => self::IS_FINAL,
29+
'abstract' => self::IS_ABSTRACT,
30+
'native' => self::IS_NATIVE,
31+
'readonly' => self::IS_READONLY,
32+
'private(set)' => self::IS_PRIVATE_SET,
33+
'protected(set)' => self::IS_PROTECTED_SET,
34+
'public(set)' => self::IS_PUBLIC_SET,
2935
];
3036
private $bits;
3137

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

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

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

111-
/** @return bool */
112-
public function isPrivate() { return 0 !== ($this->bits & self::IS_PRIVATE); }
117+
/**
118+
* Gets whether these modifiers are public in regard to the specified hook
119+
*
120+
* @param ?string $hook
121+
* @return bool
122+
* @throws lang.IllegalArgumentException
123+
*/
124+
public function isPublic($hook= 'get') {
125+
switch ($hook) {
126+
case 'get': return 0 !== ($this->bits & self::IS_PUBLIC);
127+
case 'set': return 0 !== ($this->bits & self::IS_PUBLIC_SET);
128+
default: throw new IllegalArgumentException('Unknown hook '.$hook);
129+
}
130+
}
113131

114-
/** @return bool */
115-
public function isNative() { return 0 !== ($this->bits & self::IS_NATIVE); }
132+
/**
133+
* Gets whether these modifiers are protected in regard to the specified hook
134+
*
135+
* @param ?string $hook
136+
* @return bool
137+
* @throws lang.IllegalArgumentException
138+
*/
139+
public function isProtected($hook= 'get') {
140+
switch ($hook) {
141+
case 'get': return 0 !== ($this->bits & self::IS_PROTECTED);
142+
case 'set': return 0 !== ($this->bits & self::IS_PROTECTED_SET);
143+
default: throw new IllegalArgumentException('Unknown hook '.$hook);
144+
}
145+
}
116146

117-
/** @return bool */
118-
public function isReadonly() { return 0 !== ($this->bits & self::IS_READONLY); }
147+
/**
148+
* Gets whether these modifiers are private in regard to the specified hook
149+
*
150+
* @param ?string $hook
151+
* @return bool
152+
* @throws lang.IllegalArgumentException
153+
*/
154+
public function isPrivate($hook= 'get') {
155+
switch ($hook) {
156+
case 'get': return 0 !== ($this->bits & self::IS_PRIVATE);
157+
case 'set': return 0 !== ($this->bits & self::IS_PRIVATE_SET);
158+
default: throw new IllegalArgumentException('Unknown hook '.$hook);
159+
}
160+
}
119161

120162
/**
121163
* Compares a given value to this modifiers instance

src/test/php/lang/reflection/unittest/ModifiersTest.class.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ private function cases() {
1515
yield [Modifiers::IS_PRIVATE, 'private'];
1616
yield [Modifiers::IS_NATIVE, 'native'];
1717
yield [Modifiers::IS_READONLY, 'readonly'];
18+
yield [Modifiers::IS_PRIVATE_SET, 'private(set)'];
19+
yield [Modifiers::IS_PROTECTED_SET, 'protected(set)'];
20+
yield [Modifiers::IS_PUBLIC_SET, 'public(set)'];
1821
yield [Modifiers::IS_FINAL | Modifiers::IS_PUBLIC, 'public final'];
1922
yield [Modifiers::IS_ABSTRACT | Modifiers::IS_PUBLIC, 'public abstract'];
2023
yield [Modifiers::IS_ABSTRACT | Modifiers::IS_PROTECTED, 'protected abstract'];
@@ -83,6 +86,36 @@ public function isReadonly($input, $expected) {
8386
Assert::equals($expected, (new Modifiers($input))->isReadonly());
8487
}
8588

89+
#[Test, Values([['public(set)', true], ['public', true]])]
90+
public function isPublicGet($input, $expected) {
91+
Assert::equals($expected, (new Modifiers($input))->isPublic('get'));
92+
}
93+
94+
#[Test, Values([['protected(set)', false], ['protected', true]])]
95+
public function isProtectedGet($input, $expected) {
96+
Assert::equals($expected, (new Modifiers($input))->isProtected('get'));
97+
}
98+
99+
#[Test, Values([['private(set)', false], ['private', true]])]
100+
public function isPrivateGet($input, $expected) {
101+
Assert::equals($expected, (new Modifiers($input))->isPrivate('get'));
102+
}
103+
104+
#[Test, Values([['public(set)', true], ['public', false]])]
105+
public function isPublicSet($input, $expected) {
106+
Assert::equals($expected, (new Modifiers($input))->isPublic('set'));
107+
}
108+
109+
#[Test, Values([['protected(set)', true], ['protected', false]])]
110+
public function isProtectedSet($input, $expected) {
111+
Assert::equals($expected, (new Modifiers($input))->isProtected('set'));
112+
}
113+
114+
#[Test, Values([['private(set)', true], ['private', false]])]
115+
public function isPrivateSet($input, $expected) {
116+
Assert::equals($expected, (new Modifiers($input))->isPrivate('set'));
117+
}
118+
86119
#[Test]
87120
public function public_modifier_default_no_arg() {
88121
Assert::true((new Modifiers())->isPublic());

src/test/php/lang/reflection/unittest/PropertiesTest.class.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,13 @@ public function set_accessing_failed_exceptions_target_member() {
227227
Assert::equals($t->property('fixture'), $expected->target());
228228
}
229229
}
230+
231+
#[Test, Runtime(php: '>=8.4')]
232+
public function asymmetric_visibility() {
233+
$t= $this->declare('{ public private(set) string $fixture; }');
234+
Assert::equals(
235+
'public private(set) string $name',
236+
$t->property('fixture')->toString()
237+
);
238+
}
230239
}

0 commit comments

Comments
 (0)