Re: enum flag backed enum
| From: | Rowan Tommins [IMSoP] | Date: | Fri, 05 Sep 2025 20:01:04 +0000 |
| Subject: | Re: enum flag backed enum | ||
| References: | 1 2 3 4 5 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-128645@lists.php.net to get a copy of this message | ||
On 4 September 2025 15:50:08 BST, Rob Landers <rob@bottled.codes> wrote:
>I think this is a fair observation and a fair question; but I think it is important not to have
>"magic". The power-of-two rule is to make it possible to work back how $enum->value ===
>15 (0x1111) even if you are completely new to the language. If you just use some magical cardinal
>order, it is impossible to reserve ranges, handle communications with external systems, etc.
A set does not need elements to have a defined order, only a defined identity; that is available on
any enum, even one with no backing at all. That pure set could be serialised in various ways, based
on the available serialisations of its elements (comma-separated list, integer bitmask, binary
string bitmask, etc). That's the strongly typed model.
The weakly typed model is to keep the values permanently in their serialised form, and manipulate
that directly. That is, you have a set of integers for the flags, and construct a new integer for
the set of flags. That has the advantage of being simple and efficient, at the cost of safety and
easy tool affordance.
What you're suggesting sounds like somewhere between the two: the individual flags are of a
specific type, rather than raw integers, but the set itself is just an integer composed of their
"backing values". The big downside I see is that you can't natively label a parameter
or return value as being a "set of flags from this enum". You could probably make a
docblock type annotation work with an external static analyser, but in that case, you might as well
use that tool to enforce the powers of 2 on your enum or Plain Old Constants.
Indeed, enforcing "this integer must be a power of 2" is not really anything to do with
enums, it would be useful on *any* type declaration.
Personally, I find the concept of enum cases having a single backing value unnecessarily limiting,
and would much prefer Java-style case properties.
enum FooFlag: object {
case None(0, '');
case ThrowOnError(0b1, 'T');
// etc.
case PrettyPrint(0b1000, 'P');
// etc.
public function __construct(public int $flagForBinaryApi, public string $flagForTextApi){}
}
FooFlag::ThrowOnError->flagForBinaryApi; // 0b1
FooFlag:: PrettyPrint->flagForTextApi; // 'P'
Ideally we would declare that $flagForBinaryApi must be a power of 2, and that $flagForTextApi must
be a single character, using some general-purpose feature of the type system.
The only thing that might be enum-specific is a way to say whether the values of a property must be
unique (something we don't currently have with single-value backed enums).
Then the ideal would be an EnumSet customised to work with any properties or methods it wanted:
$foo = FooFlag::ThrowOnError | FooFlag::PrettyPrint;
$foo->serialiseForBinaryApi(); // 0b1001
$foo->serialiseForTextApi(); // 'TP'
Rowan Tommins
[IMSoP]