# [RFC] [Under Discussion] Auto-implement Stringable for string backed enums

- Thread: https://externals.io/message/118040
- Original archive: https://news-web.php.net/php.internals/118040
- Started by Nicolas Grekas on 2022-06-21T22:47:15+00:00
- Messages: 19
- RFC: Auto-implement Stringable for string backed enums (Withdrawn) — https://externals.io/rfc/auto-implement_stringable_for_string_backed_enums.md
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #118040 — Nicolas Grekas — 2022-06-21T22:47:15+00:00

URL: https://externals.io/message/118040#118040

Hi everyone!

I'd like to open a discussion on this RFC, to auto-implement Stringable for
string-backed enums:
https://wiki.php.net/rfc/auto-implement_stringable_for_string_backed_enums

I'm looking forward to your feedback,

Cheers,
Nicolas

---

## #118045 — Benjamin Eberlei — 2022-06-22T12:33:41+00:00

URL: https://externals.io/message/118040#118045 · In reply to: #118040

On Wed, Jun 22, 2022 at 12:47 AM Nicolas Grekas <
nicolas.grekas+php@gmail.com> wrote:

> Hi everyone!
>
> I'd like to open a discussion on this RFC, to auto-implement Stringable for
> string-backed enums:
> https://wiki.php.net/rfc/auto-implement_stringable_for_string_backed_enums
>
> I'm looking forward to your feedback,


Hi Nicolas, Hi Ilija,

I would prefer if this was an explicit opt-in to have a __toString on a
backed enum. Maybe a special trait for enums that has the implementation,
so that a custom __toString cannot be implemented or a new syntax "enum Foo
: Stringable".

My concern is that auto implementing __toString, will lead to decreasing
type safety of enums in weak typing mode, since they get auto-casted to
string when passed to a function accepting strings. This effectively adds
more type juggling cases.

The example in the RFC about attributes accepting strings or Enums can be
solved by union types on the side of the library developers, it doesn't
need to be magically implemented by the engine.

I don't consider "use strict mode" a good argument to avoid this problem,
because that has other downsides such as overcasting.

>
> Cheers,
> Nicolas

---

## #118046 — Derick Rethans — 2022-06-22T13:05:31+00:00

URL: https://externals.io/message/118040#118046 · In reply to: #118040

>Hi everyone!
>
>I'd like to open a discussion on this RFC, to auto-implement Stringable for
>string-backed enums:
>https://wiki.php.net/rfc/auto-implement_stringable_for_string_backed_enums
>
>I'm looking forward to your feedback,

I am not in favour of this RFC, especially because it is not opt-in. 

1. Enums were introduced to add extra type safety. Allowing them to degrade to simple strings reduces that type safety.
2. It is not implemented for all enum types, only for string backed ones. This change would now make different classes of enums, without them being differently inherited classes. 
3. The main use case seems to be to prevent having to type ->value, which would otherwise indicate reliably whether an enum is used as string argument. 

I won't be voting in favour of this RFC as it currently stands. 

cheers 
Derick

---

## #118054 — Nicolas Grekas — 2022-06-22T16:01:13+00:00

URL: https://externals.io/message/118040#118054 · In reply to: #118046

Hi Benjamin and Derick,

I'm replying to both of you because I see some things in common in your
comments.

>https://wiki.php.net/rfc/auto-implement_stringable_for_string_backed_enums


I would prefer if this was an explicit opt-in to have a __toString on a
> backed enum. Maybe a special trait for enums that has the implementation,
> so that a custom __toString cannot be implemented or a new syntax "enum Foo
> : Stringable".
>

We can make this opt-in by simply allowing user-land to implement
Stringable.
This is a different solution to the problem the RFC tries to solve and I
would also personally be fine with.
I would then *not* try to limit which implementation of it is allowed by
the engine. Instead, I would give all powers to user-land to decide on
their own what makes sense for their use case. As a general principle, I
believe that empowering user-land is always a win vs trying to "save them
from themselves", as if we knew better than them what they want to achieve,
and especially how.


> My concern is that auto implementing __toString, will lead to decreasing
> type safety of enums in weak typing mode, since they get auto-casted to
> string when passed to a function accepting strings. This effectively adds
> more type juggling cases.
>

I don't share this concern: if an API accepts a string and the engine can
provide a string, let it do so. There is nothing inherently dangerous in
doing so. But we don't need to agree on that if the proposal above makes
sense to everybody :)


