Skip to content

Commit 919a24a

Browse files
committed
AsymmetricVisibility wip
1 parent 100fa20 commit 919a24a

File tree

6 files changed

+304
-4
lines changed

6 files changed

+304
-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+
$get = $param->getVisibility();
408+
$set = $param->getSetterVisibility();
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/PromotedParameter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818
final class PromotedParameter extends Parameter
1919
{
20-
use Traits\VisibilityAware;
20+
use Traits\AsymmetricVisibilityAware;
2121

2222
private bool $readOnly = false;
2323

src/PhpGenerator/Property.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
final class Property
2020
{
2121
use Traits\NameAware;
22-
use Traits\VisibilityAware;
22+
use Traits\AsymmetricVisibilityAware;
2323
use Traits\CommentAware;
2424
use Traits\AttributeAware;
2525

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\PhpGenerator\Traits;
11+
12+
use Nette;
13+
use Nette\PhpGenerator\ClassLike;
14+
15+
16+
/**
17+
* @internal
18+
*/
19+
trait AsymmetricVisibilityAware
20+
{
21+
private ?string $visibility = null;
22+
private ?string $setVisibility = null;
23+
24+
25+
/**
26+
* @param ?string $getter public|protected|private
27+
* @param ?string $setter public|protected|private
28+
*/
29+
public function setVisibility(?string $getter, ?string $setter = null): static
30+
{
31+
if (!in_array($getter, [ClassLike::VisibilityPublic, ClassLike::VisibilityProtected, ClassLike::VisibilityPrivate, null], true)
32+
|| !in_array($setter, [ClassLike::VisibilityPublic, ClassLike::VisibilityProtected, ClassLike::VisibilityPrivate, null], true)) {
33+
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
34+
}
35+
36+
$this->visibility = $getter;
37+
$this->setVisibility = $setter;
38+
return $this;
39+
}
40+
41+
42+
public function getVisibility(): ?string
43+
{
44+
return $this->visibility;
45+
}
46+
47+
48+
public function getSetterVisibility(): ?string
49+
{
50+
return $this->setVisibility;
51+
}
52+
53+
54+
public function setPublic(): static
55+
{
56+
$this->visibility = ClassLike::VisibilityPublic;
57+
return $this;
58+
}
59+
60+
61+
public function isPublic(): bool
62+
{
63+
return $this->visibility === ClassLike::VisibilityPublic || $this->visibility === null;
64+
}
65+
66+
67+
public function setPublicSet(): static
68+
{
69+
$this->setVisibility = ClassLike::VisibilityPublic;
70+
return $this;
71+
}
72+
73+
74+
public function isPublicSet(): bool
75+
{
76+
$visibility = $this->setVisibility ?? $this->visibility;
77+
return $visibility === ClassLike::VisibilityPublic || $visibility === null;
78+
}
79+
80+
81+
public function setProtected(): static
82+
{
83+
$this->visibility = ClassLike::VisibilityProtected;
84+
return $this;
85+
}
86+
87+
88+
public function isProtected(): bool
89+
{
90+
return $this->visibility === ClassLike::VisibilityProtected;
91+
}
92+
93+
94+
public function setProtectedSet(): static
95+
{
96+
$this->setVisibility = ClassLike::VisibilityProtected;
97+
return $this;
98+
}
99+
100+
101+
public function isProtectedSet(): bool
102+
{
103+
return ($this->setVisibility ?? $this->visibility) === ClassLike::VisibilityProtected;
104+
}
105+
106+
107+
public function setPrivate(): static
108+
{
109+
$this->visibility = ClassLike::VisibilityPrivate;
110+
return $this;
111+
}
112+
113+
114+
public function isPrivate(): bool
115+
{
116+
return $this->visibility === ClassLike::VisibilityPrivate;
117+
}
118+
119+
120+
public function setPrivateSet(): static
121+
{
122+
$this->setVisibility = ClassLike::VisibilityPrivate;
123+
return $this;
124+
}
125+
126+
127+
public function isPrivateSet(): bool
128+
{
129+
return ($this->setVisibility ?? $this->visibility) === ClassLike::VisibilityPrivate;
130+
}
131+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/**
4+
* Test: Property 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->isPublicSet());
22+
Assert::null($default->getVisibility());
23+
Assert::null($default->getSetterVisibility());
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->isPublicSet());
31+
Assert::true($restricted->isPrivateSet());
32+
Assert::null($restricted->getVisibility());
33+
Assert::same('private', $restricted->getSetterVisibility());
34+
35+
// Public with protected setter using individual methods
36+
$mixed = $class->addProperty('third')
37+
->setType('string')
38+
->setPublic()
39+
->setProtectedSet();
40+
Assert::true($mixed->isPublic());
41+
Assert::false($mixed->isPublicSet());
42+
Assert::true($mixed->isProtectedSet());
43+
Assert::same('public', $mixed->getVisibility());
44+
Assert::same('protected', $mixed->getSetterVisibility());
45+
46+
// Protected with private setter
47+
$nested = $class->addProperty('fourth')
48+
->setType('string')
49+
->setProtected()
50+
->setPrivateSet();
51+
Assert::false($nested->isPublic());
52+
Assert::true($nested->isProtected());
53+
Assert::true($nested->isPrivateSet());
54+
Assert::same('protected', $nested->getVisibility());
55+
Assert::same('private', $nested->getSetterVisibility());
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);
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/**
4+
* Test: Property 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 (public)
18+
$default = $class->addProperty('first')
19+
->setType('string');
20+
Assert::true($default->isPublic());
21+
Assert::false($default->isProtected());
22+
Assert::false($default->isPrivate());
23+
Assert::null($default->getVisibility());
24+
25+
// Explicit public
26+
$public = $class->addProperty('second')
27+
->setType('string')
28+
->setPublic();
29+
Assert::true($public->isPublic());
30+
Assert::false($public->isProtected());
31+
Assert::false($public->isPrivate());
32+
Assert::same('public', $public->getVisibility());
33+
34+
// Protected
35+
$protected = $class->addProperty('third')
36+
->setType('string')
37+
->setProtected();
38+
Assert::false($protected->isPublic());
39+
Assert::true($protected->isProtected());
40+
Assert::false($protected->isPrivate());
41+
Assert::same('protected', $protected->getVisibility());
42+
43+
// Private
44+
$private = $class->addProperty('fourth')
45+
->setType('string')
46+
->setPrivate();
47+
Assert::false($private->isPublic());
48+
Assert::false($private->isProtected());
49+
Assert::true($private->isPrivate());
50+
Assert::same('private', $private->getVisibility());
51+
52+
// Change visibility
53+
$changing = $class->addProperty('fifth')
54+
->setType('string')
55+
->setPublic();
56+
$changing->setVisibility('protected');
57+
Assert::false($changing->isPublic());
58+
Assert::true($changing->isProtected());
59+
Assert::false($changing->isPrivate());
60+
61+
// Test invalid visibility
62+
Assert::exception(
63+
fn() => $changing->setVisibility('invalid'),
64+
Nette\InvalidArgumentException::class,
65+
'Argument must be public|protected|private.',
66+
);
67+
68+
same(<<<'XX'
69+
class Demo
70+
{
71+
public string $first;
72+
public string $second;
73+
protected string $third;
74+
private string $fourth;
75+
protected string $fifth;
76+
}
77+
78+
XX, (string) $class);

0 commit comments

Comments
 (0)