From ed277975664f24d6f879e55bb22d11ae119e40df Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 May 2024 15:41:01 +0200 Subject: [PATCH] Factory, Extractor: sets flag readonly for promoted properties [Closes #158] --- src/PhpGenerator/Extractor.php | 3 +++ src/PhpGenerator/Factory.php | 2 +- src/PhpGenerator/Method.php | 2 ++ src/PhpGenerator/Printer.php | 4 ++++ tests/PhpGenerator/ClassType.from.81.phpt | 2 ++ tests/PhpGenerator/ClassType.readonly.phpt | 4 ++-- .../expected/ClassType.from.81.expect | 16 ++++++++++++++++ 7 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index e783ef71..5d39905e 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -343,6 +343,9 @@ private function addMethodToClass(ClassLike $class, Node\Stmt\ClassMethod $node) $method->setStatic($node->isStatic()); $method->setVisibility($this->toVisibility($node->flags)); $this->setupFunction($method, $node); + if ($method->getName() === Method::Constructor && $class instanceof ClassType && $class->isReadOnly()) { + array_map(fn($param) => $param instanceof PromotedParameter ? $param->setReadOnly() : $param, $method->getParameters()); + } } diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index 431d28e8..abc1f763 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -200,7 +200,7 @@ public function fromCallable(callable $from): Method|GlobalFunction|Closure public function fromParameterReflection(\ReflectionParameter $from): Parameter { $param = $from->isPromoted() - ? new PromotedParameter($from->name) + ? (new PromotedParameter($from->name))->setReadOnly(PHP_VERSION_ID >= 80100 && $from->getDeclaringClass()->getProperty($from->name)->isReadonly()) : new Parameter($from->name); $param->setReference($from->isPassedByReference()); $param->setType((string) $from->getType()); diff --git a/src/PhpGenerator/Method.php b/src/PhpGenerator/Method.php index 0f6eeae8..0e8e765b 100644 --- a/src/PhpGenerator/Method.php +++ b/src/PhpGenerator/Method.php @@ -23,6 +23,8 @@ final class Method use Traits\CommentAware; use Traits\AttributeAware; + public const Constructor = '__construct'; + private bool $static = false; private bool $final = false; private bool $abstract = false; diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index ea2d5468..4a1cf447 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -193,6 +193,10 @@ public function printClass( } foreach ($class->getMethods() as $method) { + if ($readOnlyClass && $method->getName() === Method::Constructor) { + $method = clone $method; + array_map(fn($param) => $param instanceof PromotedParameter ? $param->setReadOnly(false) : null, $method->getParameters()); + } $methods[] = $this->printMethod($method, $namespace, $class->isInterface()); } } diff --git a/tests/PhpGenerator/ClassType.from.81.phpt b/tests/PhpGenerator/ClassType.from.81.phpt index 6dd3db33..ce30dfc1 100644 --- a/tests/PhpGenerator/ClassType.from.81.phpt +++ b/tests/PhpGenerator/ClassType.from.81.phpt @@ -12,5 +12,7 @@ require __DIR__ . '/../bootstrap.php'; require __DIR__ . '/fixtures/classes.81.php'; $res[] = ClassType::from(new Abc\Class11); +$res[] = ClassType::from(Abc\Attr::class); +$res[] = ClassType::from(Abc\Class12::class); sameFile(__DIR__ . '/expected/ClassType.from.81.expect', implode("\n", $res)); diff --git a/tests/PhpGenerator/ClassType.readonly.phpt b/tests/PhpGenerator/ClassType.readonly.phpt index 1f34721b..33079aa7 100644 --- a/tests/PhpGenerator/ClassType.readonly.phpt +++ b/tests/PhpGenerator/ClassType.readonly.phpt @@ -15,9 +15,9 @@ require __DIR__ . '/fixtures/classes.82.php'; $class = ClassType::from(new Abc\Class13); Assert::true($class->getProperty('foo')->isReadOnly()); -Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); +Assert::true($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); $file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.82.php')))->extractAll(); $class = $file->getClasses()[Abc\Class13::class]; Assert::true($class->getProperty('foo')->isReadOnly()); -Assert::false($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); +Assert::true($class->getMethod('__construct')->getParameter('bar')->isReadOnly()); diff --git a/tests/PhpGenerator/expected/ClassType.from.81.expect b/tests/PhpGenerator/expected/ClassType.from.81.expect index f52acd06..22b0d789 100644 --- a/tests/PhpGenerator/expected/ClassType.from.81.expect +++ b/tests/PhpGenerator/expected/ClassType.from.81.expect @@ -16,3 +16,19 @@ class Class11 { } } + +#[\Attribute] +class Attr +{ +} + +class Class12 +{ + private readonly string $bar; + + + public function __construct( + public readonly string $foo, + ) { + } +}