Hi,
I'd like to propose an improvement to backed enumerations introduced in PHP 8.1.
When using enums, it's very common to get all values using the following boilerplate:
$enumValues = array_map(
fn (\BackedEnum $case) => $case->value,
ExampleEnum::cases()
);
The primary use case is when an enum is used in a "set" (array of values).
For example, a user inputs a subset of available values.
The input may be optional defaulting to all values if omitted.
For example, an array option of a CLI tool:
$inputValues = $input->getOption('enum') ?: $enumValues
The secondary use case is for informational purposes.
For example, the CLI tool may list all available values of an enum option in --help.
The list of values can also be used to produce informative error messages:
$output->writeln("Option is invalid, allowed values: " . implode(', ', $enumValues))
The last use case is input validation.
Although tryFrom() can be used, having access to all values can come in handy.
For instance, you could immediately get valid and invalid enum values:
$invalidValues = array_diff($inputValues, $enumValues);
The implementation would be more verbose and wasteful otherwise:
$invalidValues = array_filter($inputValues, fn ($value) => !ExampleEnum::tryFrom($value));
The proposal is to add the values getter to backed enums:
interface BackedEnum extends UnitEnum
{
public static values(): array;
}
$enumValues = ExampleEnum::values();
Indeed, it's possible to implement this userland in a trait reused across enums.
However, the implementation in C will be substantially more performant.
For instance, the values() method call can resolve to a constant!
An alternative syntax is a pseudo-constant, for example, ExampleEnum::values.
Unlike the constant, the method would be part of the interface complementing cases().
Method cases() itself is not a constant only because it returns instances.
I'm fine with either syntax leaning towards the method for consistency.
Please let me know your thoughts.
With enough support, I'd be happy to submit an RFC and implement it myself.
Some guidance on the performance optimization aspect may be necessary though.
I'm still missing the karma necessary to create RFCs in the wiki.
Regards,
Sergii Shymko
Hi,
I'd like to propose an improvement to backed enumerations introduced in
PHP 8.1.
When using enums, it's very common to get all values using the
following boilerplate:
$enumValues = array_map(
fn (\BackedEnum $case) => $case->value,
ExampleEnum::cases()
);The primary use case is when an enum is used in a "set" (array of values).
For example, a user inputs a subset of available values.
The input may be optional defaulting to all values if omitted.
For example, an array option of a CLI tool:
$inputValues = $input->getOption('enum') ?: $enumValuesThe secondary use case is for informational purposes.
For example, the CLI tool may list all available values of an enum
option in --help.
The list of values can also be used to produce informative error
messages:
$output->writeln("Option is invalid, allowed values: " . implode(', ',
$enumValues))The last use case is input validation.
Although tryFrom() can be used, having access to all values can come in
handy.
For instance, you could immediately get valid and invalid enum values:
$invalidValues = array_diff($inputValues, $enumValues);
The implementation would be more verbose and wasteful otherwise:
$invalidValues = array_filter($inputValues, fn ($value) =>
!ExampleEnum::tryFrom($value));The proposal is to add the values getter to backed enums:
interface BackedEnum extends UnitEnum
{
public static values(): array;
}
$enumValues = ExampleEnum::values();Indeed, it's possible to implement this userland in a trait reused across enums.
However, the implementation in C will be substantially more performant.
For instance, the values() method call can resolve to a constant!An alternative syntax is a pseudo-constant, for example,
ExampleEnum::values.
Unlike the constant, the method would be part of the interface
complementing cases().
Method cases() itself is not a constant only because it returns
instances.
I'm fine with either syntax leaning towards the method for consistency.Please let me know your thoughts.
With enough support, I'd be happy to submit an RFC and implement it myself.
Some guidance on the performance optimization aspect may be necessary though.
I'm still missing the karma necessary to create RFCs in the wiki.Regards,
Sergii Shymko
I would be OK with this, assuming the method approach. (The class constant feels clunkier.)
--Larry Garfield
I'd like to propose an improvement to backed enumerations introduced in
PHP 8.1.
When using enums, it's very common to get all values using the following
boilerplate:
$enumValues = array_map(
fn (\BackedEnum $case) => $case->value,
ExampleEnum::cases()
);
Since the value is exposed as a public property, there is already a much
shorter way of writing this:
$enumValues = array_column(BackedEnum::cases(), 'value');
As I pointed out in a StackOverflow answer [
https://stackoverflow.com/a/71235974/157957], you can get the case name the
same way, and use different arguments to array_column to get combinations
like a look up table from value to name:
$nameToValue = array_column(BackedEnum::cases(), 'name', 'value');
Regards,
Rowan Tommins
[IMSoP]
Hi
BackedEnum::values() is much more convenient than array_column(BackedEnum::cases(), 'value');
I suspect the answer is "preference", but I have to ask: Why?
Personally looking at that I find it more convenient to use an existing
function that I can apply to any array or object than having to learn
about a new convenience method that is a few characters shorter, but
entirely specific to enums.
It also raises the question why your proposal only includes
BackedEnum::values() instead of also including UnitEnum::names()?
I find that I seldomly have the need for backed enums in the first
place. The name is sufficient to uniquely identify the case and it's
also the value that is used when serialize()
ing an enum.
Best regards
Tim Düsterhus
PS: I appreciate that you follow the "no top post" rule, however I find
it hard to identify the end of the quoted text and the beginning of your
reply, because there is no clear separator or indicator. It also appears
to break the externals.io parser [1], which is unfortunate. Is it
possible for you to configure your email client to use a quote format
that includes the standard '> ' prefix in quoted lines?
IMO, other variations of
array_column()
returning a map don't seem to be
useful in practice.
Particularly, what is knowing the case name good for? Do you propose to
resolve it to the case instance like so?
$case = constant(ExampleEnum::class . '::' . $caseName);
My understanding is that backed enums serve the purpose of doing the
mapping.
The same argument can be made for your validation examples: the enum is
"doing the mapping" when you use tryFrom.
My opinion on this probably comes from my general opinion of backed enums -
to me, the opaque case object is primary, and the "value" is just an
arbitrary piece of data attached. I would have preferred to be able to
attach any number of such properties, e.g.
Options::verbose->longArgumentName, Options::verbose->shortArgumentName.
Regards,
Rowan Tommins
[IMSoP]