> The example in the RFC about attributes accepting strings or Enums can be
> solved by union types on the side of the library developers, it doesn't
> need to be magically implemented by the engine.
>

I extensively explain in the RFC why this should not be on the side of lib
authors, but on the side of end-users. Please double check and let me know
your thoughts.


> I don't consider "use strict mode" a good argument to avoid this problem,
> because that has other downsides such as overcasting.
>

I'm 100% aligned with that.

2. It is not implemented for all enum types, only for string backed ones.
> This change would now make different classes of enums, without them being
> differently inherited classes.
>

This would also be solved by allowing user-land to implement Stringable on
*all* kind of enums. Would that make sense to you?


> 3. The main use case seems to be to prevent having to type ->value, which
> would otherwise indicate reliably whether an enum is used as string
> argument.
>

Yep, that's a "strict mode" approach and this RFC mostly applies to
non-strict mode.
There are also cases where using "->value" is just not possible. I mention
attributes in the RFC, but we also have a case in Symfony where defining
service definitions in yaml doesn't work with enums because there is no way
to express the "->value" part.

If that's the consensus, I'm fine updating the RFC to turn the vote into
whether "allowing user-land to implement Stringable on any kind of enums"
is desired or not.

Nicolas

---

## #118057 — Rowan Tommins — 2022-06-22T17:22:13+00:00

URL: https://externals.io/message/118040#118057 · In reply to: #118040

On Tue, 21 Jun 2022 at 23:47, Nicolas Grekas <nicolas.grekas+php@gmail.com>
wrote:

> Hi everyone!
>
> I'd like to open a discussion on this RFC, to auto-implement Stringable for
> string-backed enums:
> https://wiki.php.net/rfc/auto-implement_stringable_for_string_backed_enums
>


Hi Nicolas,

Like others, I'm lukewarm on this - but then I've always been lukewarm on
both Backed Enums and __toString

For me, the big value of an enum is as a value that is never equal to
anything but itself, so the idea of MyEnum::Foo == 'foo' evaluating to true
feels instinctively wrong to me.

Meanwhile on the string side, I feel like most objects have more than one
string representation, so "blessing" one makes little sense. Taking the
example of permissions given in the PR discussion, what if you need to pass
these same permissions to two different libraries, which have different
format requirements?

However, we DO have backed enums, and people clearly want to use them for
this scenario, so I'm tentatively OK with allowing them to do so, since I
can just use a non-backed enum to get my preferred "style".

Derick raises a good point about it not working for int-backed enums,
though. What if, rather than implementing __toString() as such, we
implemented internal cast handlers for either string or int, depending on
the backing value? (Other internal objects do have this ability, e.g. GMP
and SimpleXMLElement)

Regards,
-- 
Rowan Tommins
[IMSoP]

---

## #118058 — Larry Garfield — 2022-06-22T17:26:56+00:00

URL: https://externals.io/message/118040#118058 · In reply to: #118046

> On 21 June 2022 23:47:15 BST, Nicolas Grekas 
> <nicolas.grekas+php@gmail.com> wrote:
> > […]
>
> I am not in favour of this RFC, especially because it is not opt-in. 
>
> 1. Enums were introduced to add extra type safety. Allowing them to 
> degrade to simple strings reduces that type safety.
> 2. It is not implemented for all enum types, only for string backed 
> ones. This change would now make different classes of enums, without 
> them being differently inherited classes. 
> 3. The main use case seems to be to prevent having to type ->value, 
> which would otherwise indicate reliably whether an enum is used as 
> string argument. 
>
> I won't be voting in favour of this RFC as it currently stands. 
>
> cheers 
> Derick 

I am also still opposed to this proposal, and the more I think on it the more opposed I get.

Here's the basic problem.  There are two use cases (broadly speaking) where this would be applicable.  In one, a parameter is string typed today but there is a finite number of legal values, logically.  Think $order = ASC/DESC.  In the other, the parameter is string typed today and there is a technically infinite number of legal values, from the point of view of that function.  In a given use case there may be a finite set we want to use, but from the function's point of view, it's nominally infinite.  That's the "roles" example that Nicolas cites.

In case 1, I'd argue that the function should be switching to an Enum long term *and dropping the string*.  For that, a union type is the optimal solution.  Does that have BC implications for sub-classes?  Well, yes, but so does any type improvement.  This is a known problem space, with known solutions and migration strategies.  It's conceptually no different from migrating from "this takes a string" to "this takes an array of strings": Widen the type, have transitional code, retighten the type.  The time frame for that could be weeks, months, or years depending on the situation, but it's not a novel concept.

