Skip to content

Commit

Permalink
AsymmetricVisibility wip
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Nov 27, 2024
1 parent 3455866 commit a0a1ff3
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 4 deletions.
14 changes: 12 additions & 2 deletions src/PhpGenerator/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ private function formatParameters(Closure|GlobalFunction|Method|PropertyHook $fu
$this->printDocComment($param)
. ($attrs ? ($multiline ? substr($attrs, 0, -1) . "\n" : $attrs) : '')
. ($param instanceof PromotedParameter
? ($param->getVisibility() ?: 'public') . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ' '
? $this->printPropertyVisibility($param) . ($param->isReadOnly() && $param->getType() ? ' readonly' : '') . ' '
: '')
. ltrim($this->printType($param->getType(), $param->isNullable()) . ' ')
. ($param->isReference() ? '&' : '')
Expand Down Expand Up @@ -382,7 +382,7 @@ private function printProperty(Property $property, bool $readOnlyClass = false,
$type = $property->getType();
$def = ($property->isAbstract() && !$isInterface ? 'abstract ' : '')
. ($property->isFinal() ? 'final ' : '')
. ($property->getVisibility() ?: 'public')
. $this->printPropertyVisibility($property)
. ($property->isStatic() ? ' static' : '')
. (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '')
. ' '
Expand All @@ -402,6 +402,16 @@ private function printProperty(Property $property, bool $readOnlyClass = false,
}


private function printPropertyVisibility(Property|PromotedParameter $param): string
{
$read = $param->getVisibility(write: false);
$write = $param->getVisibility(write: true);
return $write
? ($read ? "$read $write(set)" : "$write(set)")
: $read ?? 'public';
}


protected function printType(?string $type, bool $nullable): string
{
if ($type === null) {
Expand Down
67 changes: 65 additions & 2 deletions src/PhpGenerator/Traits/PropertyLike.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace Nette\PhpGenerator\Traits;

use Nette;
use Nette\PhpGenerator\Modifier;
use Nette\PhpGenerator\PropertyHook;


Expand All @@ -17,14 +19,75 @@
*/
trait PropertyLike
{
use VisibilityAware;

/** @var array{?string, ?string} */
private array $visibility = [null, null];
private bool $readOnly = false;

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


/**
* @param ?string $read public|protected|private
* @param ?string $write public|protected|private
*/
public function setVisibility(?string $read, ?string $write = null): static
{
if (!in_array($read, [Modifier::Public, Modifier::Protected, Modifier::Private, null], true)
|| !in_array($write, [Modifier::Public, Modifier::Protected, Modifier::Private, null], true)) {
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
}

$this->visibility = [$read, $write];
return $this;
}


public function getVisibility(bool $write = false): ?string
{
return $this->visibility[$write];
}


public function setPublic(bool $write = false): static
{
$this->visibility[$write] = Modifier::Public;
return $this;
}


public function isPublic(bool $write = false): bool
{
return in_array($this->visibility[$write], [Modifier::Public, null], true);
}


public function setProtected(bool $write = false): static
{
$this->visibility[$write] = Modifier::Protected;
return $this;
}


public function isProtected(bool $write = false): bool
{
return $this->visibility[$write] === Modifier::Protected;
}


public function setPrivate(bool $write = false): static
{
$this->visibility[$write] = Modifier::Private;
return $this;
}


public function isPrivate(bool $write = false): bool
{
return $this->visibility[$write] === Modifier::Private;
}


public function setReadOnly(bool $state = true): static
{
$this->readOnly = $state;
Expand Down
81 changes: 81 additions & 0 deletions tests/PhpGenerator/Property.asymmetric-visiblity.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

/**
* Test: Property asymmetric visibility
*/

declare(strict_types=1);

use Nette\PhpGenerator\ClassType;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


$class = new ClassType('Demo');

// Default visibility
$default = $class->addProperty('first')
->setType('string');
Assert::true($default->isPublic());
Assert::true($default->isPublic(write: true));
Assert::null($default->getVisibility());
Assert::null($default->getVisibility(write: true));

// Public with private setter
$restricted = $class->addProperty('second')
->setType('string')
->setVisibility(null, 'private');
Assert::true($restricted->isPublic());
Assert::false($restricted->isPublic(write: true));
Assert::true($restricted->isPrivate(write: true));
Assert::null($restricted->getVisibility());
Assert::same('private', $restricted->getVisibility(write: true));

// Public with protected setter using individual methods
$mixed = $class->addProperty('third')
->setType('string')
->setPublic()
->setProtected(write: true);
Assert::true($mixed->isPublic());
Assert::false($mixed->isPublic(write: true));
Assert::true($mixed->isProtected(write: true));
Assert::same('public', $mixed->getVisibility());
Assert::same('protected', $mixed->getVisibility(write: true));

// Protected with private setter
$nested = $class->addProperty('fourth')
->setType('string')
->setProtected()
->setPrivate(write: true);
Assert::false($nested->isPublic());
Assert::true($nested->isProtected());
Assert::true($nested->isPrivate(write: true));
Assert::same('protected', $nested->getVisibility());
Assert::same('private', $nested->getVisibility(write: true));

// Test invalid getter visibility
Assert::exception(
fn() => $default->setVisibility('invalid', 'public'),
Nette\InvalidArgumentException::class,
'Argument must be public|protected|private.',
);

// Test invalid setter visibility
Assert::exception(
fn() => $default->setVisibility('public', 'invalid'),
Nette\InvalidArgumentException::class,
'Argument must be public|protected|private.',
);


same(<<<'XX'
class Demo
{
public string $first;
private(set) string $second;
public protected(set) string $third;
protected private(set) string $fourth;
}

XX, (string) $class);

0 comments on commit a0a1ff3

Please sign in to comment.