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 9fa3236

Browse files
committed
added support for asymmetric visibility
1 parent b9b02a3 commit 9fa3236

File tree

5 files changed

+227
-4
lines changed

5 files changed

+227
-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(PropertyAccessMode::Get);
408+
$set = $param->getVisibility(PropertyAccessMode::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/PropertyAccessMode.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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;
11+
12+
use Nette;
13+
14+
15+
/**
16+
* Property access mode.
17+
*/
18+
/*enum*/ final class PropertyAccessMode
19+
{
20+
use Nette\StaticClass;
21+
22+
public const Set = 'set';
23+
public const Get = 'get';
24+
25+
26+
/** @internal */
27+
public static function from(string $value): string
28+
{
29+
return $value === self::Set || $value === self::Get
30+
? $value
31+
: throw new \ValueError("'$value' is not a valid value of access mode");
32+
}
33+
}

‎src/PhpGenerator/Traits/PropertyLike.php

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,91 @@
99

1010
namespace Nette\PhpGenerator\Traits;
1111

12+
use Nette\PhpGenerator\PropertyAccessMode;
1213
use Nette\PhpGenerator\PropertyHook;
1314
use Nette\PhpGenerator\PropertyHookType;
15+
use Nette\PhpGenerator\Visibility;
1416

1517

1618
/**
1719
* @internal
1820
*/
1921
trait PropertyLike
2022
{
21-
use VisibilityAware;
22-
23+
/** @var array{'set' => ?string, 'get' => ?string} */
24+
privatearray$visibility = [PropertyAccessMode::Set => null, PropertyAccessMode::Get => null];
2325
private bool $readOnly = false;
2426

2527
/** @var array<string, ?PropertyHook> */
2628
private array $hooks = [PropertyHookType::Set => null, PropertyHookType::Get => null];
2729

2830

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

0 commit comments

Comments
(0)

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