Skip to content

Commit

Permalink
Add support for properties hooks [Closes #171]
Browse files Browse the repository at this point in the history
  • Loading branch information
shanginn authored and dg committed Nov 21, 2024
1 parent 1e85ee7 commit 4acefba
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 8 deletions.
55 changes: 47 additions & 8 deletions src/PhpGenerator/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo
}


protected function printFunctionBody(Closure|GlobalFunction|Method $function): string
protected function printFunctionBody(Closure|GlobalFunction|Method|PropertyHook $function): string
{
return ltrim(rtrim(Strings::normalize(
Helpers::simplifyTaggedNames($function->getBody(), $this->namespace),
Expand Down Expand Up @@ -313,7 +313,7 @@ protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace:
}


protected function printParameters(Closure|GlobalFunction|Method $function, int $column = 0): string
protected function printParameters(Closure|GlobalFunction|Method|PropertyHook $function, int $column = 0): string
{
$special = false;
foreach ($function->getParameters() as $param) {
Expand All @@ -332,13 +332,13 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int
}


private function formatParameters(Closure|GlobalFunction|Method $function, bool $multiline): string
private function formatParameters(Closure|GlobalFunction|Method|PropertyHook $function, bool $multiline): string
{
$params = $function->getParameters();
$res = '';

foreach ($params as $param) {
$variadic = $function->isVariadic() && $param === end($params);
$variadic = !$function instanceof PropertyHook && $function->isVariadic() && $param === end($params);
$attrs = $this->printAttributes($param->getAttributes(), inline: true);
$res .=
$this->printDocComment($param)
Expand Down Expand Up @@ -386,13 +386,20 @@ private function printProperty(Property $property, bool $readOnlyClass = false):
. ltrim($this->printType($type, $property->isNullable()) . ' ')
. '$' . $property->getName());

$hooks = !$property->getSetHook() && !$property->getGetHook()
? ';'
: " {\n" . $this->printHooks($property) . '}';

$defaultValue = $property->getValue() === null && !$property->isInitialized()
? ''
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3); // 3 = ' = '

return $this->printDocComment($property)
. $this->printAttributes($property->getAttributes())
. $def
. ($property->getValue() === null && !$property->isInitialized()
? ''
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
. ";\n";
. $defaultValue
. $hooks
. "\n";
}


Expand Down Expand Up @@ -489,4 +496,36 @@ protected function isBraceOnNextLine(bool $multiLine, bool $hasReturnType): bool
{
return $this->bracesOnNextLine && (!$multiLine || $hasReturnType);
}


private function printHooks(Property $property): ?string
{
$out = null;

if ($setHook = $property->getSetHook()) {
$out .= $this->indent(
$this->printDocComment($setHook)
. $this->printAttributes($setHook->getAttributes())
. 'set '
. ($setHook->getParameter() ? $this->printParameters($setHook) . ' ' : '')
. ($setHook->isShortcut()
? '=> ' . rtrim($setHook->getBody(), "\n") . ";\n"
: "{\n" . $this->indent($this->printFunctionBody($setHook)) . "}\n"),
);
}

if ($getHook = $property->getGetHook()) {
$out .= $this->indent(
$this->printDocComment($getHook)
. $this->printAttributes($getHook->getAttributes())
. ($getHook->getReturnReference() ? '&' : '')
. 'get '
. ($getHook->isShortcut()
? '=> ' . rtrim($getHook->getBody(), "\n") . ";\n"
: "{\n" . $this->indent($this->printFunctionBody($getHook)) . "}\n"),
);
}

return $out;
}
}
52 changes: 52 additions & 0 deletions src/PhpGenerator/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ final class Property
private bool $nullable = false;
private bool $initialized = false;
private bool $readOnly = false;
private ?PropertyHook $getHook = null;
private ?PropertyHook $setHook = null;


public function setValue(mixed $val): static
Expand Down Expand Up @@ -113,6 +115,56 @@ public function isReadOnly(): bool
}


public function setSetHook(?PropertyHook $hook): static
{
$this->setHook = $hook;
return $this;
}


public function getSetHook(): ?PropertyHook
{
return $this->setHook;
}


public function addSetHook(string $body, bool $shortcut = false): PropertyHook
{
return $this->setHook = (new PropertyHook)->setBody($body, $shortcut);
}


public function hasSetHook(): bool
{
return $this->setHook !== null;
}


public function setGetHook(?PropertyHook $hook): static
{
$this->getHook = $hook;
return $this;
}


public function getGetHook(): ?PropertyHook
{
return $this->getHook;
}


public function addGetHook(string $body, bool $shortcut = false): PropertyHook
{
return $this->getHook = (new PropertyHook)->setBody($body, $shortcut);
}


public function hasGetHook(): bool
{
return $this->getHook !== null;
}


/** @throws Nette\InvalidStateException */
public function validate(): void
{
Expand Down
73 changes: 73 additions & 0 deletions src/PhpGenerator/PropertyHook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace Nette\PhpGenerator;

class PropertyHook
{
use Traits\AttributeAware;
use Traits\CommentAware;

private ?string $body = null;
private bool $shortcut = false; // Whether to use the '=>' syntax
private ?Parameter $parameter = null;
private bool $returnReference = false;


public function setBody(string $body, bool $shortcut = false): self
{
$this->body = $body;
$this->shortcut = $shortcut;
return $this;
}


public function getBody(): ?string
{
return $this->body;
}


public function isShortcut(): bool
{
return $this->shortcut;
}


/**
* Adds a parameter. If it already exists, it overwrites it.
* @param string $name without $
*/
public function setParameter(string $name = 'value'): Parameter
{
$param = new Parameter($name);
return $this->parameter = $param;
}


public function getParameter(): ?Parameter
{
return $this->parameter;
}


/** @internal */
public function getParameters(): array
{
return $this->parameter ? [$this->parameter] : [];
}


public function setReturnReference(bool $state = true): static
{
$this->returnReference = $state;
return $this;
}


public function getReturnReference(): bool
{
return $this->returnReference;
}
}
72 changes: 72 additions & 0 deletions tests/PhpGenerator/Property.hooks.classes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/**
* Test: Nette\PhpGenerator - PHP 8.4 property hooks for classes
*/

declare(strict_types=1);

use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\PropertyHook;
use Nette\PhpGenerator\PsrPrinter;

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

$file = new PhpFile;
$file->setStrictTypes();

$class = new ClassType('Locale');

$class->addProperty('languageCode')
->setType('string')
->setPublic();

$countryCodeSetHookClosure = (new PropertyHook)
->setBody('$this->countryCode = strtoupper($countryCode);');

$countryCodeSetHookClosure->setParameter('countryCode')->setType('string');

$class->addProperty('countryCode')
->setType('string')
->setValue('AA')
->setPublic()
->setSetHook($countryCodeSetHookClosure);

$combinedCodeGetHookClosure = (new PropertyHook)
->setBody('return \sprintf("%s_%s", $this->languageCode, $this->countryCode);');

$combinedCodeSetHookClosure = (new PropertyHook)
->setBody('[$this->languageCode, $this->countryCode] = explode(\'_\', $value, 2);');

$combinedCodeSetHookClosure->setParameter('value')->setType('string');

$class->addProperty('combinedCode')
->setType('string')
->setPublic()
->setGetHook($combinedCodeGetHookClosure)
->setSetHook($combinedCodeSetHookClosure);

$expected = <<<'PHP'
class Locale
{
public string $languageCode;
public string $countryCode = 'AA' {
set (string $countryCode) {
$this->countryCode = strtoupper($countryCode);
}
}
public string $combinedCode {
set (string $value) {
[$this->languageCode, $this->countryCode] = explode('_', $value, 2);
}
get {
return \sprintf("%s_%s", $this->languageCode, $this->countryCode);
}
}
}
PHP;

same(rtrim($expected), rtrim((new PsrPrinter)->printClass($class)));

0 comments on commit 4acefba

Please sign in to comment.