Hi people!
Today I tried to do ResourceType::from()
and was surprised that my IDE
immediately screamed at me. After further investigation, I realized that
Basic Enums (this is what the RFC called it [1]) does not have these
commodities backed into them.
I did some digging and I found 2 main discussions: Stringable by default
[2][3] and backed-in commodities [4]. I think Benjamin, Derick and Nicolas
have argued why Stringable by default may not be desirable and I only
mention this for completeness of the conversation. I don't want to talk
about that at all. Instead, I want to focus on the other side: construct
from string by default (from/tryFrom()).
I also read Larry's post that has been shared throughout these discussions
[5] and it seems fair and sound to use guardrails to discourage unintended
use. This gets into the territory of "let me do what I want, I know what
I'm doing" vs "let me limit what you can do because I built it and I know
exactly what it was intended for", which I also want to try and steer away
from debating, each camp has its merits.
Bilge said:
My question, then, is why can't basic enumerations have these semantics by
default? Or, to state it more concretely, what would be the downside to
having all "basic" enumerations actually being "backed" enumerations whose
values implicitly mirror their names for the purposes of converting to/from
strings? Would this not make basic enumeration more useful without any
particular downsides?
While I'm searching for similar clarification, I want to pose the question
differently. I feel like the answer to his question is in Larry's article
about discouraging fancy strings. My question boils down purely to: can
Basic Enums implement ::from() / tryFrom() methods?
Larry said:
[...] In your case, you want to "upcast" a string to an enum. That means
you're doing some sort of deserialization, presumably. In that case, a
backed enum is what you want. A unit enum isn't serializable, by design.
Although this starts to thread into very pedantic territory, I think, in
fact, a unit enum (assuming it means Basic enum) is in fact always
serializable to a string: $enum->value
. By design, the value is a string
and it cannot have duplicate values in a single enum. It means it's
extremely easy to define an Enum in PHP and at some point store it in a
storage engine / database in the form of $enum->value
. Now if I want to
get back my Enum at a later stage I need to implement exactly the same code
that already exists in the PHP engine behind Enum::from()
. Maybe it's not
serializable in the sense that it doesn't implement any true serialization
mechanism, which I'm guessing a backed-enum probably does, but I'm trying
to come from the very practical application of creating a Basic Enum at an
HTTP context (which is the bread and butter of PHP) and then recovering
said Enum in a background worker context without having to use PHP
serialize()
function and store PHP-specific dialect in a database that is
used by multiple teams and programming languages.
I also take the point that it is easy to argue against all this: just put
: string
on your Enum and duplicate the names with values. Still, this
doesn't address the "surprise effect" of "why this Enum doesn't have
::from() in it?". There doesn't exist any other value (string or otherwise)
that could be used in ::from() or ::tryFrom() in a Basic Enum, which could
make it less contentious. Also, in the spirit of NOT making Enums "Fancy
strings", I'm looking for ways to reconstruct my Enum and all the behaviors
available inside of it without even having to associate or think about a
string. The only reason a string comes into the discussion is because
$enum->value is one and is stored. I also checked and:
enum Foo {
case 1;
case 2;
}
is a parse error. [6].
Larry has also suggested that instead of making Basic Enum implement
::from()
and ::tryFrom()
we could instead offer auto-populated
String-Backed Enum values. That means transforming this:
enum Foo: string {
case Bar;
case Baz;
}
(which is a Fatal Error today) into this:
enum Foo: string {
case Bar = 'Bar';
case Baz = 'Baz';
}
I also like this proposal. Although semantically, I think it would be
better / have been better to have Basic Enum implementing the ::from()
methods, one could argue that adding it now could be a breaking change
since people could have Basic Enum already implementing their own custom
::from() method.
In conclusion, the "complex" (as opposed to primitive) object Enum is not a
Fancy String and where I'm coming from I believe to be in sync with that
mindset. However, PHP is highly used in Web context where we may need to
use asynchronous processes to make API calls fast and schedule executions
at a different context which often involves serialization. As such, being
able to reconstruct a Basic Enum seems a rather fundamental need that we
can still make it viable by making a small : string
change to the Enum
and opting-in into the from / tryFrom utilities. This should not affect
Int-Backed Enums at all.
Where casing is concerned (camelCase, PascalCase, snake-case, etc) [7], one
can argue that if you want to have full control over casing, you should
definitely take control over the values of your Enum. The beauty (in my
mind) about making it default to the enum name is that it doesn't matter if
I follow PER-CS rules or not, the truth is I don't need to think about
strings at all because my Enum is not a Fancy string.
I didn't intend to write such a long email, but I'm really keen on hearing
arguments against everything I shared to see if there are any flaws in my
thought process.
[1] https://wiki.php.net/rfc/enumerations#basic_enumerations
[2] https://externals.io/message/118040
[3] https://externals.io/message/124991
[4] https://externals.io/message/123388
[5] https://peakd.com/hive-168588/@crell/on-the-use-of-enums
[6] https://3v4l.org/cDISV#v8.4.10
[7] https://externals.io/message/123388#123394
--
Marco Deleu
You probably know it, but I wanted to note that you can implement it in
userland with minimal effort: https://3v4l.org/DoXJo#v8.4.10
--
Best regards,
Bruce Weirdan mailto:
weirdan@gmail.com
Hi people!
Today I tried to do
ResourceType::from()
and was surprised that my IDE
immediately screamed at me. After further investigation, I realized that
Basic Enums (this is what the RFC called it [1]) does not have these
commodities backed into them.I did some digging and I found 2 main discussions: Stringable by default
[2][3] and backed-in commodities [4]. I think Benjamin, Derick and Nicolas
have argued why Stringable by default may not be desirable and I only
mention this for completeness of the conversation. I don't want to talk
about that at all. Instead, I want to focus on the other side: construct
from string by default (from/tryFrom()).I also read Larry's post that has been shared throughout these discussions
[5] and it seems fair and sound to use guardrails to discourage unintended
use. This gets into the territory of "let me do what I want, I know what
I'm doing" vs "let me limit what you can do because I built it and I know
exactly what it was intended for", which I also want to try and steer away
from debating, each camp has its merits.Bilge said:
My question, then, is why can't basic enumerations have these semantics by
default? Or, to state it more concretely, what would be the downside to
having all "basic" enumerations actually being "backed" enumerations whose
values implicitly mirror their names for the purposes of converting to/from
strings? Would this not make basic enumeration more useful without any
particular downsides?While I'm searching for similar clarification, I want to pose the question
differently. I feel like the answer to his question is in Larry's article
about discouraging fancy strings. My question boils down purely to: can
Basic Enums implement ::from() / tryFrom() methods?Larry said:
[...] In your case, you want to "upcast" a string to an enum. That means
you're doing some sort of deserialization, presumably. In that case, a
backed enum is what you want. A unit enum isn't serializable, by design.Although this starts to thread into very pedantic territory, I think, in
fact, a unit enum (assuming it means Basic enum) is in fact always
serializable to a string:$enum->value
. By design, the value is a string
and it cannot have duplicate values in a single enum. It means it's
extremely easy to define an Enum in PHP and at some point store it in a
storage engine / database in the form of$enum->value
. Now if I want to
get back my Enum at a later stage I need to implement exactly the same code
that already exists in the PHP engine behindEnum::from()
. Maybe it's not
serializable in the sense that it doesn't implement any true serialization
mechanism, which I'm guessing a backed-enum probably does, but I'm trying
to come from the very practical application of creating a Basic Enum at an
HTTP context (which is the bread and butter of PHP) and then recovering
said Enum in a background worker context without having to use PHP
serialize()
function and store PHP-specific dialect in a database that is
used by multiple teams and programming languages.I also take the point that it is easy to argue against all this: just put
: string
on your Enum and duplicate the names with values. Still, this
doesn't address the "surprise effect" of "why this Enum doesn't have
::from() in it?". There doesn't exist any other value (string or otherwise)
that could be used in ::from() or ::tryFrom() in a Basic Enum, which could
make it less contentious. Also, in the spirit of NOT making Enums "Fancy
strings", I'm looking for ways to reconstruct my Enum and all the behaviors
available inside of it without even having to associate or think about a
string. The only reason a string comes into the discussion is because
$enum->value is one and is stored. I also checked and:enum Foo {
case 1;
case 2;
}is a parse error. [6].
Larry has also suggested that instead of making Basic Enum implement
::from()
and::tryFrom()
we could instead offer auto-populated
String-Backed Enum values. That means transforming this:enum Foo: string { case Bar; case Baz; }
(which is a Fatal Error today) into this:
enum Foo: string {
case Bar = 'Bar';
case Baz = 'Baz';
}I also like this proposal. Although semantically, I think it would be
better / have been better to have Basic Enum implementing the ::from()
methods, one could argue that adding it now could be a breaking change
since people could have Basic Enum already implementing their own custom
::from() method.In conclusion, the "complex" (as opposed to primitive) object Enum is not
a Fancy String and where I'm coming from I believe to be in sync with that
mindset. However, PHP is highly used in Web context where we may need to
use asynchronous processes to make API calls fast and schedule executions
at a different context which often involves serialization. As such, being
able to reconstruct a Basic Enum seems a rather fundamental need that we
can still make it viable by making a small: string
change to the Enum
and opting-in into the from / tryFrom utilities. This should not affect
Int-Backed Enums at all.Where casing is concerned (camelCase, PascalCase, snake-case, etc) [7],
one can argue that if you want to have full control over casing, you should
definitely take control over the values of your Enum. The beauty (in my
mind) about making it default to the enum name is that it doesn't matter if
I follow PER-CS rules or not, the truth is I don't need to think about
strings at all because my Enum is not a Fancy string.I didn't intend to write such a long email, but I'm really keen on hearing
arguments against everything I shared to see if there are any flaws in my
thought process.[1] https://wiki.php.net/rfc/enumerations#basic_enumerations
[2] https://externals.io/message/118040
[3] https://externals.io/message/124991
[4] https://externals.io/message/123388
[5] https://peakd.com/hive-168588/@crell/on-the-use-of-enums
[6] https://3v4l.org/cDISV#v8.4.10
[7] https://externals.io/message/123388#123394--
Marco Deleu
It's important to keep in mind that when non-backed enums have a value by
default, it will be used as such. When inevitably the assumption is made
that a value can be used because it exists, and then the enum name is
changed, the "from" will break as well. If I rename a case and someone else
is using this in a database and neither of us consider the name to be
changed, we're now stuck with datacorruption and broken code. I prefer this
to remain separate as it is.
Hi people!
Today I tried to do
ResourceType::from()
and was surprised that my IDE immediately screamed at me. After further investigation, I realized that Basic Enums (this is what the RFC called it [1]) does not have these commodities backed into them.I did some digging and I found 2 main discussions: Stringable by default [2][3] and backed-in commodities [4]. I think Benjamin, Derick and Nicolas have argued why Stringable by default may not be desirable and I only mention this for completeness of the conversation. I don't want to talk about that at all. Instead, I want to focus on the other side: construct from string by default (from/tryFrom()).
I also read Larry's post that has been shared throughout these discussions [5] and it seems fair and sound to use guardrails to discourage unintended use. This gets into the territory of "let me do what I want, I know what I'm doing" vs "let me limit what you can do because I built it and I know exactly what it was intended for", which I also want to try and steer away from debating, each camp has its merits.
Bilge said:
My question, then, is why can't basic enumerations have these semantics by default? Or, to state it more concretely, what would be the downside to having all "basic" enumerations actually being "backed" enumerations whose values implicitly mirror their names for the purposes of converting to/from strings? Would this not make basic enumeration more useful without any particular downsides?
While I'm searching for similar clarification, I want to pose the question differently. I feel like the answer to his question is in Larry's article about discouraging fancy strings. My question boils down purely to: can Basic Enums implement ::from() / tryFrom() methods?
**
Larry said:[...] In your case, you want to "upcast" a string to an enum. That means you're doing some sort of deserialization, presumably. In that case, a backed enum is what you want. A unit enum isn't serializable, by design.
Although this starts to thread into very pedantic territory, I think, in fact, a unit enum (assuming it means Basic enum) is in fact always serializable to a string:
$enum->value
. By design, the value is a string and it cannot have duplicate values in a single enum. It means it's extremely easy to define an Enum in PHP and at some point store it in a storage engine / database in the form of$enum->value
. Now if I want to get back my Enum at a later stage I need to implement exactly the same code that already exists in the PHP engine behindEnum::from()
. Maybe it's not serializable in the sense that it doesn't implement any true serialization mechanism, which I'm guessing a backed-enum probably does, but I'm trying to come from the very practical application of creating a Basic Enum at an HTTP context (which is the bread and butter of PHP) and then recovering said Enum in a background worker context without having to use PHPserialize()
function and store PHP-specific dialect in a database that is used by multiple teams and programming languages.I also take the point that it is easy to argue against all this: just put
: string
on your Enum and duplicate the names with values. Still, this doesn't address the "surprise effect" of "why this Enum doesn't have ::from() in it?". There doesn't exist any other value (string or otherwise) that could be used in ::from() or ::tryFrom() in a Basic Enum, which could make it less contentious. Also, in the spirit of NOT making Enums "Fancy strings", I'm looking for ways to reconstruct my Enum and all the behaviors available inside of it without even having to associate or think about a string. The only reason a string comes into the discussion is because $enum->value is one and is stored. I also checked and:enum Foo {
case 1;
case 2;
}is a parse error. [6].
Larry has also suggested that instead of making Basic Enum implement
::from()
and::tryFrom()
we could instead offer auto-populated String-Backed Enum values. That means transforming this:enum Foo: string { case Bar; case Baz; }
(which is a Fatal Error today) into this:
enum Foo: string {
case Bar = 'Bar';
case Baz = 'Baz';
}I also like this proposal. Although semantically, I think it would be better / have been better to have Basic Enum implementing the ::from() methods, one could argue that adding it now could be a breaking change since people could have Basic Enum already implementing their own custom ::from() method.
In conclusion, the "complex" (as opposed to primitive) object Enum is not a Fancy String and where I'm coming from I believe to be in sync with that mindset. However, PHP is highly used in Web context where we may need to use asynchronous processes to make API calls fast and schedule executions at a different context which often involves serialization. As such, being able to reconstruct a Basic Enum seems a rather fundamental need that we can still make it viable by making a small
: string
change to the Enum and opting-in into the from / tryFrom utilities. This should not affect Int-Backed Enums at all.Where casing is concerned (camelCase, PascalCase, snake-case, etc) [7], one can argue that if you want to have full control over casing, you should definitely take control over the values of your Enum. The beauty (in my mind) about making it default to the enum name is that it doesn't matter if I follow PER-CS rules or not, the truth is I don't need to think about strings at all because my Enum is not a Fancy string.
I didn't intend to write such a long email, but I'm really keen on hearing arguments against everything I shared to see if there are any flaws in my thought process.
[1] https://wiki.php.net/rfc/enumerations#basic_enumerations
[2] https://externals.io/message/118040
[3] https://externals.io/message/124991
[4] https://externals.io/message/123388
[5] https://peakd.com/hive-168588/@crell/on-the-use-of-enums
[6] https://3v4l.org/cDISV#v8.4.10
[7] https://externals.io/message/123388#123394--
Marco Deleu
Hey Marco,
I think this relies on whether the “natural value” of a unit enum is a string or not. It might be, or it might not be. I don’t think you have a compelling argument on why it is a string and that string is the name. Personally, I used int-backed enumerations far more so I would argue that the natural value is an integer, not a string. I’ve been thinking of a “quality-of-life RFC” for obvious enums, for some time now. Basically, backed enums without a value receive the “obvious” value. So a string backed enum gets the name, while an int gets the ordered number.
I think something like that makes more sense than trying to decide what the “natural value” of a unit enum is.
— Rob
I’ve been thinking of a “quality-of-life RFC” for obvious enums, for some time now. Basically, backed enums without a value receive the “obvious” value. So a string backed enum gets the name, while an int gets the ordered number.
I agree that this is a more fruitful direction. It allows users to say "the value of this case happens right now to match its name", but still keeps name and value as strictly separate concepts.
Importantly, it should be possible to mix default and non-default values on one enum, for refactoring, e.g. you might want to change an auto-valued "case ZendFramework;" to an explicit "case Laminas='ZendFramework';" or "case ZendFramework='Laminas';" without rewriting all the other cases on the enum.
It could even be a specific short-hand syntax on the case, like "case ZendFramework = auto" or "case ZendFramework = _".
Rowan Tommins
[IMSoP]
Hey Marco,
I think this relies on whether the “natural value” of a unit enum is a
string or not. It might be, or it might not be. I don’t think you have a
compelling argument on why it is a string and that string is the name.
Personally, I used int-backed enumerations far more so I would argue that
the natural value is an integer, not a string. I’ve been thinking of a
“quality-of-life RFC” for obvious enums, for some time now. Basically,
backed enums without a value receive the “obvious” value. So a string
backed enum gets the name, while an int gets the ordered number.I think something like that makes more sense than trying to decide what
the “natural value” of a unit enum is.— Rob
It makes sense that if a Unit Enum were to be given a value
we would have
a hard time defining what type such value should have. This strengthens my
desire for Larry's suggestion for String-backed Enums defaulting to their
names. That way, we sidestep the issue of the value type of a Unit Enum, we
avoid a potential breaking change by introducing a method that could
conflict with userland and it also makes it opt-in. By adding : string
to
a Unit Enum, we make the ->value
equivalent to the ->name
and receive
the from() implementation for it.
It's currently undeniable that a Unit Enum name is a string.
--
Marco Deleu
It's currently undeniable that a Unit Enum name is a string.
So is a class name, as accessed by $foo::class.
$foo->name on an enum case is really just the same kind of
pseudo-reflection, in my view: it's telling you information about the
source code, useful primarily for debugging.
If you want to associate a single string value with each enum case, use
a string-backed enum, and propose some short-hand syntax. Please leave
unit enums as opaque objects for those of us who see value in that.
--
Rowan Tommins
[IMSoP]
On Wed, Jul 9, 2025 at 7:23 PM Rowan Tommins [IMSoP] imsop.php@rwec.co.uk
wrote:
It's currently undeniable that a Unit Enum name is a string.
If you want to associate a single string value with each enum case, use
a string-backed enum, and propose some short-hand syntax. Please leave
unit enums as opaque objects for those of us who see value in that.
That's exactly what I'm doing :shrug:
--
Marco Deleu
On Wed, Jul 9, 2025 at 7:23 PM Rowan Tommins [IMSoP] imsop.php@rwec.co.uk
wrote:It's currently undeniable that a Unit Enum name is a string.
If you want to associate a single string value with each enum case, use
a string-backed enum, and propose some short-hand syntax. Please leave
unit enums as opaque objects for those of us who see value in that.That's exactly what I'm doing :shrug:
I apologise for the unnecessary tone of my previous email. I should know better than to post when tired.
Rowan Tommins
[IMSoP]