[go: nahoru, domu]

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

[RFC] Property hooks #13455

Merged
merged 1 commit into from
Jul 14, 2024
Merged

[RFC] Property hooks #13455

merged 1 commit into from
Jul 14, 2024

Conversation

iluuu1994
Copy link
Member

Zend/zend_vm_def.h Outdated Show resolved Hide resolved
Comment on lines +173 to 175
case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
/* The argument passing optimizations are valid for prototypes as well,
* as inheritance cannot change between ref <-> non-ref arguments. */
Copy link
Member

Choose a reason for hiding this comment

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

Since hooks never receive by ref, we may be able to optimize ops to their non-ref variants like we do for known functions. E.g. for this code:

    public mixed $x {
        set => parent::$x::set($value['x']);
    }

we generate this:

0000 CV0($value) = RECV 1
0001 INIT_PARENT_PROPERTY_HOOK_CALL 1 string("x") 1
0002 CHECK_FUNC_ARG 1
0003 V1 = FETCH_DIM_FUNC_ARG CV0($value) string("x")
0004 SEND_FUNC_ARG V1 1
0005 DO_FCALL
0006 RETURN null

and it should optimize to something like this once the optimizer knows that hooks do not receive by ref:

0000 CV0($value) = RECV 1
0001 INIT_PARENT_PROPERTY_HOOK_CALL 1 string("x") 1
0003 T1 = FETCH_DIM_R CV0($value) string("x")
0004 SEND_VAL T1 1
0005 DO_FCALL
0006 RETURN null

Copy link
Member Author

Choose a reason for hiding this comment

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

Right. Parent hooks are not very optimized at this moment. I think they will be rare in practice. I'll have a look nonetheless. We might be switching to a different syntax though (parent::$prop and parent::$prop = $value), if possible.

Zend/zend_ast.h Outdated Show resolved Hide resolved
Zend/zend_vm_def.h Outdated Show resolved Hide resolved
Zend/zend_compile.c Show resolved Hide resolved
Zend/zend_inheritance.c Outdated Show resolved Hide resolved
Zend/zend_inheritance.c Show resolved Hide resolved
Zend/zend_vm_def.h Outdated Show resolved Hide resolved
Zend/zend_compile.c Outdated Show resolved Hide resolved
Zend/zend_object_handlers.h Outdated Show resolved Hide resolved
Zend/zend_vm_def.h Outdated Show resolved Hide resolved
Zend/zend_ast.c Outdated Show resolved Hide resolved
Zend/zend_compile.h Show resolved Hide resolved
@TRowbotham
Copy link
Contributor

Potential scoping issue when hooks are defined in traits. Not sure if this is the expected behavior or not.

https://3v4l.org/8NILP/rfc#vrfc.property-hooks

@iluuu1994 iluuu1994 self-assigned this Mar 27, 2024
@iluuu1994 iluuu1994 changed the title [RFC] Property hooks Trait bug - https://github.com/php/php-src/pull/13455#issuecomment-2017098635 Mar 27, 2024
@iluuu1994 iluuu1994 changed the title Trait bug - https://github.com/php/php-src/pull/13455#issuecomment-2017098635 [RFC] Property hooks Mar 27, 2024
@iluuu1994
Copy link
Member Author

@TRowbotham Thanks for letting me know. I added this to my todo list.

@TRowbotham
Copy link
Contributor

Here is another one: https://3v4l.org/7Al8b/rfc#vrfc.property-hooks

When hooks are defined in a class that implements the \Iterator interface the iterator returns the values of each property's get hook instead of the values that the iterator contains.

@iluuu1994
Copy link
Member Author

@TRowbotham Good catch! I'll have a look at that one as well.

@TRowbotham
Copy link
Contributor

Ran into a behavior difference between __get/__set and property hooks when a property hook's setter calls a function, which reads a property hook's value from a different scope. With __get/__set it works without error, however, the property hooks version reports a fatal error. I couldn't tell from the RFC if the difference in behavior was intentional.

(property hooks) https://3v4l.org/6W26c/rfc#vrfc.property-hooks
(__get/__set) https://3v4l.org/3pUtD/rfc#vrfc.property-hooks

@iluuu1994
Copy link
Member Author

@TRowbotham This is intended behavior. Inside functions called from a hook, $this->prop refers to the backing field, as it would in the hook. However, the compiler cannot infer that that a backing field must be added, since it isn't used directly in the hook. As such, the property is marked as virtual. When doSomething is called, it tries to access this backing field that doesn't exist.

An additional difference is that, for the __get/__set case, each magic method is "guarded" on it's own. Guarding in this context means that inside the magic method, it won't be called again, but the underlying property will be accessed. If you assign a variable, invoking __set, __get can still be invoked inside __set. The same is not true for hooks, once you're in the hook $this->prop always refers to the backing value. This is intentional, because otherwise read/write assignments such as ++ within set might unexpectedly call your getter. Furthermore, it allows for more flexibility in the future, where self::$prop::get() may still be used as an explicit way to invoke the other hook.

I hope that clears it up.

@TRowbotham
Copy link
Contributor

@iluuu1994 Thanks for the explanation. That makes it clear.

@rodrigoslayertech
Copy link

Congratulations!!! RFC passed! PHP 8.4 will be AWESOME!!!

@ramsey
Copy link
Member
ramsey commented Apr 16, 2024

@rodrigoslayertech The voting is open until 29 April. Let's not count our chickens before they hatch. ;-)

@MarienMupenda
Copy link

Awsome, this sounds like the dynamic properties we have in laravel models, voted.