In case two, an enum is simply the wrong tool.  Every time I say this someone whines that I'm being elitist or judgemental or holier than thou or whatever, but... they're different types.  If someone proposed "I want to be able to pass a single-element array to a string parameter and have it automatically unwrap the array", it wouldn't be taken seriously.  That's what's being proposed here, though.

If an access control system works on roles as strings, then... it expects a string.  It doesn't expect "one of these and only these values, defined at code time."  The *application* may be written for that, but the access check function does not.  It works on strings.  Passing it a non-string *should* be an error, just as much as passing it an array should be.

The argument presented is that it's easier to type `AppRoles::Admin` than `"admin"`, because the former provides you with an error if you typo something.  That's a valid argument, but... not for using enums.  It's an argument for using constants.

class AppRoles
{
  public const Admin = 'admin';
  public const Editor = 'editor';
  public const User = 'user';
}

enum AppRoles: string
{
  case Admin = 'admin';
  case Editor = 'editor';
  case User = 'user';
}

It's basically the same work to setup, but one does exactly what it says: It provides syntax-checked shortcuts for strings, which is what the API wants.  Using an enum here instead of constants provides exactly zero additional value, because the limited-set-ness of the enum won't be checked in the first place.

Just because someone wants to use the claw-side of a hammer as a screwdriver doesn't mean we should design it to be a screwdriver.  They should just use a screwdriver.

And for those arguing that we shouldn't be "protecting users from themselves"... that's exactly what types are.  That's exactly what they're for.  The whole reason to have typed parameters is to stop people from doing things that make no sense.  That's the point.

So I am firmly against making it easier to (mis)use enums in a situation where constants are already the superior solution by every metric.  The only argument I see is making case 1, transitioning from a string to an enum for a genuinely limited-case, easier.  But in that case, the transition is going to have to happen eventually anyway, and that means the type is going to change at some point, and the same BC issue will appear, just at a different time.  Unless the intent is to then never change the type and keep the function incorrectly typed (from the POV that it's logically an enum, even though string typed was the best/correct type for years) forever, in which case... use a set of constants.  

--Larry Garfield

---

## #118060 — tim@bastelstu.be — 2022-06-22T17:52:46+00:00

URL: https://externals.io/message/118040#118060 · In reply to: #118058

Hi


> In case 1, I'd argue that the function should be switching to an Enum long term *and dropping the string*.  For that, a union type is the optimal solution.  Does that have BC implications for sub-classes?  Well, yes, but so does any type improvement.  This is a known problem space, with known solutions and migration strategies.  It's conceptually no different from migrating from "this takes a string" to "this takes an array of strings": Widen the type, have transitional code, retighten the type.  The time frame for that could be weeks, months, or years depending on the situation, but it's not a novel concept.

Exactly this. It might be painful for existing code, but I expect any 
newly written and any updated code to handle enums "natively" and so 
this is something that will solve itself over time.

On the other hand once the enum type safety is watered up by allowing 
implicit string conversions, there is no easy way to revert this.

Best regards
Tim Düsterhu

---

## #118063 — drealecs@gmail.com — 2022-06-22T19:31:22+00:00

URL: https://externals.io/message/118040#118063 · In reply to: #118058

On Wed, Jun 22, 2022 at 8:27 PM Larry Garfield <larry@garfieldtech.com>
wrote:

>
> So I am firmly against making it easier to (mis)use enums in a situation
> where constants are already the superior solution by every metric.  The
> only argument I see is making case 1, transitioning from a string to an
> enum for a genuinely limited-case, easier.  But in that case, the
> transition is going to have to happen eventually anyway, and that means the
> type is going to change at some point, and the same BC issue will appear,
> just at a different time.  Unless the intent is to then never change the
> type and keep the function incorrectly typed (from the POV that it's
> logically an enum, even though string typed was the best/correct type for
> years) forever, in which case... use a set of constants.
>
>

Hi!
I'm with you on what you mentioned here.

But also, I think the need I understood arises from another case that is
neither 1 or 2.
When you have two domains the value might need to be represented as a
backed enum in one side and as a string in the other.
As far as I understood, this is the case, with applications that are in one
domain wants to have a proper enum for let's say the app roles as the
possible roles are just a limited set.
That application is using another library to configure the ACL using those
roles and this is another domain that does not have a limited value on the
role representation, it's just a string.
Naturally, you should just transform the enum instance to the string and
that should be done using the value property. But this does not work for
configurations done through attribute parameters.
And I think this is the only problem we should fix and that's fixable by
https://wiki.php.net/rfc/fetch_property_in_const_expressions

What you mentioned about developers using enum in the wrong way is
completely true and it's been a long effort for me in explaining this.
I was hoping it would be diminished somehow by increased popularity of
enums, now that they are supported by everyone. But the usages are also
increasing.

Regards,
Alex

---

## #118066 — Rowan Tommins — 2022-06-22T21:43:46+00:00

URL: https://externals.io/message/118040#118066 · In reply to: #118058

> The argument presented is that it's easier to type `AppRoles::Admin` than `"admin"`, because the former provides you with an error if you typo something.  That's a valid argument, but... not for using enums.  It's an argument for using constants.


I wonder if the reality is that neither enums (as implemented) nor 
constants are the right solution to to this.

What users want is some way to say "this value should be a string, but 
in this context it should be one of this list of strings"; or, 
sometimes, "is this string one of this list of strings?" Constants can't 
do that - they give you a way of referring to the possible values, but 
no tools for enforcing or testing against them. Backed enums can kinda 
sorta do that with a bit of effort, using ->value and ::tryFrom, but 
they're not really built for it.


A better fit would be some kind of "domain type", which would allow you 
to write something vaguely like this:

domain SymfonyPermission: string;
domain AcmePermission: string { 'admin' | 'user' | 'bot' };

assert( in_domain('admin', SymfonyPermission) );
assert( in_domain('admin', AcmePermission) );

assert( in_domain('random stranger', SymfonyPermission) );
assert( ! in_domain('random stranger', AcmePermission) );


Domains can also be considered sets, which you could compare directly, 
and maybe even calculate intersections, unions, etc:

assert( is_subset(AcmePermission, SymfonyPermission) );


The actual values would be ordinary strings, and type constraints would 
just be checking the value passed against the domain:

function doSymfonyThing(SymfonyPermission $permission) {
     echo $permission; // no coercion needed, $permission is a string
}

function doAcmeThing(AcmePermission $permission) {
     doSymfonyThing($permission);
}

doAcmeThing('admin'); // no special syntax needed to "construct" or 
"look up" an instance


Crucially, this solves the described problem of a library accepting an 
infinite (or perhaps just very wide) set of values, and a consuming app 
wanting to constrain that set within its own code.

It's one disadvantage is the typo-proofing and look up availability that 
constants give, but you could always combine the two.


Regards,

-- 
Rowan Tommins
[IMSoP]

---

## #118068 — Benjamin Eberlei — 2022-06-23T08:07:17+00:00

URL: https://externals.io/message/118040#118068 · In reply to: #118054

On Wed, Jun 22, 2022 at 6:01 PM Nicolas Grekas <nicolas.grekas+php@gmail.com>
wrote:

> Hi Benjamin and Derick,
>
> I'm replying to both of you because I see some things in common in your
> comments.
>
> > […]
>
>
> I would prefer if this was an explicit opt-in to have a __toString on a
> > […]
>
> We can make this opt-in by simply allowing user-land to implement
> Stringable.
> This is a different solution to the problem the RFC tries to solve and I
> would also personally be fine with.
> I would then *not* try to limit which implementation of it is allowed by
> the engine. Instead, I would give all powers to user-land to decide on
> their own what makes sense for their use case. As a general principle, I
> believe that empowering user-land is always a win vs trying to "save them
> from themselves", as if we knew better than them what they want to achieve,
> and especially how.
>
>
> > […]
>
> I don't share this concern: if an API accepts a string and the engine can
> provide a string, let it do so. There is nothing inherently dangerous in
> doing so. But we don't need to agree on that if the proposal above makes
> sense to everybody :)
>
>
> > […]
>
> I extensively explain in the RFC why this should not be on the side of lib
> authors, but on the side of end-users. Please double check and let me know
> your thoughts.
>

I don't fully agree with your argument in the RFC.

You mention as an exmaple that users want to pass PossibleRoles enum values
into the IsGranted attribute. But the way Symfony works as you know, you
can pass arbitrary role names here from the library POV, and applications
can add their own. So naturally the API is to pass a string. So
"PossibleRoles" can only be a user provided enum, it can never be a Symfony
type.

I don't see how PHP should provide a workaround for Symfony and its users
wanting to allow users to use an arbitrary enum as input to a function.

In this example, in Symfony code you only expect "any" BackedEnum and
cannot validate that it is a value of PossibleRoles enum. As such Rowan
suggestion to use constants is the way Symfony should recommend their users.


>
> > […]
>
> I'm 100% aligned with that.
>
> 2. It is not implemented for all enum types, only for string backed ones.
> > […]
>
> This would also be solved by allowing user-land to implement Stringable on
> *all* kind of enums. Would that make sense to you?
>
>
> > […]
>
> Yep, that's a "strict mode" approach and this RFC mostly applies to
> non-strict mode.
> There are also cases where using "->value" is just not possible. I mention
> attributes in the RFC, but we also have a case in Symfony where defining
> service definitions in yaml doesn't work with enums because there is no way
> to express the "->value" part.
>
> If that's the consensus, I'm fine updating the RFC to turn the vote into
> whether "allowing user-land to implement Stringable on any kind of enums"
> is desired or not.
>
> Nicolas

---

## #118074 — Larry Garfield — 2022-06-23T15:32:22+00:00

URL: https://externals.io/message/118040#118074 · In reply to: #118063

> On Wed, Jun 22, 2022 at 8:27 PM Larry Garfield <larry@garfieldtech.com>
> wrote:
>
> > […]
>
> Hi!
> I'm with you on what you mentioned here.
>
> But also, I think the need I understood arises from another case that is
> neither 1 or 2.
> When you have two domains the value might need to be represented as a
> backed enum in one side and as a string in the other.
> As far as I understood, this is the case, with applications that are in one
> domain wants to have a proper enum for let's say the app roles as the
> possible roles are just a limited set.
> That application is using another library to configure the ACL using those
> roles and this is another domain that does not have a limited value on the
> role representation, it's just a string.
> Naturally, you should just transform the enum instance to the string and
> that should be done using the value property. But this does not work for
> configurations done through attribute parameters.
> And I think this is the only problem we should fix and that's fixable by
> https://wiki.php.net/rfc/fetch_property_in_const_expressions

Yes, I'm planing to vote +1 on that RFC.  Although in that case, the same logic for why to use a constant instead still applies.

> What you mentioned about developers using enum in the wrong way is
> completely true and it's been a long effort for me in explaining this.
> I was hoping it would be diminished somehow by increased popularity of
> enums, now that they are supported by everyone. But the usages are also
> increasing.
>
> Regards,
> Alex

I may need to publish a blog post on this issue specifically that Symfony and others can point to when people keep asking them to do the wrong thing.  I fully understand that major frameworks and libraries (Symfony et al) are getting the pushback of people trying to do the wrong thing, but the solution should be better educational efforts so people stop trying to do the wrong thing, not making it easier to do the wrong thing.

--Larry Garfield

---

## #118075 — Larry Garfield — 2022-06-23T15:35:49+00:00

URL: https://externals.io/message/118040#118075 · In reply to: #118066

> 
> > […]
>
>
> I wonder if the reality is that neither enums (as implemented) nor 
> constants are the right solution to to this.
>
> What users want is some way to say "this value should be a string, but 
> in this context it should be one of this list of strings"; or, 
> sometimes, "is this string one of this list of strings?" Constants can't 
> do that - they give you a way of referring to the possible values, but 
> no tools for enforcing or testing against them. Backed enums can kinda 
> sorta do that with a bit of effort, using ->value and ::tryFrom, but 
> they're not really built for it.
>
>
> A better fit would be some kind of "domain type", which would allow you 
> to write something vaguely like this:
>
> domain SymfonyPermission: string;
> domain AcmePermission: string { 'admin' | 'user' | 'bot' };
>
> assert( in_domain('admin', SymfonyPermission) );
> assert( in_domain('admin', AcmePermission) );
>
> assert( in_domain('random stranger', SymfonyPermission) );
> assert( ! in_domain('random stranger', AcmePermission) );
>
>
> Domains can also be considered sets, which you could compare directly, 
> and maybe even calculate intersections, unions, etc:
>
> assert( is_subset(AcmePermission, SymfonyPermission) );
>
>
> The actual values would be ordinary strings, and type constraints would 
> just be checking the value passed against the domain:
>
> function doSymfonyThing(SymfonyPermission $permission) {
>      echo $permission; // no coercion needed, $permission is a string
> }
>
> function doAcmeThing(AcmePermission $permission) {
>      doSymfonyThing($permission);
> }
>
> doAcmeThing('admin'); // no special syntax needed to "construct" or 
> "look up" an instance
>
>
> Crucially, this solves the described problem of a library accepting an 
> infinite (or perhaps just very wide) set of values, and a consuming app 
> wanting to constrain that set within its own code.
>
> It's one disadvantage is the typo-proofing and look up availability that 
> constants give, but you could always combine the two.

Interesting concept.  I'm not sure if I like it yet, but it's interesting. :-)  It somehow feels related to Go's type aliasing, but I'm not sure if that's a fair comparison.

Is there a type-theoretic basis we could look at for that?  It seems like the sort of thing some mathematician has likely thought through for funsies before.

--Larry Garfield

---

## #118076 — Guilliam Xavier — 2022-06-23T15:50:52+00:00

URL: https://externals.io/message/118040#118076 · In reply to: #118054

Hi Nicolas, thanks for the RFC,

> There are also cases where using "->value" is just not possible. I mention
> attributes in the RFC,

which also mentions
https://wiki.php.net/rfc/fetch_property_in_const_expressions (but with
"For people that use non-strict mode, this extra “->value” is
boilerplate that they'd better remove")

> but we also have a case in Symfony where defining
> service definitions in yaml doesn't work with enums because there is no way
> to express the "->value" part.

Symfony YAML has a `!php/const X` feature, which also works when X is
an Enum::CASE; how about a `!php/enum_value` feature?

Otherwise, I also like Rowan's suggestion of implementing "internal
cast handlers", so that non-strict users could call e.g.
`takes_int(IntEnum::CASE)` as well as
`takes_string(StringEnum::CASE)`; but what about
`takes_string(IntEnum::CASE)`, and
`takes_Stringable(StringEnum::CASE)`?

