On Thu, 23 Nov 2023 at 08:48, Nicolas Grekas <nicolas.grekas+...@gmail.com> wrote:
> Sorry this comes as a surprise to you but you're rewriting history here. > The current behavior, the one that was fixed in that commit, matches how > PHP behaved before typed properties, so this commit brought consistency. > The question of "what does __get do with a property that has been declared but not assigned a value?" has no answer before PHP 7.4, because that situation simply never happened. So there isn't really one answer to what is "consistent". If I understand rightly, your position, and Nikita's, is that the behaviour should be consistent with the statement "if you have declared a property, access doesn't trigger __get unless you explicitly call unset()". This is what the change that slipped into 7.4.1 "fixed". The reason it surprised me is that I expected it to be consistent with a different statement: "if you have an __get method, this takes precedence over 'undefined variable' notices/warnings/errors". That statement was true in 7.3, and still true in the original implementation in 7.4.0 (including "unitialized" alongside "undefined"), but was *broken* by the change in 7.4.1: now, the "uninitialized property" error takes precedence over __get in the specific case of never having assigned a value. > About the behavior, it's been in use for many years to build lazy proxies. > I know two major use cases that leverage this powerful capability: Doctrine > entities and Symfony lazy services. There are more as any code that > leverages ocramius/proxy-manager relies on this. > Just to be clear, it is not the behaviour *after* calling unset which I am concerned about, it is the behaviour *before* calling unset or assigning any value. I was aware of the interaction with __get, but wrongly assumed that the rule was simply "uninitialized properties trigger __get". > About the vocabulary, the source tells us that "uninitialized" properties > that are unset() become "undefined". I know that's not super accurate since > a typed property is always defined semantically > Just "undefined" is not sufficiently unambiguous; you have to distinguish four different states: 1) Never declared, or added dynamically and then unset 2) Declared without a type, then unset 3) Declared with a type, not yet assigned any value 4) Declared with a type, then unset The messages presented to the user refer to both (1) and (2) as "undefined", and both (3) and (4) as "uninitialized". As it stands, the RFC would replace all instances of (2) with (4), but that still leaves us with two names for three states. Claude Pache wrote: > However, it is not a problem in practice, because users of classes implementing (1) but not (2) do not unset declared properties, ever. Nikita Popov wrote: > ... and then forbid calling unset() on declared properties Right now, unset() is also the only way to break a reference, other than another assign-by-reference, so it's quite reasonable to write "unset($this->foo)" instead of "$this->foo" to ensure a property is truly reset to a known state. Maybe we need a new function or operator to atomically break the reference and assign a new value, e.g. unreference($this->foo, 42); or $this->foo := 42; to replace unset($this->foo); $this->foo=42; Regards, -- Rowan Tommins [IMSoP]