On 4 September 2025 15:50:08 BST, Rob Landers <[email protected]> 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]