Skip to content

Commit 3ee5a0d

Browse files
committed
AsymmetricVisibility wip
1 parent 1dd1cad commit 3ee5a0d

File tree

3 files changed

+158
-4
lines changed

3 files changed

+158
-4
lines changed

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+
$read = $param->getVisibility(write: false);
408+
$write = $param->getVisibility(write: true);
409+
return $write
410+
? ($read ? "$read $write(set)" : "$write(set)")
411+
: $read ?? '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: 65 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,75 @@
1719
*/
1820
trait PropertyLike
1921
{
20-
use VisibilityAware;
21-
22+
/** @var array{?string, ?string} */
23+
private array $visibility = [null, 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 $read public|protected|private
32+
* @param ?string $write public|protected|private
33+
*/
34+
public function setVisibility(?string $read, ?string $write = null): static
35+
{
36+
if (!in_array($read, [Modifier::Public, Modifier::Protected, Modifier::Private, null], true)
37+
|| !in_array($write, [Modifier::Public, Modifier::Protected, Modifier::Private, null], true)) {
38+
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
39+
}
40+
41+
$this->visibility = [$read, $write];
42+
return $this;
43+
}
44+
45+
46+
public function getVisibility(bool $write = false): ?string
47+
{
48+
return $this->visibility[$write];
49+
}
50+
51+
52+
public function setPublic(bool $write = false): static
53+
{
54+
$this->visibility[$write] = Modifier::Public;
55+
return $this;
56+
}
57+
58+
59+
public function isPublic(bool $write = false): bool
60+
{
61+
return in_array($this->visibility[$write], [Modifier::Public, null], true);
62+
}
63+
64+
65+
public function setProtected(bool $write = false): static
66+
{
67+
$this->visibility[$write] = Modifier::Protected;
68+
return $this;
69+
}
70+
71+
72+
public function isProtected(bool $write = false): bool
73+
{
74+
return $this->visibility[$write] === Modifier::Protected;
75+
}
76+
77+
78+
public function setPrivate(bool $write = false): static
79+
{
80+
$this->visibility[$write] = Modifier::Private;
81+
return $this;
82+
}
83+
84+
85+
public function isPrivate(bool $write = false): bool
86+
{
87+
return $this->visibility[$write] === Modifier::Private;
88+
}
89+
90+
2891
public function setReadOnly(bool $state = true): static
2992
{
3093
$this->readOnly = $state;
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());
21+
Assert::true($default->isPublic(write: true));
22+
Assert::null($default->getVisibility());
23+
Assert::null($default->getVisibility(write: true));
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(write: true));
31+
Assert::true($restricted->isPrivate(write: true));
32+
Assert::null($restricted->getVisibility());
33+
Assert::same('private', $restricted->getVisibility(write: true));
34+
35+
// Public with protected setter using individual methods
36+
$mixed = $class->addProperty('third')
37+
->setType('string')
38+
->setPublic()
39+
->setProtected(write: true);
40+
Assert::true($mixed->isPublic());
41+
Assert::false($mixed->isPublic(write: true));
42+
Assert::true($mixed->isProtected(write: true));
43+
Assert::same('public', $mixed->getVisibility());
44+
Assert::same('protected', $mixed->getVisibility(write: true));
45+
46+
// Protected with private setter
47+
$nested = $class->addProperty('fourth')
48+
->setType('string')
49+
->setProtected()
50+
->setPrivate(write: true);
51+
Assert::false($nested->isPublic());
52+
Assert::true($nested->isProtected());
53+
Assert::true($nested->isPrivate(write: true));
54+
Assert::same('protected', $nested->getVisibility());
55+
Assert::same('private', $nested->getVisibility(write: true));
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)