php / php-src

The PHP Interpreter
https://www.php.net
Other
37.87k stars 7.72k forks source link

Enum case as Array key? #9208

Open geraldofebrian opened 2 years ago

geraldofebrian commented 2 years ago

Description

Before enum exist, i used constant to define the array key. Now i want to use enum. But i got error when i declare the enum as array key. so i do the workaround like this:

if (!function_exists('_api')) {
    /**
     * use enum as Array Key.
     *
     * @param array $values
     * @param \App\Enums\Api\ResponseKey $key
     * @param mixed $data
     * @return array
     */
    function _api(?array &$values, ResponseKey $key, $data)
    {
        if (! is_array($values)) $values = [];

        $values[$key->value] = $data;

        return $values;
    }
}

Then call it like _api($data, ResponseKey::message, "success");

Can this workaround can be implemented as feature for future PHP?

iluuu1994 commented 2 years ago

There's an RFC for this: https://wiki.php.net/rfc/object_keys_in_arrays AFAIK the author has no interest in pursuing this RFC any further but somebody else might do so in the future. Another option you have for now is using SplObjectStorage or WeakMap instead of arrays.

mvorisek commented 2 years ago

On of the major problem with the RFC objects as keys is https://github.com/php/php-src/pull/6588#issuecomment-780545671. Given enums are unique by definition, this feature request makes sense even if objects as keys in general are not supported.

daftspunk commented 1 year ago

A nice casting interface is probably all that is needed here.

// Casts to scalar
$array[StatusEnum::DRAFT] 

// Casts back to enum
(StatusEnum) 'draft'

The same can be applied to all objects, like what we find in C#.

// Cast array to a custom object
$collection = (MyCollection) $array

With some __method to handle incoming and outgoing logic.

// In
public static function __castStatic(mixed $from): mixed
{
    return new static($from);
}

// Out
public function __toCasted(): mixed
{
    return $this->toArray();
}
SuitespaceDev commented 1 year ago

Honestly; I cannot fathom why

$team[ role::lead ] = $some_person;

does not automatically assume role::lead->value.

Traditionally enums are used to get associative benefits with indexed arrays, as well as providing bitwise combinations in function arguments and working as a constraint system.

There are three things PHP is constantly lacking and constantly avoiding taking on

Any of these three singularly solve a large swathe of use cases. There are so many concepts in other languages that would fit great in PHP if only one of these existed. Any one of these makes the other 2 much easier to implement as well.

Once again this is true for efficient enum usage. As presently implemented, enums are practically fighting their own existence. (ie, I can target ->name and a string backed value separately, but not actually use enums as typesafe labels on values)

All this said, if the interpreter just assumes ->value instead of erroring, like any reasonable enum implementation should -- as enums have the job of being a countable label, then you could indeed meet the bare minimum of needs for enums across a decent number of use cases.

hguenot commented 1 year ago

SplObjectStorage (and ArrayAccess) cannot be used with array_keyexists (and maybe some other array*** functions). I think it would be very usefull and easier to use Enum as array key. Also it will reduce the impact of moving from const class attributes to enum...

ahajzik-crido commented 1 year ago

I'm also very interested in enums as array keys. It's something obvious in other mature programming languages. I miss it as I could use it before. I thought it would be added in PHP 8.3, but I don't see such proposal. Please consider this as an important request. Enums are not just objects, they are UNIQUE cases and it is natural to use them as index (not their internal values).

oplanre commented 1 year ago

For anyone still stuck on this, check out the php-ds extension which provides some nice data structures to help with this

TheApproach commented 1 year ago

Noted data-structures in php-ds do not appear to have native enums or cases as array keys

Is there some update which completed this?

oplanre commented 1 year ago

Yeah, most of the DS can have objects as keys(which include enums)

oplanre commented 1 year ago

Check out this post by the author of the plugin

hguenot commented 1 year ago

Changing every usage of native arrays by SPL or DS structure on a live and huge project will be a very hard and risky job... It seems obvious to use enum values as native array keys. They are basically replacement for class that have only constants (ie. status class)

oplanre commented 1 year ago

I mean obviously but its not like enums work as array keys by default. Meaning this is just something to keep in mind for when you are refactoring or working on a new project. If you are going to be using more than a few spl structures then it would be more efficient to get the DS package? Or am i missing something? Swapping out a class with only constants for an enum should work even in an array.

hguenot commented 1 year ago

I don't understand the need to use anything else than native array... The enum interest is to get properly typed "constant" value instead of int or string, and add some other methods. SPL or DS (I suppose) cannot be used with native array functions. The PHP preprocessor could simply replace the enum value by ->value if the enum class is used as array keys.

SuitespaceDev commented 1 year ago

I don't understand the need to use anything else than native array... The enum interest is to get properly typed "constant" value instead of int or string, and add some other methods. SPL or DS (I suppose) cannot be used with native array functions. The PHP preprocessor could simply replace the enum value by ->value if the enum class is used as array keys.

Yea agree. While it is true that using php-ds could solve some use cases due to object keys, this issue boils down to simply turning an error into a feature that makes the language work as expected.

I'd also point out that using objects as keys is computationally different than aliasing integers as keys. There's also all sorts of existing code using class constants which now would require type checking to disambiguate between enum and int/string being provided to a function, purely in order to call ->value or not

image

image

image

MorganLOCode commented 1 year ago

The PHP preprocessor could simply replace the enum value by ->value if the enum class is used as array keys.

Just to correct this, it should use ->name because the value is only available on backed Enums - a distinction that shouldn't make a difference if the whole idea is "enums as array keys".

It would also involve a bit more work than just preprocessing, unless the idea is that working with the array keys means working with the Enum instances' names, rather than the instances themselves. (Which itself introduces further ambiguity: just as $a[0] is the same element as $a['0'], so $a[Suit::Hearts] would be the same element as $a['Hearts'] and $a[Game::Hearts].

TheApproach commented 1 year ago

So far we are all specifically addressing the use case where an enum should be treated as ->value with full intention. The current implementation is an anti-feature without that behavior. That value is only available with backed enums gets to the heart of the issue really.

->name would indeed require more work but this is not the intent of the issue or the desired behavior.

An instance is set to a value, so using an instance will not produce ambiguity. Using an instance bare in all contexts this would apply now produces an error, so no overlap to concern with. Aliasing such instances to their value solves the problem and makes PHP compliant with all other major C-style languages, enabling the porting of various projects.

Unbacked enums should behave exactly as int-backed enums. The only difference would be that you can't define the value of unbacked enums as cases should be assigned an unsigned int value according to their order in the enum, starting at 0.

This is what enums do in practically all languages, and really because enum is short for "enumeration" the default action should indeed be to enumerate the labels/cases.

MorganLOCode commented 1 year ago

->value would still have the problem of ambiguity unless there were measures in place on the array to prevent instances from different enums being used as keys.

The problem with looking at "major C-style languages" is that they themselves have poor support for enums, treating them as collections of integer constants instead of a domain type in their own right that - at best - have a defined order. Plenty more on this subject is available in the original Enumeration RFC, including a link to a language survey made on how enums are done in other languages.

"Backed" enums were designed with the intent that the backing value is for the purpose of de/serialising enums in and out of PHP (see the first paragraph of the manual's page on Backed enumerations). Not for identifying enum instances within PHP itself.

If Enums were to be used as array keys (which I agree would be nice as a less verbose alternative to SPLObjectStorage or closures over match expressions), they would have to be used as enums in their own right, with type safety and all; not a kludged solution for the sake of avoiding having to type ->value while transitioning/porting.

And this is why the proposal should go to the ML and an RFC.

TheApproach commented 1 year ago

Enums in their own right are collections of integers. To enumerate is to count a collection.

To provide enums focused more strongly on backed enums and typing, in a language which is still only loosely typed, is not only strange but as I say an anti-feature. Further, because there are backed enums backed by both int and string, treating non-backed enums with this behavior allows for every use case.

You may precisely have type checked backed enums with integers or string, and you can use enums in the standard way developers are accustomed to which more closely aligns to the meaning "enum." Enums were added to PHP due to demand, yet if they do not serve this purpose first and foremost, we really still do not have enums at all. Enum presently is just the keyword to some wild concept of labeling.

While it would be desirable for enums to have type safety, that is certainly not a reason to avoid simple changes which will iterate us toward such usage. Additionally there are use cases for pure enums without such checks. Since both cases may be served without creating breaking changes or overhauling the entire enum concept, this is ideal. Further optimizations can then be more easily RFC'd and iterated.

MorganLOCode commented 1 year ago

The focus is on pure enums; backed enums, as I already pointed out, exist for the sake of round-tripping values outside PHP.

"To enumerate: to count off or name one by one; list." https://www.wordnik.com/words/enumerate "To specify one after another" https://www.merriam-webster.com/dictionary/enumerate "To name thing separately, one by one:" https://dictionary.cambridge.org/dictionary/english/enumerate

Backing by integers isn't a requirement for an enumeration (but languages such as C chose to represent enumerations by integers). The defining feature of an enumerated type is that it is defined by explicitly listing all of the possible values. https://foldoc.org/enum https://foldoc.org/enumerated+type Because all possible values are listed, you can immediately count (i.e. enumerate) them. That doesn't mean the things being counted are necessarily integers. You can count playing card suits but playing card suits are a different type than integers.

PHP already has two enumerated types, null and bool (three, if you count never as the type with no values). These are not collections of integers, they are the sets {null}, {false,true} (and {}). Defined by explicitly listing their members, and distinct types in their own right.

The whole point of enums is type safety. If you don't want type safety then all you're really asking for are collections of constants - a way to give values of some other type (string or integer) some kind of label in your source code.

For example: let's use (backed) enums in arrays by using their backing values as keys. So those keys are actually just integers and strings, not enums. No more type safety. No guarantee that the array isn't going to go off and get other elements with other keys that are not in your enum's backing values (or, worse, are in your enum's backing values but came from somewhere else, maybe a different enum's backing values happen to overlap), and now you're right back with the work checking the validity of keys you were trying to avoid by using enums in the first place.

SuitespaceDev commented 1 year ago

Enumeration as a concept and a logic structure long predates programming. Unsigned integers are the natural numbers, and the natural numbers encapsulate counting. This is most definitely their true identity.

Making enums associative with strings or other values is a neat trick but should not be conflated with the primary purpose or take priority. Same for type-safety; no one is suggesting solutions that would rid us of the existing type-safety, but it is not 'the whole point of enums'. The point of enums are to enumerate. Hence the name.

Yes you can think of some wild way to implement them which differs from every other suggestion so far, and in that version you could have problems. Don't do that. Easy.

You would still have the ability to do everything you can do now; plus use enums as keys. Really there's no need to restrict this to unbacked enums even. Right now you can use class constants and enum->value in an array key, there is no type safety for array keys. You cannot use an enum case instance at all without appending ->somthing in that context so there is no conflict either. Every context that provides type-safety at present would still do so.

Once implemented people can think about making it even more type-safe, great. That's no reason for PHP developers to be piling up more code relying on class constants instead of enum across the entire PHP ecosystem. Because that is what many of us are forced to do thanks to the current implementation.

That's absurd. You don't need to start an emotional flamewar over the perfect enum implementation rather than gradually getting it in place. Your crusade on C-style enums is not the topic and slowing progress because of it would be making your own goals more difficult to implement in the long run.

Right now some of us have to use constants which are not typesafe. Especially if the goal, for PHP, is to have typesafe enums, we should immediately patch this as folks have recommended in this thread. That way when typesafety does come into effect for keys, they will already be using enums in their project and can take advantage of this.

Conversely if we do not, enums will be adopted more slowly or potentially completely neglected in favor of the old class constant model. You can't change how every project's habits play out, based on your opinion of what enums are, but we can support future-proofing the use patterns right now.

ahajzik-crido commented 1 year ago

Having thinked about what had been written above I find the concept of using basic enums as array keys more appropriate. It shouldn't metter if they are backed or not.

Backed enums values does not have a global uniquity. They are good for serialization and representation outside PHP.

While we don't have a typed arrays in PHP that would restrict cases to a collection of one enum to be used as a keys in a particular array, support for enums cases in general as array keys is definitely better than support of case values or even case names performed by preprocessor.

MorganLOCode commented 1 year ago

For the past three hundred years one of the meanings of "enumeration" is "a list". And that is what the word means in programming: a list of possible values. Yes, the point of enums is to enumerate, but just because you can list the number of days in a week (Friday, Monday, Saturday, Sunday, Thursday, Tuesday, and Wednesday) and then count them (seven), doesn't mean that the days of the week are themselves numbers any more so than (Greed, Lust, Gluttony, Envy, Wrath, Sloth, Pride). Those are three different types. Invisibly mashing one type into another has been the source of some of PHP's most notorious gotchas.

I do like the idea of having Enums as array keys. But not by casting them into integers or strings just to make them fit somewhere they currently don't go by secretly appending "->value". So yes, typed arrays aren't a thing and you would end up with arrays with integer, string and Enum keys, and (barring being convinced otherwise) I'm okay with that.

But I've put way too much effort into researching this subject and recapitulating the work of others for someone who isn't going to be writing the RFC or actually doing the work implementing it.

hguenot commented 1 year ago

From my point of view PHP is a less typed language so it seems acceptable, for a first approach, $arr[Role::ADMIN] and $arr['ADMIN'] and $arr[Other::ADMIN] references the same memory space (by using the enum name) It just need to be clearly documented. I agree it should be better to check the type but this can be done in a second step.

TheApproach commented 1 year ago

For the past three hundred years one of the meanings of "enumeration" is "a list". And that is what the word means in programming: a list of possible values. Yes, the point of enums is to enumerate, but just because you can list the number of days in a week (Friday, Monday, Saturday, Sunday, Thursday, Tuesday, and Wednesday) and then count them (seven), doesn't mean that the days of the week are themselves numbers any more so than (Greed, Lust, Gluttony, Envy, Wrath, Sloth, Pride). Those are three different types. Invisibly mashing one type into another has been the source of some of PHP's most notorious gotchas.

I do like the idea of having Enums as array keys. But not by casting them into integers or strings just to make them fit somewhere they currently don't go by secretly appending "->value". So yes, typed arrays aren't a thing and you would end up with arrays with integer, string and Enum keys, and (barring being convinced otherwise) I'm okay with that.

But I've put way too much effort into researching this subject and recapitulating the work of others for someone who isn't going to be writing the RFC or actually doing the work implementing it.

The idea is not to cast them or secretly append anything. Rather than ->value aught to be assumed in the context of keys. Whether by intent or not, you're conflating a wide swath of things and seeing problems where none exist. The correlation of "enumeration" to the natural numbers above is a very good point. There is not a 3rd type of key, there remain 2 types of keys; string and int.

Enums ARE unsigned ints, the natural numbers, indeed this is because lists have cardinality and ordinality. The "numer" in "enumeration" comes from "numeric". Enumerating is the act of numbering, or counting, the lists which you yourself refer.

Backed enums are ints associated by preprocessor to strings or secondary ints. This is unavoidable in any implementations. Under the hood it doesn't matter what syntactic sugar a language presents, this is the underlying reality. Some need to use the underlying structure. There is no reason to prevent them as you are still freely able to use all the type checking that exists.

The errors in your definition and implementation have been addressed multiple times. Let us be adults here. Future version may indeed require an RFC for more mature object-based array keys, at which point backend enums could include ints, strings and objects as a backing. That would be the proper implementation. Trying to derail this issue by conflating type safety in a context where no such thing exists is drastically more akin to kludging a square peg into a round hole. The simple solution, which has significant support, is best on all fronts and helps the language move toward all goals presented, including your own. Let it go.

ahajzik-crido commented 1 year ago

I don't agree that enums are unsigned integers. If you enumerate something you provide a name of things one by one (according to the definition). It also doesn't cause that named things are oreder. If you enumerate days of week you may use natural feeling and start from Monday, but somone else might start from Sunday or even not respect the sequence in a week. If you enumerate car brands, colors, flowers, continents, professions and so on, you don't provide any order, you just specify named members of particular collection.

Returning to our issue... Any implementation that allows to use Enums as array keys is better than none. If it will use ->name or ->value under hood it is also OK, unless not documented. I think it can be good for a first step. Everyone who will use such solution must ensure to use only one Enum cases in one array to prevent potential disambiguous errors.

Finally, implementation that treat each case from any Enum type as unique is much better and should be a target solution. It could provide a powerful feature as we could for example have enum FooModuleConfig, enum BarModuleConfig etc. and use them in a single array of all config parameters (cases).

hemberger commented 1 year ago

Trying to derail this issue by conflating type safety in a context where no such thing exists

An Enum type is simply a way of expressing what set the members belong to. You can use both the "Days of the Week" and the "Seven Deadly Sins" to count to 7, but that doesn't mean they're the same. I believe this is what @MorganLOCode means when they talk about type safety. It's more than just PHP types.

The quick and dirty solution (coercing enums to an array key type) looks appealing at first glance. It would allow you to do something like

$sides = [
    Shape::Triangle => 3,
    Shape::Square => 4,
];
print($sides[Shape::Triangle]); // prints 3, yay!

But what other keys would you allow to reference the same memory space? Not an integer, since enum cases are not intrinsically backed by a scalar value. Do you use the FQCN of the enum? Or its serialized type? If simply ->name, then Shape::Triangle and Instrument::Triangle would collide with each other when used as keys in the same array. And how do you safely recover an enum if you only have the array keys?

If you're not concerned about strict types, it may not seem like a big deal to switch the underlying implementation from some string or int proxy to the enum itself as a future enhancement. However, because PHP is a typed language, this would be a massive breaking change. I think we're all here because we agree that enums as array keys is worth a bc break, but if we do it the quick and dirty way, then all we really have is some syntactic sugar that is unintuitive and potentially unsafe.

MorganLOCode commented 1 year ago

The idea is not to cast them or secretly append anything. Rather than ->value aught to be assumed in the context of keys.

Which would have the effect of casting the enum into a string, an integer, or bottom (depending on whether it's a string-backed Enum, an integer-backed Enum, or an unbacked Enum).

Backed enums are ints associated by preprocessor to strings or secondary ints. This is unavoidable in any implementations. Under the hood it doesn't matter what syntactic sugar a language presents, this is the underlying reality. Some need to use the underlying structure.

And they can. But since no-one has mentioned UnitEnum::cases() (denoted in the page you link to by ν), it's probably not relevant to the issue.

SuitespaceDev commented 1 year ago

Trying to derail this issue by conflating type safety in a context where no such thing exists

An Enum type is simply a way of expressing what set the members belong to. You can use both the "Days of the Week" and the "Seven Deadly Sins" to count to 7, but that doesn't mean they're the same. I believe this is what @MorganLOCode means when they talk about type safety. It's more than just PHP types.

The quick and dirty solution (coercing enums to an array key type) looks appealing at first glance. It would allow you to do something like

$sides = [
    Shape::Triangle => 3,
    Shape::Square => 4,
];
print($sides[Shape::Triangle]); // prints 3, yay!

But what other keys would you allow to reference the same memory space? Not an integer, since enum cases are not intrinsically backed by a scalar value. Do you use the FQCN of the enum? Or its serialized type? If simply ->name, then Shape::Triangle and Instrument::Triangle would collide with each other when used as keys in the same array. And how do you safely recover an enum if you only have the array keys?

If you're not concerned about strict types, it may not seem like a big deal to switch the underlying implementation from some string or int proxy to the enum itself as a future enhancement. However, because PHP is a typed language, this would be a massive breaking change. I think we're all here because we agree that enums as array keys is worth a bc break, but if we do it the quick and dirty way, then all we really have is some syntactic sugar that is unintuitive and potentially unsafe.

There is no coercing or casting involved. This is simply asserting that a symbol should represent its value. Just as when $x = 5 means x is now an integer and $myList[ $x ] makes sense. While requiring $myList[ $x->value ] is awful.

Any concerns about type should segment into their own separate issue or RFC.

Consider any code that has to deal with character sets -- as one of hundreds of examples why any discussion of types should be segmented from this assertion. For example ASCII, Unicode and Braille all encode the alphabet and all can be stored as text blobs but use different byte orderings on their enums traditionally. Recommendations about typing above would create large amounts of duplicate, very verbose code where no other language would. These reveal a fundamental misunderstanding about implementation and purpose but most of all are a completely separate issue.

The effect of typing go beyond current implementation even for standard variable keys. It is a non-sequitur and brings a wide arrangement of secondary issues which are not appropriate for this discussion.

Again: This issue is simply asserting that a symbol assigned to an instance of an enum should represent its value. This breaks no existing code and lets developers begin using the use pattern, regardless of where future RFCs fall on typing.

marc-mabe commented 1 year ago

I think what you are looking for has been declined 9 years ago https://wiki.php.net/rfc/objkey

Would be interesting to see if opinions have been changed or what the reason was

SuitespaceDev commented 1 year ago

Hmm the conversation around that https://marc.info/?l=php-internals&m=141164271705240&w=2 RFC seems to have centered around adding a magic hash() method which disambiguated toString() specifically for use in keys -- but there were several issues with that approach and a very different user base.

At this time I've drafted up a new RFC centering around transparently using value semantics which, in my opinion, are natural to enum. I have also included some notes to the conversation about typed keys but given there are several issues and RFCs which currently hit on changing ArrayAccess, operator overloading, type keys and/or implicit casting with arrays...

This conversation has been mostly focused on using existing semantics and simply replacing an error with a successful statement in a way that lets the remaining issues surrounding object keys and typing in general be iterated on. So, with enums unlike other objects, the instances do boil down to some primitive type. This would allow code usage to be cleaned up now, enabling future RFCs and avoiding backward incompatibility issues.

Though it is not really something I am particularly worried about; I have included briefly recommendations for how we can move toward typed object keys from this if desired -- but that would be a separate next phase RFC if so. Hopefully I've written it up properly to minimize the scope for internals to consider.

On Wed, Apr 19, 2023 at 11:23 AM Marc Bennewitz @.***> wrote:

I think what you are looking for has been declined 9 years ago https://wiki.php.net/rfc/objkey

— Reply to this email directly, view it on GitHub https://github.com/php/php-src/issues/9208#issuecomment-1515027746, or unsubscribe https://github.com/notifications/unsubscribe-auth/AUDHGOBLLGHMGBDCNXBKFI3XCAGR3ANCNFSM55EX6SIA . You are receiving this because you commented.Message ID: @.***>

Crell commented 1 year ago

Backed enums are ints associated by preprocessor to strings or secondary ints. This is unavoidable in any implementations. Under the hood it doesn't matter what syntactic sugar a language presents, this is the underlying reality. Some need to use the underlying structure. There is no reason to prevent them as you are still freely able to use all the type checking that exists.

At least in PHP, this is fundamentally untrue. Enums are object instances. Period. They are not ints, at least no more than everything in a computer is eventually an int or float. As far as the engine is concerned, they are objects. This is also the case in several other languages.

There seem to be a lot of misconceptions being spread here, so I encourage everyone in this thread to read https://peakd.com/hive-168588/@crell/on-the-use-of-enums before continuing. A lot of thought went into the design of enums, including what they are not.

I'm not against allowing arrays of enums; I'd actually love to see EnumSets at some point in the future, so we can "bit flag" them, too. But treating enums as "magic ints" or "magic strings" is fundamentally wrong and the wrong way to go about it.

Crell commented 1 year ago

Also note that enums, being objects, can have methods, and that is a natural way to handle a lot of the "lookup table" use cases that have been shown above. See the various examples in the documentation of using enum methods.

KapitanOczywisty commented 1 year ago

PHP is missing proper Map / Set classes, if anything I'd think about adding these. For convenience Map could have constructor similar to classic array(...) e.g. map(Foo::Bar => 3) for better experience.

Discussion about allowing more key types for array doesn't make sense right now, since following BC issues would be much greater than recent deprecations, which already created heated discussion about migration inconvenience and syntax stability.

Having BackedEnums cast to int/string is probably more realistic, but at the end of the day someone needs to write RFC, get votes and implement proposal. Though still this would be only a partial fix for this issue.

SuitespaceDev commented 1 year ago

While I think there are some points being misconstrued, as we have been discussing the concept not the implementation, which is obviously an object reference. I have indeed followed PHP enums development, as well as enums standardization in many other languages in the last 4 decades, it is important not to put the cart before the horse here. BackedEnums are not the primary identity or purpose of enums. That's a secondary concept generated by use patterns -- a very useful one. However the relation to ordinality and being able to model it is critical to both adoption (over class const), code legibility and underlying performance.

The actual claim about the definition is evident by PHP's UnitEnums, where the "Unit" concept comes from and why it is the default action. You are absolutely right that enums have, in programming, traditionally been used for labeling but this practice originates from developers taking note of their use in mathematics. This use case is strongly tied to the ability to function as numerical indices which made using enums as labels as efficient as using literals in many cases. Practically this gave us "free" O(1) associative structures. However that is emergent from use, rather than definitional. BackedEnum came about later as an extension to this use pattern, noticing that user input always comes as a string that has to be checked and mapped.

Indeed PHP has had many debates about enum, many of which line up with this very well in that they proposed to get the feature out to get feedback ASAP and iterate.

This is that feedback from scores of folks saying, hey, no matter which way you define enums, we shouldn't be required to write ->value in an array key. It is fine if you believe that my definitions are wrong or dislike that I care about the origins in mathematics -- but even in the alternate definition this remains true.

  1. No breaking changes or backward incompatibility

  2. All current use cases are stable

  3. If typing ever does get more advanced, great. Enums can be backed by more types if that happens.

  4. There is no "magic" here, nor can methods alleviate the index labeling problem. PHP has UnitEnum, Int backed enums and String backed enums -- this was not a mistake. As long as PHP intends to have int and string keys, that fight is about PHP array access not enums.

  5. Class consts are the only solution for anyone using enums "the normal way". This should be the number 1 reason really. Our team waited for years hoping for PHP enums, only to find an implementation that we cannot use for our projects. I have to now defend PHP from those in our group wishing to switch because this was such a big issue in our company projects.

  6. Nothing is lost, something is gained.

  7. A second reason no "magic" functionality is happening. Though this fix is small, it should have been the original default action. We have a symbol that literally is asking us to say ->value to get its value. I think everyone can see why this is silly if you back up a little. It is especially silly when enums are supposed to make labels and keys more readable and self-documenting.

  8. Transforming an error into behavior the developer expected.

KapitanOczywisty commented 1 year ago

We have written up an RFC and, unless someone internal already knows the path of least resistance

I'd be useful to share RFC draft e.g. on gists. You can even link gist/gh on internals ml for purpose of discussion, before publishing proper RFC.

we are committed to put people on implementation if accepted

I'd suggest writing (at least partial) implementation before voting, it'd be better received and by implementing you can discover design flaws. Such feature should be relatively easy to implement anyway.

MorganLOCode commented 1 year ago

I'm just concerned at how, without the ->value to (make it explicit / be honest) that what is going on is not what it otherwise says is going on,

echo count([Element::Tungsten => 74, PhysicalQuantity::Power => 270]);

would obviously output 1.

(Perhaps calling the property value was a mistake, as the object instances already are their own values; now the values have values and which is the value?).

ramsey commented 1 year ago

@SuitespaceDev I recommend starting here. It has all the information on how the process works. https://wiki.php.net/rfc/howto

Crell commented 1 year ago

What @MorganLOCode said. Enum values are only unique within their own enum space. They're not unique across all array keys. Silently treating them that way is creating an opportunity for data loss.

I have actually yet to need to use enums as an array key myself. Any time I do, it's a static case, which means it's just easier to move that logic to a match statement in a method.

enum Element
{
  case Hydrogen;
  case Oxygen;
  case Iron;
  case Mercury;

  public function isGas(): bool
  {
    return match ($this) {
      self::Hydrogen, self::Oxygen => true,
      default => false;
    }
  }
}

I am very much in favor of making enum lists (maps, sets, etc.) more capable and robust. But just silently translating it to its serialized value (which is what backed enums are for) is no the way to do it.

SuitespaceDev commented 1 year ago

There is now an RFC for this issue. https://wiki.php.net/rfc/treat_enum_instances_as_values

Please let me know if I have failed to capture your wishes on either side of the debates here. Hopefully I have provided a roadmap which represents the most viable starting point and reasonable iterations to a state which everyone appreciates.

SuitespaceDev commented 1 year ago

I hear you. However it is precisely because we can swap indexing for logic, as you point out, that many of us have this need. It means there is a tradeoff between storage/structure and processing that can be tuned in both directions. Some algorithms require this in order to remain constant time - many algorithms have the ability to take advantage of it.

That said, I indeed tried to consider the typesafe case and made several recommendations for how we can get there in above mentioned RFC. At present the fact is that no array keys are typesafe and that is by design. PHP remaining loosely-typed is one of its top three principles; I have just learned while using the RFC template. This roadmap will enable both loose and strict typing, similar to how we have stronger and weaker equality comparisons.

I'm sure it can be further refined so please let me know if your use cases are not covered. My goal is to cover as many as possible and avoid matching a specific ideal too closely.

Crell commented 1 year ago

The RFC template is about 15 years old and no longer reflects the effective consensus of the Internals community, which is that type predictability is a virtue and making the developer guess about a type is a vice. The "loosely typed" thing is vestigial and we should probably remove it, because it's misleading and completely inaccurate if you look at modern PHP.

SuitespaceDev commented 1 year ago

Well, to be honest you cannot ever have fully strict typing in a scripting or web language. There will always be a need to have special conversions between ints, strings and symbols due to the nature of decoding user input. I do not see broad consensus in the RFC history in either direction and do not know the full context but I am in full support of the legacy of that quote. Either way, for this case the result is more consistent with modern usage in both sides of the type system.

github-actions[bot] commented 1 year ago

There has not been any recent activity in this feature request. It will automatically be closed in 14 days if no further action is taken. Please see https://github.com/probot/stale#is-closing-stale-issues-really-a-good-idea to understand why we auto-close stale feature requests.

MacDada commented 1 year ago

I don't want (implicit) type conversions -> the whole point of enum for me is to have strict and explicit value types.

enum Role: string
{
    case ADMIN = 'admin';
    case MODERATOR = 'moderator';
}

$roles2Colors = [
    Role::ADMIN => 'red',
    Role::MODERATOR => 'blue',
];

isset($roles2Colors[Role::ADMIN]); // true - cool
isset($roles2Colors['not a role']); // false - cool
isset($roles2Colors['ADMIN']); // FALSE - I wish
isset($roles2Colors['admin']); // FALSE - I wish
isset($roles2Colors[Role::from('admin')]); // true - cool
SuitespaceDev commented 1 year ago

I don't want (implicit) type conversions -> the whole point of enum for me is to have strict and explicit value types.

enum Role: string
{
    case ADMIN = 'admin';
    case MODERATOR = 'moderator';
}

$roles2Colors = [
    Role::ADMIN => 'red',
    Role::MODERATOR => 'blue',
];

isset($roles2Colors[Role::ADMIN]); // true - cool
isset($roles2Colors['not a role']); // false - cool
isset($roles2Colors['ADMIN']); // FALSE - I wish
isset($roles2Colors['admin']); // FALSE - I wish
isset($roles2Colors[Role::from('admin')]); // true - cool

Unfortunately, and by no fault of your own, the recommendation has absolutely nothing to do with implicit type conversion but this has become the focus of discussion due to severe miscommunication issues.

This issue is simply about removing the requirement to type ->value, which is unnecessarily verbose.

This would not change the type safety of using enums in any existing context in any way. This would allow for greater type safety dealing with userland keys in fact.

We are still happy to donate as much time as needed to this RFC, however one person in the internals group with outsized influence appears to have tanked it. If community has any interest you will have to rally them to the RFC otherwise our hands are basically tied. A very disappointing result which has shown me PHP has become more of an oddity than a serious language, which would rather pursue quirky implementations than take feedback

KapitanOczywisty commented 1 year ago

however one person in the internals group with outsized influence appears to have tanked it.

I've seen some people disagreeing with your proposal, but none of them have "outsized" influence, not to mention that they themselves had some RFC declined. If you want that feature, you need to convince people that throwing away type safety is a good thing, which as expected will be hard. Some people put quite a lot of effort to make PHP type system more predictable and to eliminate some implicit type casting, and in that context such change could be considered as a step backward.

Also there are some other ways to achieve the similar result e.g. magic method for casting (example only), which would have other advantages and drawbacks, so even if your idea won't have enough support problem still could be solved.

SuitespaceDev commented 1 year ago

If you want that feature, you need to convince people that throwing away type safety is a good thing, which as expected will be hard

A) More people have supported the feature than disagreed with it.

B) All who have disagreed with it bring up type safety, which is not changed by the proposal in any way, shape or form. All that changes is how many keystrokes are used to achieve the same result as presently available. All arguments presented about type safety are directed at the current implementation, not this proposal -- and blocking this proposal because of that is nothing short of petty.

C) Well we disagree on the assessment of outsize influence. Said person has vied for increased authority consistently, and even admits to dismissing proposals without considering them if they might breach the roadmap of his pet features.

Steering away from long stated core PHP principles for unintuitive, non-standard implementations as community members cry foul -- only to serve a handful of niche interests -- is not a professional or viable roadmap.

You don't have to worry about me, after watching how several other issues are handled; like the attempt to create a technical committee which doesn't act like a technical committee: I will assume this battle lost and move our company slowly into another language.

hemberger commented 1 year ago

This issue is simply about removing the requirement to type ->value, which is unnecessarily verbose.

I would say that this issue was originally about using enum cases as array keys, not about implicit conversion between a backed enum case and its value.

All who have disagreed with it bring up type safety, which is not changed by the proposal in any way, shape or form. All that changes is how many keystrokes are used to achieve the same result as presently available.

Your RFC, as I understand it, would allow array<int|string, mixed> to be accessed using an enum case. The type of the enum case is not int|string, it is an object that is an instance of its enum class. Strictly speaking, accessing the array with the enum case would be a type error. Its value attribute (if it has one) is a different type, and implicitly converting between the two seems like a step backwards.

That said, since enums cannot be array keys currently, there is no ambiguity about what we mean if we try to access an array with an enum case (we mean to use its value, if it has one). This is the sense in which the RFC has a practical value. However, I would not want to jeopardize a comprehensive solution to the issue of enum cases as array keys for the sake of some syntactic sugar, and I trust the PHP devs to make that judgment call.

