Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ec8a95b

Browse files
committed
added support for asymmetric visibility
1 parent 6de4f01 commit ec8a95b

File tree

4 files changed

+205
-4
lines changed

4 files changed

+205
-4
lines changed

‎readme.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,38 @@ $class->addProperty('role')
713713
->setFinal();
714714
```
715715

716+
<!---->
717+
718+
Asymmetric Visibility
719+
---------------------
720+
721+
PHP 8.4 introduces asymmetric visibility for properties. You can set different access levels for reading and writing.
722+
The visibility can be set using either the `setVisibility()` method with two parameters, or by using `setPublic()`, `setProtected()`, or `setPrivate()` with the `mode` parameter that specifies whether the visibility applies to getting or setting the property. The default mode is 'get'.
723+
724+
```php
725+
$class = new Nette\PhpGenerator\ClassType('Demo');
726+
727+
$class->addProperty('name')
728+
->setType('string')
729+
->setVisibility('public', 'private'); // public for read, private for write
730+
731+
$class->addProperty('id')
732+
->setType('int')
733+
->setProtected('set'); // protected for write
734+
735+
echo $class;
736+
```
737+
738+
This generates:
739+
740+
```php
741+
class Demo
742+
{
743+
public private(set) string $name;
744+
745+
protected(set) int $id;
746+
}
747+
```
716748

717749
<!---->
718750

‎src/PhpGenerator/Printer.php‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ private function formatParameters(Closure|GlobalFunction|Method|PropertyHook $fu
344344
$this->printDocComment($param)
345345
. ($attrs ? ($multiline ? substr($attrs, 0, -1) . "\n" : $attrs) : '')
346346
. ($param instanceof PromotedParameter
347-
? ($param->getVisibility() ?: 'public') . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ''
347+
? $this->printPropertyVisibility($param) . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ''
348348
: '')
349349
. ltrim($this->printType($param->getType(), $param->isNullable()) . '')
350350
. ($param->isReference() ? '&' : '')
@@ -382,7 +382,7 @@ private function printProperty(Property $property, bool $readOnlyClass = false,
382382
$type = $property->getType();
383383
$def = ($property->isAbstract() && !$isInterface ? 'abstract ' : '')
384384
. ($property->isFinal() ? 'final ' : '')
385-
. ($property->getVisibility() ?: 'public')
385+
. $this->printPropertyVisibility($property)
386386
. ($property->isStatic() ? ' static' : '')
387387
. (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '')
388388
. ''
@@ -402,6 +402,16 @@ private function printProperty(Property $property, bool $readOnlyClass = false,
402402
}
403403

404404

405+
private function printPropertyVisibility(Property|PromotedParameter $param): string
406+
{
407+
$get = $param->getVisibility('get');
408+
$set = $param->getVisibility('set');
409+
return $set
410+
? ($get ? "$get$set(set)" : "$set(set)")
411+
: $get ?? 'public';
412+
}
413+
414+
405415
protected function printType(?string $type, bool $nullable): string
406416
{
407417
if ($type === null) {

‎src/PhpGenerator/Traits/PropertyLike.php‎

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace Nette\PhpGenerator\Traits;
1111

12+
use Nette;
13+
use Nette\PhpGenerator\Modifier;
1214
use Nette\PhpGenerator\PropertyHook;
1315

1416

@@ -17,14 +19,82 @@
1719
*/
1820
trait PropertyLike
1921
{
20-
use VisibilityAware;
21-
22+
/** @var array{'set' => ?string, 'get' => ?string} */
23+
privatearray$visibility = ['set' => null, 'get' => null];
2224
private bool $readOnly = false;
2325

2426
/** @var array<string, ?PropertyHook> */
2527
private array $hooks = ['set' => null, 'get' => null];
2628

2729

30+
/**
31+
* @param ?string $get public|protected|private
32+
* @param ?string $set public|protected|private
33+
*/
34+
public function setVisibility(?string $get, ?string $set = null): static
35+
{
36+
if (!in_array($get, [Modifier::Public, Modifier::Protected, Modifier::Private, null], true)
37+
|| !in_array($set, [Modifier::Public, Modifier::Protected, Modifier::Private, null], true)) {
38+
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
39+
}
40+
41+
$this->visibility = ['set' => $set, 'get' => $get];
42+
return $this;
43+
}
44+
45+
46+
/** @param 'set'|'get' $mode */
47+
public function getVisibility(string $mode = 'get'): ?string
48+
{
49+
return $this->visibility[$this->checkMode($mode)];
50+
}
51+
52+
53+
/** @param 'set'|'get' $mode */
54+
public function setPublic(string $mode = 'get'): static
55+
{
56+
$this->visibility[$this->checkMode($mode)] = Modifier::Public;
57+
return $this;
58+
}
59+
60+
61+
/** @param 'set'|'get' $mode */
62+
public function isPublic(string $mode = 'get'): bool
63+
{
64+
return in_array($this->visibility[$this->checkMode($mode)], [Modifier::Public, null], true);
65+
}
66+
67+
68+
/** @param 'set'|'get' $mode */
69+
public function setProtected(string $mode = 'get'): static
70+
{
71+
$this->visibility[$this->checkMode($mode)] = Modifier::Protected;
72+
return $this;
73+
}
74+
75+
76+
/** @param 'set'|'get' $mode */
77+
public function isProtected(string $mode = 'get'): bool
78+
{
79+
return $this->visibility[$this->checkMode($mode)] === Modifier::Protected;
80+
}
81+
82+
83+
/** @param 'set'|'get' $mode */
84+
public function setPrivate(string $mode = 'get'): static
85+
{
86+
$this->visibility[$this->checkMode($mode)] = Modifier::Private;
87+
return $this;
88+
}
89+
90+
91+
/** @param 'set'|'get' $mode */
92+
public function isPrivate(string $mode = 'get'): bool
93+
{
94+
return $this->visibility[$this->checkMode($mode)] === Modifier::Private;
95+
}
96+
97+
2898
public function setReadOnly(bool $state = true): static
2999
{
30100
$this->readOnly = $state;
@@ -79,4 +149,12 @@ public function hasHook(string|\PropertyHookType $type): bool
79149
{
80150
return isset($this->hooks[$type]);
81151
}
152+
153+
154+
private function checkMode(string $mode): string
155+
{
156+
return $mode === Modifier::Set || $mode === Modifier::Get
157+
? $mode
158+
: throw new Nette\InvalidArgumentException('Argument must be set|get.');
159+
}
82160
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/**
4+
* Test: PropertyLike asymmetric visibility
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\PhpGenerator\ClassType;
10+
use Tester\Assert;
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
$class = new ClassType('Demo');
16+
17+
// Default visibility
18+
$default = $class->addProperty('first')
19+
->setType('string');
20+
Assert::true($default->isPublic('get'));
21+
Assert::true($default->isPublic('set'));
22+
Assert::null($default->getVisibility());
23+
Assert::null($default->getVisibility('set'));
24+
25+
// Public with private setter
26+
$restricted = $class->addProperty('second')
27+
->setType('string')
28+
->setVisibility(null, 'private');
29+
Assert::true($restricted->isPublic());
30+
Assert::false($restricted->isPublic('set'));
31+
Assert::true($restricted->isPrivate('set'));
32+
Assert::null($restricted->getVisibility());
33+
Assert::same('private', $restricted->getVisibility('set'));
34+
35+
// Public with protected setter using individual methods
36+
$mixed = $class->addProperty('third')
37+
->setType('string')
38+
->setPublic()
39+
->setProtected('set');
40+
Assert::true($mixed->isPublic());
41+
Assert::false($mixed->isPublic('set'));
42+
Assert::true($mixed->isProtected('set'));
43+
Assert::same('public', $mixed->getVisibility());
44+
Assert::same('protected', $mixed->getVisibility('set'));
45+
46+
// Protected with private setter
47+
$nested = $class->addProperty('fourth')
48+
->setType('string')
49+
->setProtected()
50+
->setPrivate('set');
51+
Assert::false($nested->isPublic());
52+
Assert::true($nested->isProtected());
53+
Assert::true($nested->isPrivate('set'));
54+
Assert::same('protected', $nested->getVisibility());
55+
Assert::same('private', $nested->getVisibility('set'));
56+
57+
// Test invalid getter visibility
58+
Assert::exception(
59+
fn() => $default->setVisibility('invalid', 'public'),
60+
Nette\InvalidArgumentException::class,
61+
'Argument must be public|protected|private.',
62+
);
63+
64+
// Test invalid setter visibility
65+
Assert::exception(
66+
fn() => $default->setVisibility('public', 'invalid'),
67+
Nette\InvalidArgumentException::class,
68+
'Argument must be public|protected|private.',
69+
);
70+
71+
72+
same(<<<'XX'
73+
class Demo
74+
{
75+
public string $first;
76+
private(set) string $second;
77+
public protected(set) string $third;
78+
protected private(set) string $fourth;
79+
}
80+
81+
XX, (string) $class);

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /