On Wed, 29 Mar 2023 at 09:31, Rokas Šleinius <rave...@gmail.com> wrote:
> Enums were a very useful addition to PHP, however one aspect of them is > neither > explicitly documented - or seemingly even talked about. > > Enums were implemented as final so they cannot be extended nor can extend > anything else. > This is by design. Enumerations are in type theory parlance sum types. Objects in PHP, in general, are what are called product types. > From a user perspective it's surprising - and actually limiting. > The point of enums is to be limiting. > USAGE EXAMPLE: > I am making an error management system: each error presented to the user > must have a unique code visible. > > ```php > class SystemError > { > public function __construct( > private string $errorText, > private ErrorCode $code > ) { > } > > public function __toString(): > { > return $this->errorText . ' ' . $this->code->toString(); > } > } > // ... > > enum ErrorCode > { > case Code_1; > case Code_2; > > public function toString(): string > { > return 'Error code:' . substr($this->name, strlen('Code_')); > } > } > ``` > > Now I want to modify it to support different modules with different > namespaces for > errors, e.g. an ApiError, simple enough: > > ```php > enum BaseErrorCode > { > // ... > } > > enum ErrorCode extends BaseErrorCode > { > case Code_1; > case Code_2; > > // ... > } > > enum ApiErrorCode extends BaseErrorCode { > // ... > function toString(): string > { > return 'Error code:API-' . substr($this->name, strlen('Code_')); > } > } > ``` > > This results in a syntax error. > You are approaching the design of this in a fundamentally wrong way. Enums, as sum types, are meant to enclose a *finite* number of states. If you want to expand those finite number of states, the solution is to use a union type. Let's say you have those 3 enums: enum GenericErrors { case ErrorType1; case ErrorType2; } enum FileErrors { case FileErrorType1; case FileErrorType2; case FileErrorType3; } enum NetworkErrors { case NetworkErrorType1; case NetworkErrorType2; case NetworkErrorType3; } And you have a function that can fail with either a generic error or a file error then you can be explicit by having: function foo(): T|GenericErrors|FileErrors { /* Do something or Fail */ } This signature provides you with great type safety and can be checked via static analysis that *all* error cases are handled. Moreover, you know you don't need to handle NetworkErrors. Extending an Enum *loses* you this type safety, because now ff you say something returns a GenericErrors well who knows if you cover all cases because someone might have ad-hock added a new one. PHP's type system is not perfect yet, but it has become powerful enough that you can do things like what I just described. Ideally we would have generics, and you could use a Result/Either Monad (fancy word for "wrapper") to have a return type of Result<T, GenericErrors|FileErrors> Use the type system properly instead of trying to shove everything into an "Inheritance" view of things. Union types are not "bad", they are another way of creating a sum type. (You could, with a lot of effort, create "proper" enumerations since PHP 8.0 by using union types and singleton classes.) PROPOSAL: > > Enums should be able to extend other enums. > > For a complete wishlist, add: > * abstract enums; > * enums allowed to implement interfaces; > Enums can already implement interfaces: https://3v4l.org/tKtQ0 > However since I have no experience in PHP source code, I can only > provide the test suite for a possible PR this might have :( > > Do you think this is likely to get implemented? > I dear hope so not. Breaking fundamental type theoretical assumptions is terrible. Best regards, George P. Banyard