In any case, several people requested that it should require to be
*opted-in* explicitly; but then [for solutions other than "allowing
user-land to implement Stringable"] we probably also need a way to
test whether a BackedEnum [instance] is "coercible"?

Regards,

-- 
Guilliam Xavier

---

## #118089 — Nicolas Grekas — 2022-06-24T16:11:55+00:00

URL: https://externals.io/message/118040#118089 · In reply to: #118066

> domain SymfonyPermission: string;
> domain AcmePermission: string { 'admin' | 'user' | 'bot' };
> [...]
> Domains can also be considered sets, which you could compare directly,
> and maybe even calculate intersections, unions, etc:
>
> The actual values would be ordinary strings, and type constraints would
> just be checking the value passed against the domain:
>
> Crucially, this solves the described problem of a library accepting an
> infinite (or perhaps just very wide) set of values, and a consuming app
> wanting to constrain that set within its own code.
>
> It's one disadvantage is the typo-proofing and look up availability that
> constants give, but you could always combine the two.
>

Thanks for this idea Rowan, that's really interesting.
I would go one step further and require naming the values in the set.
Borrowing from the syntax of enums, we could have:

set AcmePermission: string {
  case ADMIN = 'admin';
  case USER = 'user';
  case BOT = 'bot';
}

Then AcmePermission::ADMIN would === 'admin' and, as you said,
AcmePermission could be used as a type on functions:

function (AcmePermission $perm): AcmePermission
{
    $perm instanceof AcmePermission; // true

    return $perm;
}

In order to make this work, we would need to be able to autoload the type
AcmePermission.This could be done by the type-checking logic: when checking
$var against AcmePermission and the type is undefined, if $var is an int or
a string, call the autoloader for 'AcmePermission'.

There are other things to consider, like possible changes to reflection,
whether such a set also declares a class like enums do, etc.But overall, I
really like it and I agree with you: this would provide a really good
solution for the problem ppl try to use enums for.

I don't know how we could move forward on that idea. By starting another
thread?

Nicolas

---

## #118091 — Nicolas Grekas — 2022-06-24T16:37:52+00:00

URL: https://externals.io/message/118040#118091 · In reply to: #118076

Hi Guilliam,


> > […]
> mention
> > […]
>
> which also mentions
> https://wiki.php.net/rfc/fetch_property_in_const_expressions (but with
> "For people that use non-strict mode, this extra “->value” is
> boilerplate that they'd better remove")
>

Absolutely.

Providing a nice and expressive syntax is critical. We added e.g. short
closures, constructor property promotion, etc. all because we care to not
write ugly code.

With the "Fetch properties of enums in const expressions" RFC, we soon
might be able to reference values in enums with this syntax:
#[MyAttr(MyEnum::Case->value)]

I understand why we're considering this and this might be only me, but I
find this ugly.
The same happens when coding: take_string($enum->value)

This ugliness is part of the problem I'd like to solve. Rowan's proposals
about sets could solve this in a very nice way , but we're not there yet. I
can wait though.



> Symfony YAML has a `!php/const X` feature, which also works when X is
> an Enum::CASE; how about a `!php/enum_value` feature?
>

I submitted something similar today at
https://github.com/symfony/symfony/pull/46771

Otherwise, I also like Rowan's suggestion of implementing "internal
> cast handlers", so that non-strict users could call e.g.
>

I had a look at gmp for example: cast handlers don't work when calling a
function.
They do work when explicit casting and when doing loose comparisons, but
they don't when calling functions (or returning from one.) I don't know the
underlying reason for that behavior but it looks consistent. Unfortunately
that idea looks like a dead end.

In any case, several people requested that it should require to be
> *opted-in* explicitly; but then [for solutions other than "allowing
> user-land to implement Stringable"] we probably also need a way to
> test whether a BackedEnum [instance] is "coercible"?
>

I don't have a solution that meets all the requirements that ppl expressed
so far.
If I could get back in time, I would enable that missing casting rule I
described for gmp in non-strict mode, and I would implement it on backed
enums from the start.

I'm now considering withdrawing the RFC because I don't see a way forward
that could be consensual enough.
If other ppl share my concerns and have a proposal, please let us know.

Nicolas

---

## #118095 — Dan Ackroyd — 2022-06-25T14:44:03+00:00

URL: https://externals.io/message/118040#118095 · In reply to: #118091

Hi Nicolas,

On Fri, 24 Jun 2022 at 17:38, Nicolas Grekas
<nicolas.grekas+php@gmail.com> wrote:
>
> I'm now considering withdrawing the RFC because I don't see a way forward
> that could be consensual enough.

Just in general, I think changes to the type system are always going
to take longer than a few weeks to discuss. There are always going to
be subtle implications that need to be thought through thorougly.

Also, the argument for a change to the type system like this should be
based on why it's the right thing to do for new code that is being
written. Although obviously everyone who has a large code base wants
to be able to upgrade to the latest and greatest PHP to get the new
features at the lowest cost in changing code, imo it's more important
long term to get the language right, rather than putting too much
emphasis on lowering the cost of upgrading.

> > […]
>
> I don't share this concern: if an API accepts a string and the engine can
> provide a string,

function/method calls are not the only place were type comparisons are done.

My understanding is that if this RFC is passed, then the situation would be:

Foo::Bar == 'Bar'; // true
Baz::Bar == 'Bar'; // true
Baz::Bar == Foo::Bar; // false

Which is not obviously the correct thing.

There is also the issue that callbacks called internally by the engine
are always in weak/coercive mode. That means that changes to
weak/coercive can leak through to code that wants to always be in
strict mode.

> It would be great if we could find ways to make it easier for general-purpose
> libraries to support enums without cluttering libraries with if/else blocks
> and without changing existing interfaces in a backwards-incompatible way.

There's two parts going on here. First, yes, enums could probably be
easier to work with, and we should probably be looking how to do that.

Second is an argument about how easy it should be to retrofit new
features to existing code. Although I can see why people would want
that, it's a lot harder to justify it.

> As experienced on the Symfony repository, this problem is especially visible
> at the boundary of libraries: when some component accepts a string as input,
> ppl want them to also accept backed-enums. This usually means that they
> propose widening the accepted types of some method to make them work
> seamslessly with enums.

Doing that appears to be a violation of the "open for extension,
closed for modification" principle.

If you want to change the type signature, introducing a new function
is almost certainly the 'right' thing to do, even if that means more
work for people with large existing code-bases. e.g.

Change:
class Foo {
   function bar(string $quux) {}
}

To this:
class Foo {
   function bar(string $quux) {}
   function barEx(string|SomeEnum $quux) {}
}

And eventually deprecate the original bar method.

> but we also have a case in Symfony where defining service
> definitions in yaml doesn't work with enums because there
> is no way to express the "->value" part.

I very strongly think the limitations of what yaml config file choices
were made in a downstream project should not influence the design of a
the type system of an upstream project. I'm pretty sure that a
successful RFC isn't going to need to mention that problem.

> If other ppl share my concerns and have a proposal, please let us know.

It might be hard to have a one size fits all rule, hard-coded in the
engine as people genuinely have different views of what enums are.

One idea that I think may have been mentioned before (and if I recall,
shot down pretty hard) would be to allow people to register cast
callbacks similar to the code below. That would allow people who view
enums as special constants to cast away, and those view enums as
types, to not cast.

This currently would be problematic for the same reason that PHP ini
settings are problematic; without a module or package system, any
settings that affect how the engine behave affect all code that is
run, rather than being limited to just the library that wants to
enable that setting. Which is a problem that keeps rearing it's head.

Maybe someone sponsoring some blue-sky research on how feasible a
module/package system could be, would make addressing problems similar
to the one here be easier to work on.

cheers
Dan
Ack


enum Suit: string {
    case Hearts = 'H';
    case Spades = 'S';
}

function foo(string $bar) {
    var_dump($bar);
}

function cast_backed_enum_to_string(BackedEnum $enum): string {
    return $enum->value;
}

register_cast_callback(
    BackedEnum::class, // source type
    'string',           // target type
    cast_backed_enum_to_string(...)
);

foo(Suit::Hearts);
// Engine sees we have a BackedEnum and that foo wants a
// string, so calls cast_backed_enum_to_string to do the conversion

---

## #118104 — Guilliam Xavier — 2022-06-27T12:03:54+00:00

URL: https://externals.io/message/118040#118104 · In reply to: #118091

> > […]
>
> I submitted something similar today at https://github.com/symfony/symfony/pull/46771

And I see that it has been merged ;)

> > […]
>
> I had a look at gmp for example: cast handlers don't work when calling a function.
> They do work when explicit casting and when doing loose comparisons, but they don't when calling functions

Well it works for `string` at least (even though `GMP` does *not*
implement `__toString`), e.g. https://3v4l.org/cRnnW
The TypeError for `int` may be related to the fact that e.g. `+$gmp`
(or `0 + $gmp`) gives back a GMP (not an int like `(int)$gmp`)?
(But indeed I don't know much about that... nor if it could be made opt-in)

> Rowan's proposals about sets could solve this in a very nice way

Reminded me of e.g.
https://stackoverflow.com/questions/6422380/does-any-programming-language-support-defining-constraints-on-primitive-data-types
(mainly integer ranges, but the concept of "domains" looks similar)

Regards,

--
Guilliam Xavier

---

## #118109 — Larry Garfield — 2022-06-27T16:54:51+00:00

URL: https://externals.io/message/118040#118109 · In reply to: #118058

> So I am firmly against making it easier to (mis)use enums in a 
> situation where constants are already the superior solution by every 
> metric.  The only argument I see is making case 1, transitioning from a 
> string to an enum for a genuinely limited-case, easier.  But in that 
> case, the transition is going to have to happen eventually anyway, and 
> that means the type is going to change at some point, and the same BC 
> issue will appear, just at a different time.  Unless the intent is to 
> then never change the type and keep the function incorrectly typed 
> (from the POV that it's logically an enum, even though string typed was 
> the best/correct type for years) forever, in which case... use a set of 
> constants.  
>
> --Larry Garfield

As promised, a blog post on the topic that folks are welcome to link to when telling people no: 

https://peakd.com/hive-168588/@crell/on-the-use-of-enums

--Larry Garfield

---

## #118110 — Rowan Tommins — 2022-06-27T18:01:49+00:00

URL: https://externals.io/message/118040#118110 · In reply to: #118104

> Reminded me of e.g.
> https://stackoverflow.com/questions/6422380/does-any-programming-language-support-defining-constraints-on-primitive-data-types
> (mainly integer ranges, but the concept of "domains" looks similar)


Yes, I'm vaguely familiar with the facilities provided by Pascal (via 
Delphi) and SQL (via Postgres).

Looking at the Postgres docs, it has both "enumerated" and "domain" 
types, with exactly the distinction that's relevant here.


Enums are type safe:

 > Each enumerated data type is separate and cannot be compared with 
other enumerated types

https://www.postgresql.org/docs/current/datatype-enum.html

They can be explicitly cast to "text" (i.e. string), but will not be 
cast implicitly when passed to a function or operator, so it ends up 
equivalent to our case of calling ->value, but without the flexibility 
of distinguishing the "case name" and "backing value".


Whereas domains are sub-types of some other type:

 > When an operator or function of the underlying type is applied to a 
domain value, the domain is automatically down-cast to the underlying type.

https://www.postgresql.org/docs/current/domains.html

This seems to be what people are asking for here: the values are 
constrained when the domain is explicitly mentioned, but freely 
interchangeable with the underlying type.


Here's an online demo showing the difference: 
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=88639144aec58ab7cf7e34a0c103aa51


Regards,

-- 
Rowan Tommins
[IMSoP]
