On Mon, May 8, 2023, at 9:38 PM, Larry Garfield wrote: > Ilija Tovilo and I would like to offer another RFC for your > consideration. It's been a while in coming, and we've evolved the > design quite a bit just in the last week so if you saw an earlier draft > of it in the past few months, I would encourage you to read it over > again to make sure we're all on the same page. I'm actually pretty > happy with where it ended up, even if it's not the original design. > This approach eliminates several hard-to-implement edge cases while > still providing a lot of functionality in one package. > > https://wiki.php.net/rfc/property-hooks
Thanks everyone for your feedback so far. I'm going to collapse the replies into one message to reduce noise. On Tue, May 9, 2023, at 7:57 AM, Tim Düsterhus wrote: > (1) How does the set() hook interact with #[\SensitiveParameter]? Internally, the hook is a method, so it should respect it the same way as any other method. Viz, you should be able to write: ``` public string $foo { set ($[\SensitiveParameter] string $value) { $field = strtolower($foo); } } ``` I'll ask Ilija to add a test to confirm that. > (2) How will a Stack Trace emitted from a hook look like? Please include > an example of the Exception::__toString() output or so. I'll ask Ilija to add a test to his branch and link it here. > (3) ReflectionProperty::getHook(): Will this throw if an invalid hook > name is given or will this return null? Null if you ask for get/set and one doesn't exist. Error if you ask for something other than "get" or "set". We're still keeping it as a single method, though, to make supporting other hooks easier in the future if desired. I've updated the RFC to clarify that. > (4) The "Unaffected PHP Functionality" section is empty. It should > either be filled in or removed. Removed. I don't quite understand the purpose of that section. :-) On Tue, May 9, 2023, at 9:54 AM, Nicolas Grekas wrote: > - does set($value) { mean set(mixed $value) { or set(TypeFromProp > $value) { ? I understand it would be the latter but it might be nice to > clarify (unless I missed it) Conceptually, I believe it means set(TypeFromProp $value). Since at present that cannot be enforced, though, they're basically the same thing. (If someone with more engine-fu than either of us wants to help enforce contravariant types in set, please speak up because we'd prefer to do so.) > - function setFullName => ucfirst is not found in the "set" version Yes it is. Look at the end of the `set` line. > - could parent::$x::set($x) be replaced by parent::$x = $x ? Same for > the get variant? Why not if not? There's a subtle but important distinction here. parent::$x would suggest accessing the parent *property*, ie, its backing value, directly. What is actually intended here is accessing the parent *hook*, ie, its equivalent to parent::setFoo($foo). The property itself is already available as $field. We're open to alternate syntaxes for accessing the parent accessor syntax if people have good (and easily implementable) suggestions. > - readonly: could this be supported by materializing them, using the > storage for memoization after first access? that'd be quite powerful That was one of my early ideas, as it then became a cheap form of cached lazy property. However, that then means there's no meaningful way to unset the cache. In the end we decided that there were too many nooks and crannies to readonly to try and make sense of. (The more I use it, the more I think readonly was a mistake and we should have gone straight to aviz.) (As a side note, if we had aviz, then we could use the field itself as a cache by making writes private, like so: public string $name => $field ??= $this->first . $this->last; But right now that would also imply public-set, which we wouldn't want. Aviz would solve that issue entirely. Another reason aviz is an important and useful feature. :-) ) What a readonly flag on a set-only property means... I have no idea. > - I'm not sure we need ReflectionProperty::getHook('set') when we can > do getHooks()['set'] ?? null The double-methods were mainly for consistency with the rest of the Reflection API, which tends to have both getFoo(): object and getFoos(): array options. It's no more redundant than the rest of Reflection. > - What does ReflectionMethod::getName return on a hook? And getClosure? Either "get" or some hashed string that includes "get". I'm not sure off hand. I'll have to check with Ilija. > - Should we add ReflectionProperty::IS_VIRTUAL ? Funny story. I just asked Ilija this, and he said he already did add it for testing purpose but forgot to tell me. :-) RFC updated. > - I'm a bit surprised by the possibility of adding attributes to hooks. > I'm not sure I see the use case and I think this might be confusing in > addition to attributes on the property itself. I think in practice it's actually more work not to, since hooks under the hood are just methods like anything else. And as Tim noted above, there are use cases like #[SensitiveParameter] for attributes even down here. > - For serialization: exclude them from var_export() and serialize(), > which don't need virtual props to rebuild the state (you mean > json_encode(), not JsonSerializable on that list), call get for > var_dump/print_r when it's defined (but ignore exceptions). But (array) / > get_mangled_object_vars() / get_object_vars() shouldn't call any "get". I don't follow. json_encode() is on the list. > I have a bigger concern: the take on references contradicts with the intro > about BC breaks: turning a materialized property into virtual one would > break BC as far as refs are concerned. One idea to fix that: add a ref > hook, that must return a reference. If not implemented => Error when trying > to get-by-ref. This could also help solve the $foo->array[] case (including > the asym-visiblity issue). That might be possible. The issue there is that the returned ref would still be bypassing the get/set hooks, so you're just adding a way to dance around the existing hook implementations, which is exactly what we're trying to avoid. Also, what would that hook even mean if used on a virtual property? Moreover, in practice, getting a reference to a property is extremely rare, with the exception of array writes. I cannot recall the last time I even saw some code get a reference to a property. That's why we felt comfortable just leaving it at is. In an earlier version of the RFC we had a way for properties to just disable references without adding any hooks, but removed it on the grounds that it was not worth the effort. On Tue, May 9, 2023, at 11:52 AM, Dik Takken wrote: > I'm not sure which solution would be best here: Just accept the > limitations for arrays (same as for list properties in Python) or forbid > the use of hooks for array properties? I'm leaning towards accepting the > limitations, like Python does. Then it is up to the API designer to > decide if a property hook is the right tool for the job or not. We considered disallowing it on array properties; the problem is, there's also mixed and iterable properties that would have the same challenge, but that couldn't be detected at compile time. That means the runtime check has to be there anyway, so also having a compile-time check for hooks on arrays doesn't really buy us anything but more engine code. So yes, Python-style "just deal with it *sunglasses*" is the most logical approach. On Tue, May 9, 2023, at 12:38 PM, Benjamin Außenhofer wrote: > An error maybe, the "class C" example in "Detailed Proposal > set" uses a > "$this->_prop", but probably meant to use $this->_names, since private > array $_names; is declared in the example. Yep, typo, thanks. Fixed now. > I think $field should be its own "chapter". Its a central part of the > proposal that should be clarified early and so that readers don't > accidentally skip it. > > I am also confused why $field exists when $this->propertyName works and why > its not recommended to be used. Is $field a reference? or does the "compile > time macro" part mean its replaced at compile time? Ifso, this feels > different to anything else PHP, i am leaning towards $this->propertyName if > there are no other compelling reasons why $field should be used. Regarding $field vs. $this->propName, there's a few reasons we went that route. 1. It's shorter and less typing for what will be a common pattern. 2. That makes it consistent between hook implementations. In working on examples, I found many cases where I was adding basically the same line to multiple hooks on the same class. Making the code easier to copy-paste seems like a win. 3. It also will be helpful if hook packages are added in the future, as they'll need a more "generic" way to access the backing property. (See Future Scope.) Eg, "$field = someLogic($value)" applied to a dozen different properties; it wouldn't work if it was "$this->specificProperty = someLogic($value)". 4. We're used to our eyes glossing over "$this->propName", as it's so common. Having a separate name to mentally scan for to determine if a property is virtual or not seems like it will be helpful in practice. 5. There's precedent for it: Kotlin has almost the same functionality as we describe here, and uses a `field` variable in the exact same way. So it's mainly an ergonomics argument rather than a technical one. "Compile time macro" means it translates to the same AST as if you'd used $this->propName. There's precedent for that. Constructor Property Promotion works basically the same way. --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php