Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for PHP 8.4 property hooks #42

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/main/php/lang/reflection/Property.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php namespace lang\reflection;

use ReflectionException, ReflectionUnionType, Throwable;
use ReflectionException, ReflectionProperty, ReflectionUnionType, Throwable;
use lang\{Reflection, XPClass, Type, VirtualProperty, TypeUnion};

/**
Expand All @@ -9,6 +9,11 @@
* @test lang.reflection.unittest.PropertiesTest
*/
class Property extends Member {
private static $HOOKS;

static function __static() {
self::$HOOKS= method_exists(ReflectionProperty::class, 'getHooks');
}

protected function meta() { return Reflection::meta()->propertyAnnotations($this->reflect); }

Expand All @@ -19,6 +24,11 @@ protected function meta() { return Reflection::meta()->propertyAnnotations($this
*/
public function comment() { return Reflection::meta()->propertyComment($this->reflect); }

/** Returns whether this property is virtual */
public function virtual() {
return $this->reflect instanceof VirtualProperty || self::$HOOKS && $this->reflect->isVirtual();
}

/** Returns a compound name consisting of `[CLASS]::$[NAME]` */
public function compoundName(): string {
return strtr($this->reflect->getDeclaringClass()->name , '\\', '.').'::$'.$this->reflect->getName();
Expand Down
65 changes: 65 additions & 0 deletions src/test/php/lang/reflection/unittest/PropertyHooksTest.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php namespace lang\reflection\unittest;

use ReflectionProperty;
use lang\reflection\AccessingFailed;
use test\verify\Condition;
use test\{Assert, Expect, Test, Values};

#[Condition(assert: 'method_exists(ReflectionProperty::class, "getHooks")')]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot yet use PHP_VERSION_ID here as php/php-src#13455 is yet to be merged.

class PropertyHooksTest {
use TypeDefinition;

#[Test]
public function is_virtual() {
$type= $this->declare('{ public string $fixture { get => "test"; } }');
Assert::true($type->properties()->named('fixture')->virtual());
}

#[Test, Values(['get { return $this->fixture; }', 'get => $this->fixture;'])]
public function backed_is_not_virtual($declaration) {
$type= $this->declare('{ public string $fixture { '.$declaration.' } }');
Assert::false($type->properties()->named('fixture')->virtual());
}

#[Test]
public function get() {
$type= $this->declare('{ public string $fixture { get => "test"; } }');
$instance= $type->newInstance();

Assert::equals('test', $type->properties()->named('fixture')->get($instance));
}

#[Test]
public function set() {
$type= $this->declare('{ public string $fixture { set => ucfirst($value); } }');
$instance= $type->newInstance();
$type->properties()->named('fixture')->set($instance, 'test');

Assert::equals('Test', $instance->fixture);
}

#[Test]
public function set_with_parameter() {
$type= $this->declare('{ public string $fixture { set(string $arg) => ucfirst($arg); } }');
$instance= $type->newInstance();
$type->properties()->named('fixture')->set($instance, 'test');

Assert::equals('Test', $instance->fixture);
}

#[Test, Expect(AccessingFailed::class)]
public function get_set_only_raises() {
$type= $this->declare('{ public string $fixture { set => ucfirst($value); } }');
$instance= $type->newInstance();

$type->properties()->named('fixture')->get($instance);
}

#[Test, Expect(AccessingFailed::class)]
public function set_get_only_raises() {
$type= $this->declare('{ public string $fixture { get => "test"; } }');
$instance= $type->newInstance();

$type->properties()->named('fixture')->set($instance, 'test');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ private function fixtures() {
DETAIL_RETURNS => 'string',
DETAIL_ARGUMENTS => [Modifiers::IS_PUBLIC | Modifiers::IS_READONLY]
];

yield $t;
if (PHP_VERSION_ID >= 80100) {
yield $this->declare('{ public readonly string $fixture; }');
}
}

#[Test, Values(from: 'fixtures')]
Expand All @@ -36,6 +32,11 @@ public function virtual_property_included_in_list($type) {
);
}

#[Test, Values(from: 'fixtures')]
public function is_virtual($type) {
Assert::true($type->properties()->named('fixture')->virtual());
}

#[Test, Values(from: 'fixtures')]
public function named_virtual($type) {
Assert::equals($type->property('fixture'), $type->properties()->named('fixture'));
Expand Down
Loading