|  | 
| 11 | 11 | 
 | 
| 12 | 12 | use Nette; | 
| 13 | 13 | use PhpParser; | 
|  | 14 | +use PhpParser\Modifiers; | 
| 14 | 15 | use PhpParser\Node; | 
| 15 | 16 | use PhpParser\NodeFinder; | 
| 16 | 17 | use PhpParser\ParserFactory; | 
| @@ -323,14 +324,37 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): | 
| 323 | 324 | 		foreach ($node->props as $item) { | 
| 324 | 325 | 			$prop = $class->addProperty($item->name->toString()); | 
| 325 | 326 | 			$prop->setStatic($node->isStatic()); | 
| 326 |  | -			$prop->setVisibility($this->toVisibility($node->flags)); | 
|  | 327 | +			$prop->setVisibility($this->toVisibility($node->flags), $this->toSetterVisibility($node->flags)); | 
| 327 | 328 | 			$prop->setType($node->type ? $this->toPhp($node->type) : null); | 
| 328 | 329 | 			if ($item->default) { | 
| 329 | 330 | 				$prop->setValue($this->toValue($item->default)); | 
| 330 | 331 | 			} | 
| 331 | 332 | 
 | 
| 332 | 333 | 			$prop->setReadOnly((method_exists($node, 'isReadonly') && $node->isReadonly()) || ($class instanceof ClassType && $class->isReadOnly())); | 
| 333 | 334 | 			$this->addCommentAndAttributes($prop, $node); | 
|  | 335 | + | 
|  | 336 | +			$prop->setAbstract((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_ABSTRACT)); | 
|  | 337 | +			$prop->setFinal((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_FINAL)); | 
|  | 338 | +			$this->addHooksToProperty($prop, $node); | 
|  | 339 | +		} | 
|  | 340 | +	} | 
|  | 341 | + | 
|  | 342 | + | 
|  | 343 | +	private function addHooksToProperty(Property|PromotedParameter $prop, Node\Stmt\Property|Node\Param $node): void | 
|  | 344 | +	{ | 
|  | 345 | +		if (!class_exists(Node\PropertyHook::class)) { | 
|  | 346 | +			return; | 
|  | 347 | +		} | 
|  | 348 | + | 
|  | 349 | +		foreach ($node->hooks as $hookNode) { | 
|  | 350 | +			$hook = $prop->addHook($hookNode->name->toString()); | 
|  | 351 | +			$hook->setFinal((bool) ($hookNode->flags & Modifiers::FINAL)); | 
|  | 352 | +			$this->setupFunction($hook, $hookNode); | 
|  | 353 | +			if ($hookNode->body === null) { | 
|  | 354 | +				$hook->setAbstract(); | 
|  | 355 | +			} elseif (!is_array($hookNode->body)) { | 
|  | 356 | +				$hook->setBody($this->getReformattedContents([$hookNode->body], 1), short: true); | 
|  | 357 | +			} | 
| 334 | 358 | 		} | 
| 335 | 359 | 	} | 
| 336 | 360 | 
 | 
| @@ -380,7 +404,7 @@ private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): | 
| 380 | 404 | 
 | 
| 381 | 405 | 
 | 
| 382 | 406 | 	private function addCommentAndAttributes( | 
| 383 |  | -		PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse $element, | 
|  | 407 | +		PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse|PropertyHook $element, | 
| 384 | 408 | 		Node $node, | 
| 385 | 409 | 	): void | 
| 386 | 410 | 	{ | 
| @@ -408,19 +432,29 @@ private function addCommentAndAttributes( | 
| 408 | 432 | 	} | 
| 409 | 433 | 
 | 
| 410 | 434 | 
 | 
| 411 |  | -	private function setupFunction(GlobalFunction|Method $function, Node\FunctionLike $node): void | 
|  | 435 | +	private function setupFunction(GlobalFunction|Method|PropertyHook $function, Node\FunctionLike $node): void | 
| 412 | 436 | 	{ | 
| 413 | 437 | 		$function->setReturnReference($node->returnsByRef()); | 
| 414 |  | -		$function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); | 
|  | 438 | +		if (!$function instanceof PropertyHook) { | 
|  | 439 | +			$function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null); | 
|  | 440 | +		} | 
|  | 441 | + | 
| 415 | 442 | 		foreach ($node->getParams() as $item) { | 
| 416 |  | -			$visibility = $this->toVisibility($item->flags); | 
| 417 |  | -			$isReadonly = (bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY); | 
| 418 |  | -			$param = $visibility | 
| 419 |  | -				? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility)->setReadonly($isReadonly) | 
| 420 |  | -				: $function->addParameter($item->var->name); | 
|  | 443 | +			$getVisibility = $this->toVisibility($item->flags); | 
|  | 444 | +			$setVisibility = $this->toSetterVisibility($item->flags); | 
|  | 445 | +			if ($getVisibility || $setVisibility) { | 
|  | 446 | +				$param = $function->addPromotedParameter($item->var->name) | 
|  | 447 | +					->setVisibility($getVisibility, $setVisibility) | 
|  | 448 | +					->setReadonly((bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY)); | 
|  | 449 | +				$this->addHooksToProperty($param, $item); | 
|  | 450 | +			} else { | 
|  | 451 | +				$param = $function->addParameter($item->var->name); | 
|  | 452 | +			} | 
| 421 | 453 | 			$param->setType($item->type ? $this->toPhp($item->type) : null); | 
| 422 | 454 | 			$param->setReference($item->byRef); | 
| 423 |  | -			$function->setVariadic($item->variadic); | 
|  | 455 | +			if (!$function instanceof PropertyHook) { | 
|  | 456 | +				$function->setVariadic($item->variadic); | 
|  | 457 | +			} | 
| 424 | 458 | 			if ($item->default) { | 
| 425 | 459 | 				$param->setDefaultValue($this->toValue($item->default)); | 
| 426 | 460 | 			} | 
| @@ -491,6 +525,18 @@ private function toVisibility(int $flags): ?string | 
| 491 | 525 | 	} | 
| 492 | 526 | 
 | 
| 493 | 527 | 
 | 
|  | 528 | +	private function toSetterVisibility(int $flags): ?string | 
|  | 529 | +	{ | 
|  | 530 | +		return match (true) { | 
|  | 531 | +			!class_exists(Node\PropertyHook::class) => null, | 
|  | 532 | +			(bool) ($flags & Modifiers::PUBLIC_SET) => ClassType::VisibilityPublic, | 
|  | 533 | +			(bool) ($flags & Modifiers::PROTECTED_SET) => ClassType::VisibilityProtected, | 
|  | 534 | +			(bool) ($flags & Modifiers::PRIVATE_SET) => ClassType::VisibilityPrivate, | 
|  | 535 | +			default => null, | 
|  | 536 | +		}; | 
|  | 537 | +	} | 
|  | 538 | + | 
|  | 539 | + | 
| 494 | 540 | 	private function toPhp(Node $value): string | 
| 495 | 541 | 	{ | 
| 496 | 542 | 		$dolly = clone $value; | 
|  | 
0 commit comments