Consensus gathering: allowing unsetting of backed property hooks
| From: | tight dot fork3192 at fastmail dot com | Date: | Tue, 25 Feb 2025 21:13:54 +0000 |
| Subject: | Consensus gathering: allowing unsetting of backed property hooks | ||
| Groups: | php.internals | ||
| Request: | Send a blank email to internals+get-126505@lists.php.net to get a copy of this message | ||
Hello! I started GitHub issue requesting that property hooks be allowed to be
unset():
https://github.com/php/php-src/issues/17922
While discussing in the issue it was suggested I pitch this to the mailing list, so here I am.
You can review the GitHub issue so I won't rehash the whole thing here, but I'll summarize
the main points for unset() of backed property hooks:
1. It is well-established in PHP that one can unset() object properties. If we cannot
unset() property hooks - which to a consumer are indistinguishable from plain
properties - then the consumer must now be aware of the object's internal implementation before
doing what used to be explicitly allowed. This is both poor encapsulation and not compatible with
the commonly-understood behavior of unset(), leading to surprising cases where a
consumer of an object could cause a fatal error by trying to unset() a property that
unbeknownst to them is implemented as a property hook.
2. Getters may perform a calculation, or fetch from a database, and cache the result in the backing
value, but without unset() we can end up in a state where the logic to determine that
value can never be executed again. The workaround is exposing a public method that contains such
logic, but that defeats the point of putting logic in property hooks in the first place, and exposes
internal class implementation details that should not concern the consumer. Getting and caching
values is a common case in ORM layers, one of the biggest users of PHP.
3. It would be convenient to be able to unset() backed properties when changing data
elsewhere in a class, for example to trigger a recalculation or re-fetching from the database.
It's not enough to set such properties to null because 1) the actual type of the
property may in fact not be nullable, leading to inconsistency in the property's PHP type and
the database column type, and 2) in practice null is a real value that can come from
the database, which is a distinct concept from "uninitialized at the PHP level". (This is
the old isset() vs is_initialized() problem, a tired struggle going on
decades now.)
Some arguments raised against were internal implementation details; that unset() itself
is a bad design choice so we should not cater to it; and that catering to uninitialized
makes typing more complex.
I can't comment on implementation details.
To the last two points, I would say that for better or worse, unset() and
uninitialized are part of PHP and I assume they're here to stay. Therefore if
we're extending the language, we should extend it in ways that embrace, not ignore, its warts,
because they're what make the language and they're what decades of developers are
accustomed to. Ignoring the warts, or leaving them in the language but calling them bad practice
that must be avoided, results in footguns and a language that is inconsistent with itself. (As
I'm sure you're all aware, inconsistency is one of the favorite charges leveled at PHP by
its detractors.)
And while uninitialized may make types more complex, it can often be very useful,
especially when filling objects from database rows - one of the most common tasks a web developer
will ever do. It might be technically purer to require that objects only be created with lengthy
constructors that initialize every property with strongly typed values, and unset() and
isset() never be used, but for better or worse that's just not how PHP evolved.
Having the flexibility of objects with uninitialized, and therefore unsettable, properties is very
useful. This flexibility is part of what makes PHP so attractive and what made it so popular in the
first place.
In the GitHub thread it was mentioned that an RFC would be required. I've never written one,
nor am I familiar with PHP internals, but I can try putting one together if there's interest.