KapitanOczywisty commented 1 year ago

A) More people have supported the feature than disagreed with it.

Technically you've never announced that RFC as being under discussion so there wasn't much discussion about that.

B) All who have disagreed with it bring up type safety, which is not changed by the proposal in any way, shape or form.

Someone can say "why array_keys returns some strings and not my enums?". It's not breaking type safety for your use case, but breaks for other people.

C) Well we disagree on the assessment of outsize influence. Said person has vied for increased authority consistently, and even admits to dismissing proposals without considering them if they might breach the roadmap of his pet features.

Sigh... You're lacking a lot of context and throwing some wild claims...

I will assume this battle lost and move our company slowly into another language.

If you want to go, then go. TypeScript is pretty good.

SuitespaceDev commented 1 year ago

The type of the enum case is not int|string

It is true that I intend to have 1 to 3 follow-up RFCs regarding array keys, which the proposal discusses as a potential roadmap to solving array keys in a type-safe manner. Those however remain in the future and are merely an aggregation of the various wishlists found in this thread.

The actual RFC itself is no more, or less, than removing the need to type ->value -- whether in an array key or not. More specifically the RFC asserts that enum instances are userland symbols and should always imply ->value rather than produce an error.

This would allow instances as keys simply because PHP presently allows int|string as keys and enums can be backed by int|string. My entire argument about index-friendly counting enums was relegated to the followup RFCs, as indeed it seemed more controversial.

=========

A) More people have supported the feature than disagreed with it.

Technically you've never announced that RFC as being under discussion so there wasn't much discussion about that.

This is untrue. The advise for RFC noobies given by the group is to field the RFC first before jumping in deeper. Discussion may not have been comprehensive but between the initial support from the internals list, the above thread (and frankly industry norms) there is overwhelming support. Aside from such support, all detractors concerns have been accepted and incorporated into the RFC by splitting the common ground from the controversial decisions left to future RFCs.

B) All who have disagreed with it bring up type safety, which is not changed by the proposal in any way, shape or form. Someone can say "why array_keys returns some strings and not my enums?". It's not breaking type safety for your use case, but breaks for other people.

That is also not correct. Currently the action produces an error and is broken for all cases. It is not breaking type safety in any way shape or form that is not already the state of the PHP engine. No change to array keys are being made by this RFC. The only change is some code which produces an error today would no longer produce an error and be more semantically valid.

C) Well we disagree on the assessment of outsize influence. Said person has vied for increased authority consistently, and even admits to dismissing proposals without considering them if they might breach the roadmap of his pet features. Sigh... You're lacking a lot of context and throwing some wild claims...

Mmm not really. There are many great ideas in the internals newsletter constantly being hunted down and killed by Crell. There is a major major pattern of abusing influence. Said influence may have been well earned through contribution and more power to him, but this is no longer an open community or a language that seeks to be useful to its community as a priority.

A small number of people telling the community to jump through rather silly hoops to be given a small voice, only to then be told ya well that's not gonna happen because our clique wasn't feeling it.. yea I don't think that's what's best for the language.

I will assume this battle lost and move our company slowly into another language.

If you want to go, then go. TypeScript is pretty good.

Obviously I do not want to go. We have used PHP for over 20 years and I am not excited about translating legacy code. But like many users, I have been perplexed why PHP keeps trying to force square pegs into round holes instead of using well founded techniques. Array keys, proper overloading, object keys are some of the most hotly anticipated features since 5.3 came out -- yet over the years you can find proposal after proposal get shot down in defense of protecting rather arbitrary goals with less support. Having read more of the internals conversations now, this is very common across other issues too.

I thought, well I shouldn't blame the project, I should get involved and do something myself if I value it. So I asked many folks not represented here, along with the aforementioned community representation and got to it. There are several reasons I was prepared and ready to receive for this failing, but certainly I did not expect to find that the language is often decided by the mere whims of a few offering a discouraging remark.