diff --git a/Makefile b/Makefile index 5fd23d61..15c0e2d9 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,8 @@ fix-code-style: .PHONY: static-code-analysis static-code-analysis: vendor ## Runs a static code analysis with phpstan/phpstan and vimeo/psalm - docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpstan --configuration=phpstan.neon - docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/psalm.phar + docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.2-cli vendor/bin/phpstan --configuration=phpstan.neon + docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.2-cli vendor/bin/psalm.phar .PHONY: test test: test-unit test-functional ## Runs all test suites with phpunit/phpunit @@ -25,7 +25,7 @@ test-unit: ## Runs unit tests with phpunit/phpunit .PHONY: test-functional test-functional: ## Runs unit tests with phpunit/phpunit - docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpunit --testsuite=functional + docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:8.1-cli vendor/bin/phpunit --testsuite=integration .PHONY: dependency-analysis dependency-analysis: vendor ## Runs a dependency analysis with maglnet/composer-require-checker diff --git a/src/phpDocumentor/Reflection/Php/Factory/AbstractFactory.php b/src/phpDocumentor/Reflection/Php/Factory/AbstractFactory.php index 1384e5a7..6a6cdeed 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/AbstractFactory.php +++ b/src/phpDocumentor/Reflection/Php/Factory/AbstractFactory.php @@ -30,7 +30,7 @@ abstract class AbstractFactory implements ProjectFactoryStrategy { /** @param iterable $reducers */ public function __construct( - private readonly DocBlockFactoryInterface $docBlockFactory, + protected readonly DocBlockFactoryInterface $docBlockFactory, protected readonly iterable $reducers = [], ) { } diff --git a/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php index e7d27fd2..c71e6b9f 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ConstructorPromotion.php @@ -8,13 +8,10 @@ use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Fqsen; use phpDocumentor\Reflection\Location; -use phpDocumentor\Reflection\Php\AsyncVisibility; use phpDocumentor\Reflection\Php\Class_ as ClassElement; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\ProjectFactoryStrategy; -use phpDocumentor\Reflection\Php\Property; use phpDocumentor\Reflection\Php\StrategyContainer; -use phpDocumentor\Reflection\Php\Visibility; use PhpParser\Modifiers; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Param; @@ -67,17 +64,22 @@ private function promoteParameterToProperty(ContextStack $context, StrategyConta Assert::isInstanceOf($methodContainer, ClassElement::class); Assert::isInstanceOf($param->var, Variable::class); - $property = new Property( - new Fqsen($methodContainer->getFqsen() . '::$' . (string) $param->var->name), - $this->buildPropertyVisibilty($param->flags), - $this->createDocBlock($param->getDocComment(), $context->getTypeContext()), - $param->default !== null ? $this->valueConverter->prettyPrintExpr($param->default) : null, - false, - new Location($param->getLine()), - new Location($param->getEndLine()), - (new Type())->fromPhpParser($param->type), - $this->readOnly($param->flags), - ); + $property = PropertyBuilder::create( + $this->valueConverter, + $this->docBlockFactory, + $strategies, + $this->reducers, + )->fqsen(new Fqsen($methodContainer->getFqsen() . '::$' . (string) $param->var->name)) + ->visibility($param) + ->type($param->type) + ->docblock($param->getDocComment()) + ->default($param->default) + ->readOnly($this->readOnly($param->flags)) + ->static(false) + ->startLocation(new Location($param->getLine(), $param->getStartFilePos())) + ->endLocation(new Location($param->getEndLine(), $param->getEndFilePos())) + ->hooks($param->hooks ?? []) + ->build($context); foreach ($this->reducers as $reducer) { $property = $reducer->reduce($context, $param, $strategies, $property); @@ -90,44 +92,6 @@ private function promoteParameterToProperty(ContextStack $context, StrategyConta $methodContainer->addProperty($property); } - private function buildPropertyVisibilty(int $flags): Visibility - { - if ((bool) ($flags & Modifiers::VISIBILITY_SET_MASK) !== false) { - return new AsyncVisibility( - $this->buildReadVisibility($flags), - $this->buildWriteVisibility($flags), - ); - } - - return $this->buildReadVisibility($flags); - } - - private function buildReadVisibility(int $flags): Visibility - { - if ((bool) ($flags & Modifiers::PRIVATE) === true) { - return new Visibility(Visibility::PRIVATE_); - } - - if ((bool) ($flags & Modifiers::PROTECTED) === true) { - return new Visibility(Visibility::PROTECTED_); - } - - return new Visibility(Visibility::PUBLIC_); - } - - private function buildWriteVisibility(int $flags): Visibility - { - if ((bool) ($flags & Modifiers::PRIVATE_SET) === true) { - return new Visibility(Visibility::PRIVATE_); - } - - if ((bool) ($flags & Modifiers::PROTECTED_SET) === true) { - return new Visibility(Visibility::PROTECTED_); - } - - return new Visibility(Visibility::PUBLIC_); - } - private function readOnly(int $flags): bool { return (bool) ($flags & Modifiers::READONLY) === true; diff --git a/src/phpDocumentor/Reflection/Php/Factory/ContextStack.php b/src/phpDocumentor/Reflection/Php/Factory/ContextStack.php index d4c17af7..1d5878d4 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/ContextStack.php +++ b/src/phpDocumentor/Reflection/Php/Factory/ContextStack.php @@ -8,6 +8,7 @@ use phpDocumentor\Reflection\Element; use phpDocumentor\Reflection\Php\File as FileElement; use phpDocumentor\Reflection\Php\Project; +use phpDocumentor\Reflection\Php\PropertyHook; use phpDocumentor\Reflection\Types\Context as TypeContext; use function array_reverse; @@ -15,14 +16,14 @@ final class ContextStack { - /** @var (Element|FileElement)[] */ + /** @var (Element|FileElement|PropertyHook)[] */ private array $elements = []; public function __construct(private readonly Project $project, private readonly TypeContext|null $typeContext = null) { } - /** @param (Element|FileElement)[] $elements */ + /** @param (Element|FileElement|PropertyHook)[] $elements */ private static function createFromSelf(Project $project, TypeContext|null $typeContext, array $elements): self { $self = new self($project, $typeContext); @@ -31,7 +32,7 @@ private static function createFromSelf(Project $project, TypeContext|null $typeC return $self; } - public function push(Element|FileElement $element): self + public function push(Element|FileElement|PropertyHook $element): self { $elements = $this->elements; $elements[] = $element; @@ -54,7 +55,7 @@ public function getProject(): Project return $this->project; } - public function peek(): Element|FileElement + public function peek(): Element|FileElement|PropertyHook { $element = end($this->elements); if ($element === false) { @@ -72,7 +73,7 @@ public function peek(): Element|FileElement * * @param class-string $type */ - public function search(string $type): Element|FileElement|null + public function search(string $type): Element|FileElement|PropertyHook|null { $reverseElements = array_reverse($this->elements); foreach ($reverseElements as $element) { diff --git a/src/phpDocumentor/Reflection/Php/Factory/Property.php b/src/phpDocumentor/Reflection/Php/Factory/Property.php index 9f1db228..90470661 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Property.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Property.php @@ -15,13 +15,11 @@ use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Location; -use phpDocumentor\Reflection\Php\AsyncVisibility; use phpDocumentor\Reflection\Php\Class_; use phpDocumentor\Reflection\Php\Factory\Reducer\Reducer; use phpDocumentor\Reflection\Php\Property as PropertyDescriptor; use phpDocumentor\Reflection\Php\StrategyContainer; use phpDocumentor\Reflection\Php\Trait_; -use phpDocumentor\Reflection\Php\Visibility; use PhpParser\Node\Stmt\Property as PropertyNode; use PhpParser\PrettyPrinter\Standard as PrettyPrinter; use Webmozart\Assert\Assert; @@ -73,22 +71,23 @@ protected function doCreate( $iterator = new PropertyIterator($object); foreach ($iterator as $stmt) { - $default = $iterator->getDefault(); - if ($default !== null) { - $default = $this->valueConverter->prettyPrintExpr($default); - } - - $property = new PropertyDescriptor( - $stmt->getFqsen(), - $this->buildVisibility($stmt), - $this->createDocBlock($stmt->getDocComment(), $context->getTypeContext()), - $default, - $stmt->isStatic(), - new Location($stmt->getLine()), - new Location($stmt->getEndLine()), - (new Type())->fromPhpParser($stmt->getType()), - $stmt->isReadonly(), - ); + $property = PropertyBuilder::create( + $this->valueConverter, + $this->docBlockFactory, + $strategies, + $this->reducers, + ) + ->fqsen($stmt->getFqsen()) + ->visibility($stmt) + ->type($stmt->getType()) + ->docblock($stmt->getDocComment()) + ->default($iterator->getDefault()) + ->static($stmt->isStatic()) + ->startLocation(new Location($stmt->getLine())) + ->endLocation(new Location($stmt->getEndLine())) + ->readOnly($stmt->isReadonly()) + ->hooks($stmt->getHooks()) + ->build($context); foreach ($this->reducers as $reducer) { $property = $reducer->reduce($context, $object, $strategies, $property); @@ -103,48 +102,4 @@ protected function doCreate( return null; } - - /** - * Converts the visibility of the property to a valid Visibility object. - */ - private function buildVisibility(PropertyIterator $node): Visibility - { - if ($node->isAsync() === false) { - return $this->buildReadVisibility($node); - } - - $readVisibility = $this->buildReadVisibility($node); - $writeVisibility = $this->buildWriteVisibility($node); - - return new AsyncVisibility( - $readVisibility, - $writeVisibility, - ); - } - - private function buildReadVisibility(PropertyIterator $node): Visibility - { - if ($node->isPrivate()) { - return new Visibility(Visibility::PRIVATE_); - } - - if ($node->isProtected()) { - return new Visibility(Visibility::PROTECTED_); - } - - return new Visibility(Visibility::PUBLIC_); - } - - private function buildWriteVisibility(PropertyIterator $node): Visibility - { - if ($node->isPrivateSet()) { - return new Visibility(Visibility::PRIVATE_); - } - - if ($node->isProtectedSet()) { - return new Visibility(Visibility::PROTECTED_); - } - - return new Visibility(Visibility::PUBLIC_); - } } diff --git a/src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php b/src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php new file mode 100644 index 00000000..e41f89af --- /dev/null +++ b/src/phpDocumentor/Reflection/Php/Factory/PropertyBuilder.php @@ -0,0 +1,264 @@ + $reducers */ + private function __construct( + private PrettyPrinter $valueConverter, + private DocBlockFactoryInterface $docBlockFactory, + private StrategyContainer $strategies, + private iterable $reducers, + ) { + $this->visibility = new Visibility(Visibility::PUBLIC_); + } + + /** + * @param iterable $reducers + */ + public static function create( + PrettyPrinter $valueConverter, + DocBlockFactoryInterface $docBlockFactory, + StrategyContainer $strategies, + iterable $reducers, + ): self { + return new self($valueConverter, $docBlockFactory, $strategies, $reducers); + } + + public function fqsen(Fqsen $fqsen): self + { + $this->fqsen = $fqsen; + + return $this; + } + + public function visibility(Param|PropertyIterator $node): self + { + $this->visibility = $this->buildVisibility($node); + + return $this; + } + + public function type(Identifier|Name|ComplexType|null $type): self + { + $this->type = $type; + + return $this; + } + + public function readOnly(bool $readOnly): self + { + $this->readOnly = $readOnly; + + return $this; + } + + public function docblock(Doc|null $docblock): self + { + $this->docblock = $docblock; + + return $this; + } + + public function default(Expr|null $default): self + { + $this->default = $default; + + return $this; + } + + public function static(bool $static): self + { + $this->static = $static; + + return $this; + } + + public function startLocation(Location $startLocation): self + { + $this->startLocation = $startLocation; + + return $this; + } + + public function endLocation(Location $endLocation): self + { + $this->endLocation = $endLocation; + + return $this; + } + + /** @param PropertyHookNode[] $hooks */ + public function hooks(array $hooks): self + { + $this->hooks = $hooks; + + return $this; + } + + public function build(ContextStack $context): PropertyElement + { + return new PropertyElement( + $this->fqsen, + $this->visibility, + $this->docblock !== null ? $this->docBlockFactory->create($this->docblock->getText(), $context->getTypeContext()) : null, + $this->default !== null ? $this->valueConverter->prettyPrintExpr($this->default) : null, + $this->static, + $this->startLocation, + $this->endLocation, + (new Type())->fromPhpParser($this->type), + $this->readOnly, + array_filter(array_map( + fn (PropertyHookNode $hook) => $this->buildHook($hook, $context), + $this->hooks, + )), + ); + } + + /** + * Returns true when current property has async accessors. + * + * This method will always return false when your phpparser version is < 5.2 + */ + private function isAsync(Param|PropertyIterator $node): bool + { + if (method_exists($node, 'isPrivateSet') === false) { + return false; + } + + return $node->isPublicSet() || $node->isProtectedSet() || $node->isPrivateSet(); + } + + private function buildVisibility(Param|PropertyIterator $node): Visibility + { + if ($this->isAsync($node) === false) { + return $this->buildReadVisibility($node); + } + + $readVisibility = $this->buildReadVisibility($node); + $writeVisibility = $this->buildWriteVisibility($node); + + return new AsyncVisibility( + $readVisibility, + $writeVisibility, + ); + } + + private function buildReadVisibility(Param|PropertyIterator $node): Visibility + { + if ($node instanceof Param && method_exists($node, 'isPublic') === false) { + return $this->buildVisibilityFromFlags($node->flags); + } + + if ($node->isPrivate()) { + return new Visibility(Visibility::PRIVATE_); + } + + if ($node->isProtected()) { + return new Visibility(Visibility::PROTECTED_); + } + + return new Visibility(Visibility::PUBLIC_); + } + + private function buildVisibilityFromFlags(int $flags): Visibility + { + if ((bool) ($flags & Modifiers::PRIVATE) === true) { + return new Visibility(Visibility::PRIVATE_); + } + + if ((bool) ($flags & Modifiers::PROTECTED) === true) { + return new Visibility(Visibility::PROTECTED_); + } + + return new Visibility(Visibility::PUBLIC_); + } + + private function buildWriteVisibility(Param|PropertyIterator $node): Visibility + { + if ($node->isPrivateSet()) { + return new Visibility(Visibility::PRIVATE_); + } + + if ($node->isProtectedSet()) { + return new Visibility(Visibility::PROTECTED_); + } + + return new Visibility(Visibility::PUBLIC_); + } + + private function buildHook(PropertyHookNode $hook, ContextStack $context): PropertyHook|null + { + $doc = $hook->getDocComment(); + + $result = new PropertyHook( + $hook->name->toString(), + $this->buildVisibilityFromFlags($hook->flags), + $doc !== null ? $this->docBlockFactory->create($doc->getText(), $context->getTypeContext()) : null, + $hook->isFinal(), + new Location($hook->getStartLine(), $hook->getStartFilePos()), + new Location($hook->getEndLine(), $hook->getEndFilePos()), + ); + + foreach ($this->reducers as $reducer) { + $result = $reducer->reduce($context, $hook, $this->strategies, $result); + } + + if ($result === null) { + return $result; + } + + $thisContext = $context->push($result); + foreach ($hook->getStmts() ?? [] as $stmt) { + $strategy = $this->strategies->findMatching($thisContext, $stmt); + $strategy->create($thisContext, $stmt, $this->strategies); + } + + return $result; + } +} diff --git a/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php b/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php index efeadeb8..a15cd9d2 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php +++ b/src/phpDocumentor/Reflection/Php/Factory/PropertyIterator.php @@ -20,9 +20,11 @@ use PhpParser\Node\Expr; use PhpParser\Node\Identifier; use PhpParser\Node\Name; +use PhpParser\Node\PropertyHook; use PhpParser\Node\Stmt\Property as PropertyNode; use function method_exists; +use function property_exists; /** * This class acts like a combination of a PropertyNode and PropertyProperty to @@ -119,7 +121,7 @@ public function isAsync(): bool return false; } - return $this->property->isPublicSet() || $this->property->isProtected() || $this->property->isPrivateSet(); + return $this->property->isPublicSet() || $this->property->isProtectedSet() || $this->property->isPrivateSet(); } /** @@ -201,6 +203,16 @@ public function getFqsen(): Fqsen return $this->property->props[$this->index]->getAttribute('fqsen'); } + /** @return PropertyHook[] */ + public function getHooks(): array + { + if (property_exists($this->property, 'hooks') === false) { + return []; + } + + return $this->property->hooks; + } + /** @link http://php.net/manual/en/iterator.current.php */ public function current(): self { diff --git a/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php b/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php index b9eb81a0..e17ab34e 100644 --- a/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php +++ b/src/phpDocumentor/Reflection/Php/Factory/Reducer/Parameter.php @@ -9,6 +9,7 @@ use phpDocumentor\Reflection\Php\Factory\Type; use phpDocumentor\Reflection\Php\Function_; use phpDocumentor\Reflection\Php\Method; +use phpDocumentor\Reflection\Php\PropertyHook; use phpDocumentor\Reflection\Php\StrategyContainer; use PhpParser\Node\Expr\Variable; use PhpParser\Node\FunctionLike; @@ -33,7 +34,7 @@ public function reduce( return $carry; } - if ($carry instanceof Method === false && $carry instanceof Function_ === false) { + if ($carry instanceof Method === false && $carry instanceof Function_ === false && $carry instanceof PropertyHook === false) { return null; } diff --git a/src/phpDocumentor/Reflection/Php/ProjectFactory.php b/src/phpDocumentor/Reflection/Php/ProjectFactory.php index 9c6c5d74..f4aa221f 100644 --- a/src/phpDocumentor/Reflection/Php/ProjectFactory.php +++ b/src/phpDocumentor/Reflection/Php/ProjectFactory.php @@ -85,7 +85,7 @@ public static function createInstance(): self new Function_($docblockFactory, [$attributeReducer, $parameterReducer]), new Interface_($docblockFactory, [$attributeReducer]), $methodStrategy, - new Property($docblockFactory, new PrettyPrinter(), [$attributeReducer]), + new Property($docblockFactory, new PrettyPrinter(), [$attributeReducer, $parameterReducer]), new Trait_($docblockFactory, [$attributeReducer]), new IfStatement(), @@ -94,7 +94,7 @@ public static function createInstance(): self ); $strategies->addStrategy( - new ConstructorPromotion($methodStrategy, $docblockFactory, new PrettyPrinter(), [$attributeReducer]), + new ConstructorPromotion($methodStrategy, $docblockFactory, new PrettyPrinter(), [$attributeReducer, $parameterReducer]), 1100, ); $strategies->addStrategy(new Noop(), -PHP_INT_MAX); diff --git a/src/phpDocumentor/Reflection/Php/Property.php b/src/phpDocumentor/Reflection/Php/Property.php index 0013de93..0334a140 100644 --- a/src/phpDocumentor/Reflection/Php/Property.php +++ b/src/phpDocumentor/Reflection/Php/Property.php @@ -37,7 +37,10 @@ final class Property implements Element, MetaDataContainerInterface, AttributeCo private readonly Location $endLocation; - /** @param Visibility|null $visibility when null is provided a default 'public' is set. */ + /** + * @param Visibility|null $visibility when null is provided a default 'public' is set. + * @param PropertyHook[] $hooks + */ public function __construct( private readonly Fqsen $fqsen, Visibility|null $visibility = null, @@ -48,6 +51,7 @@ public function __construct( Location|null $endLocation = null, private readonly Type|null $type = null, private readonly bool $readOnly = false, + private readonly array $hooks = [], ) { $this->visibility = $visibility ?: new Visibility('public'); $this->location = $location ?: new Location(-1); diff --git a/src/phpDocumentor/Reflection/Php/PropertyHook.php b/src/phpDocumentor/Reflection/Php/PropertyHook.php new file mode 100644 index 00000000..6009c620 --- /dev/null +++ b/src/phpDocumentor/Reflection/Php/PropertyHook.php @@ -0,0 +1,94 @@ +location = $location ?? new Location(-1); + $this->endLocation = $endLocation ?? new Location(-1); + } + + /** + * Returns true when this hook is final. Otherwise, returns false. + */ + public function isFinal(): bool + { + return $this->final; + } + + /** + * Returns the Visibility of this hook. + */ + public function getVisibility(): Visibility|null + { + return $this->visibility; + } + + /** + * Returns the arguments of this hook. + * + * @return Argument[] + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * Add new argument to this hook. + */ + public function addArgument(Argument $argument): void + { + $this->arguments[] = $argument; + } + + /** + * Returns the name of this hook. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns the DocBlock of this method if available. + */ + public function getDocBlock(): DocBlock|null + { + return $this->docBlock; + } + + public function getLocation(): Location + { + return $this->location; + } + + public function getEndLocation(): Location + { + return $this->endLocation; + } +} diff --git a/tests/integration/PHP8/ConstructorPromotionTest.php b/tests/integration/PHP8/ConstructorPromotionTest.php index d14b435a..46e3b84f 100644 --- a/tests/integration/PHP8/ConstructorPromotionTest.php +++ b/tests/integration/PHP8/ConstructorPromotionTest.php @@ -50,8 +50,8 @@ public function testPropertiesAreCreated() : void $class = $file->getClasses()['\\PHP8\\ConstructorPromotion']; $constructor = $this->expectedContructorMethod(); - $constructor->addArgument(new Argument('name', new String_())); - $constructor->addArgument(new Argument('email', new String_(), '\'test@example.com\'')); + $constructor->addArgument(new Argument('name', new String_(), "'default name'")); + $constructor->addArgument(new Argument('email', new Object_(new Fqsen('\\PHP8\\Email')))); $constructor->addArgument(new Argument('birth_date', new Object_(new Fqsen('\\' . \DateTimeImmutable::class)))); self::assertEquals($constructor, $class->getMethods()['\PHP8\ConstructorPromotion::__construct()']); @@ -87,7 +87,7 @@ private function expectedContructorMethod(): Method false, false, new Location(16, 218), - new Location(27, 522) + new Location(27, 517) ); return $constructor; } @@ -105,10 +105,10 @@ private function expectedNameProperty(): Property ], new Context('PHP8', ['DateTimeImmutable' => 'DateTimeImmutable']) ), - null, + "'default name'", false, - new Location(24), - new Location(24), + new Location(24, 393), + new Location(24, 428), new String_() ); return $name; @@ -120,11 +120,11 @@ private function expectedEmailProperty(): Property new Fqsen('\PHP8\ConstructorPromotion::$email'), new Visibility(Visibility::PROTECTED_), null, - '\'test@example.com\'', + null, false, - new Location(25), - new Location(25), - new String_() + new Location(25, 439), + new Location(25, 460), + new Object_(new Fqsen('\\PHP8\\Email')) ); return $email; } @@ -137,8 +137,8 @@ private function expectedBirthDateProperty(): Property null, null, false, - new Location(26), - new Location(26), + new Location(26, 471), + new Location(26, 507), new Object_(new Fqsen('\\' . \DateTimeImmutable::class)) ); return $birthDate; diff --git a/tests/integration/PropertyHookTest.php b/tests/integration/PropertyHookTest.php new file mode 100644 index 00000000..d1a5b018 --- /dev/null +++ b/tests/integration/PropertyHookTest.php @@ -0,0 +1,23 @@ += 5.2')] +final class PropertyHookTest extends TestCase +{ + public function testPropertyHook() + { + $file = __DIR__ . '/data/PHP84/PropertyHook.php'; + $projectFactory = ProjectFactory::createInstance(); + $project = $projectFactory->create('My project', [new LocalFile($file)]); + + $class = $project->getFiles()[$file]->getClasses()['\PropertyHook']; + } +} diff --git a/tests/integration/data/PHP8/ConstructorPromotion.php b/tests/integration/data/PHP8/ConstructorPromotion.php index cc1883c6..494ddbe0 100644 --- a/tests/integration/data/PHP8/ConstructorPromotion.php +++ b/tests/integration/data/PHP8/ConstructorPromotion.php @@ -21,8 +21,8 @@ public function __construct( * * @var string $name property description */ - public string $name, - protected string $email = 'test@example.com', + public string $name = 'default name', + protected Email $email, private DateTimeImmutable $birth_date, ) {} } diff --git a/tests/integration/data/PHP84/PropertyHook.php b/tests/integration/data/PHP84/PropertyHook.php new file mode 100644 index 00000000..7c933750 --- /dev/null +++ b/tests/integration/data/PHP84/PropertyHook.php @@ -0,0 +1,32 @@ +modified) { + return $this->foo . ' (modified)'; + } + return $this->foo; + } + /** Not sure this works, but it sets */ + #[Setter(new DateTimeImmutable())] + set(string|int $value) { + $this->foo = strtolower($value); + $this->modified = true; + } + } + ) + { + } +}