Re: ::from() and tryFrom() on Basic Enums
| From: | Rob Landers | Date: | Wed, 09 Jul 2025 16:12:52 +0000 |
| Subject: | Re: ::from() and tryFrom() on Basic Enums | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-127975@lists.php.net to get a copy of this message | ||
On Wed, Jul 9, 2025, at 15:29, Deleu wrote:
> 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
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