@claudepache
Copy link
Contributor
claudepache commented Apr 29, 2024

Hi,

While playing with the feature on 3v4l.org, I found the following bug. Although I’ve not tested the PR in its current state, I suspect that the bug might be still here, because I didn’t find test against it.

Test case (https://3v4l.org/fuj6p/rfc#vrfcproperty-hooks):

abstract class A {
    abstract public $x { get; }
}

class C extends A {
    private $_x;
    public $x {
        get => $this->_x;
    }
}

// expected: bool(true); actual: bool(false)
var_dump((new ReflectionProperty(C::class, 'x'))->isVirtual());

// expected: Error: Property C::$x is read-only; actual: no error
$c = new C;
$c->x = 3;

The test case works as expected when I replace the abstract class with an interface (https://3v4l.org/ed1TG/rfc#vrfc.property-hooks).

(Background: I expect to use that pattern in order to approximate asymmetric visibility.)

@iluuu1994
Copy link
Member Author

@claudepache The 3v4l build is indeed outdated. In any case, I'll make sure to have a look at your example. Thanks!

@TRowbotham
Copy link
Contributor

While continuing to play with this, I came across a case where using the get hook as a generator results in a segfault.

<?php

class A {
    public Generator $prop {
        get {
            for ($i = 0; $i < 3; ++$i) {
                yield $i;
            }
        }
    }
}

$a = new A();
var_dump($a->prop->valid());

@iluuu1994
Copy link
Member Author
iluuu1994 commented Jul 4, 2024

@TRowbotham Thanks! This wasn't really specified in the RFC. I fixed the issue, but also raised it to Larry to discuss over whether we even want to allow yield. Edit: Talked it over with Larry and Bob. There's no good reason not to allow this, so we will.

@iluuu1994
Copy link
Member Author

The implementation now solves all known issues, and CI (including nightly) passes. So I will be merging it in the coming days.

@iluuu1994 iluuu1994 marked this pull request as ready for review July 11, 2024 15:27
}

/* Get-only virtual property can never be written to. */
if ((prop->flags & ZEND_ACC_VIRTUAL) && !prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
Copy link
Member

Choose a reason for hiding this comment

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

Is it the intention that ZEND_ACC_VIRTUAL implies the existence of hooks? If we allow to specify @virtual in the stub file in a follow-up to avoid allocating backing storage, then this check can potentially dereference a NULL pointer because prop->hooks isn't necessarily filled in.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know all the checks by heart but at least here, ZEND_ACC_VIRTUAL assumes hooks are present. I presume your intention is to use @virtual for existing virtual properties implemented using the existing object property handlers? Tweaking the logic where necessary to check for the existence of hooks would be ok with me, if these cannot immediately be migrated to hooks (in part because internal classes aren't supported yet). But I'd prefer doing that after this has merged.

Copy link
Member

Choose a reason for hiding this comment

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

I presume your intention is to use @virtual for existing virtual properties implemented using the existing object property handlers?

Indeed.

Tweaking the logic where necessary to check for the existence of hooks would be ok with me, if these cannot immediately be migrated to hooks (in part because internal classes aren't supported yet)

I don't know how internal hooks would look like so can't really comment on this (yet).

But I'd prefer doing that after this has merged.

Of course

Copy link
Member Author
@iluuu1994 iluuu1994 Jul 12, 2024

Choose a reason for hiding this comment

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

I don't know how internal hooks would look like so can't really comment on this (yet).

My intention is to allow marking the property with { get; set; } (or possibly just annotations until we have PHP-Parser support), which will look for and link the corresponding function into zend_property_info.hooks. So, hopefully it will not be too complicated.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, those would be zend_function's right? That's a bit of a pitty because it would require completely reworking the virtual properties in DOM/XMLReader with the additional penalty of having to deal with a stackframe, unless they can be frameless.

Copy link
Member Author

Choose a reason for hiding this comment

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

🤔 Good question. Avoiding the stack frame was not planned for now, but I'll see what's possible once I get to it.

iluuu1994 added a commit to iluuu1994/php-src that referenced this pull request Jul 14, 2024
RFC: https://wiki.php.net/rfc/property-hooks

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>

Closes phpGH-13455
iluuu1994 added a commit to iluuu1994/php-src that referenced this pull request Jul 14, 2024
Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>

RFC: https://wiki.php.net/rfc/property-hooks

Closes phpGH-13455
@iluuu1994 iluuu1994 force-pushed the property-hooks branch 2 times, most recently from ee4d7e2 to 9f295d9 Compare July 14, 2024 09:51
RFC: https://wiki.php.net/rfc/property-hooks

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
@iluuu1994 iluuu1994 merged commit 780a828 into php:master Jul 14, 2024
8 of 11 checks passed
@Wirone
Copy link
Wirone commented Jul 16, 2024

@iluuu1994 I know it's already merged, but I have question: wouldn't it be a good idea to introduce T_PROPERTY_HOOK_BRACE_OPEN and T_PROPERTY_HOOK_BRACE_CLOSE, so tools based on tokenizer could take direct advantage of it for their logic? Without this, PHP-CS-Fixer probably needs to introduce new custom token and transformer to make it easier for all the rules to hook into new syntax.

@iluuu1994
Copy link
Member Author

@Wirone Unfortunately, we are lacking the context in the lexer ourselves to do that.

}
}

static bool zend_compile_parent_property_hook_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */
Copy link

Choose a reason for hiding this comment

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

Don't know if anyone cares, but this comment (/* {{{ */) seems to be mismatched (no closing braces).

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. I fixed it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet