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

Reply via email to