Although enums are immutable objects, it is often useful to have functions or methods that operate on enum instances. In many cases, it would make sense to declare that functionality as static methods on the enum itself. In cases where static methods require shared state, it would be useful to allow storing those shared state in static properties. To ensure immutability of enum instances, it's only necessary to forbid instance properties, but all properties were forbidden in the initial functionality included with the enums RFC.
Allow static properties to be declared on enums or to be inherited by using traits. Continue to forbid instance properties. Additionally, update error messages to mention that only instance properties are forbidden on enums.
Instances of an enum will be immutable even if static properties are allowed - instance properties are what must be forbidden to ensure immutability.
This is useful in cases where shared state involving immutable instances is used, e.g.
E.g. for Memoization, it may be useful to keep the result of expensive operations with no side effects in a static properties in the same module. Typically, php static properties are used where many other languages may have module functions and state in a file separate from a class definition (e.g. due to autoloading and coding standards requiring one class per file).
// https://en.wikipedia.org/wiki/Sprite_(computer_graphics) enum Sprite { case FROG; case LOG; case GRASS; case WATER; // etc. /** @var array<string, MyModule\ImageData> */ private static array $cache = []; public static function getImageData(SpriteArt $value): MyModule\ImageData { $key = $value->name; if (!isset(self::$cache[$key])) { self::$cache[$key] = self::loadImageData($value); } return self::$cache[$key]; } // Called when is no longer used, the color scheme changed, etc. public static function clearImageData(SpriteArt $value): void { self::$cache = []; } // Slow operation: Read image data from disk and decode image data. public static function loadImageData(): void { // ... } }
For example, one way to represent fetching the current environment (of a known enumeration of environments) would be a static method on the environment enum itself. The environment instance is immutable, but the environment being loaded depends on a file.
enum Environment { case DEV; case STAGE; case PROD; private static Environment $currentEnvironment; /** * Read the current environment from a file on disk, once. * This will affect various parts of the application. */ public static function current(): Environment { if (!isset(self::$currentEnvironment)) { $info = json_decode(file_get_contents(__DIR__ . '/../../config.json'), true); self::$currentEnvironment = match($info['env']) { 'dev' => self::DEV, 'stage' => self::STAGE, 'prod' => self::PROD, }; } return self::$currentEnvironment; } // Other methods can also access self::$currentEnvironment } printf("Current environment is %s\n", Environment::current()->name);
While I expect that a majority of enum declarations won't need shared state at all, some will benefit from (or require) shared state in code involving instances of those enums or static methods of those enums, e.g. due to optimizations or unexpected business logic complications.
Compared to alternatives such as global variables and local static variables or static properties of placeholder classes, this is a useful option to have for the following reasons:
This is also useful because it allows enums to use
traits that contain static properties, which was previously a fatal error.
Projects may wish to enforce their own coding standards on how to appropriately use static properties in enums - e.g. I can imagine different projects may have different opinions, but having the functionality available to make use of would help their maintainers
Quoting Rasmus:
PHP is and should remain:
1) a pragmatic web-focused language
2) a loosely typed language
3) a language which caters to the skill-levels and platforms of a wide range of users
From the perspective of an end user of a library, adding a property to a trait (that previously had no instance properties) would be a new type of backwards compatibility break because if an enum were to use that trait, it would become an unavoidable backwards compatibility break because using that trait would become a fatal error at compile time.
For example, consider this trait
// StdoutLogger 1.0 trait StdoutLogger { private static function log(string $message) { printf("%s: %s\n", date(DATE_RFC2822), $message); } }
If a subsequent release of the library providing the trait were to add a static property, then enums using that trait would have an unavoidable fatal error at compile time.
// StdoutLogger 1.1 trait StdoutLogger { // each class directly using this trait has different storage for property values private static bool $loggingEnabled = true; // self within a trait refers to the class that directly uses a trait for methods inherited by a class private static function log(string $message): void { if (self::$loggingEnabled) { printf("%s: %s\n", date(DATE_RFC2822), $message); } } public static function setLoggingEnabled(bool $enabled): void { self::$loggingEnabled = $enabled; } }
The backwards compatibility break of adding static properties to traits would be minimized by allowing enums to contain static properties.
(Instance properties in traits would continue to be an issue if used by enums)
In some cases, enums may be associated with shared functionality that uses shared state, and static properties may be the most practical way for a developer/team to migrate it.
The following are potential use cases:
None
8.1
None
None
Instance properties continue to be forbidden on enums.
PHP enums were already able to have instance and static methods.
From Ilija Tovilo, co-author of the enums RFC
https://github.com/php/php-src/pull/6997#issuecomment-842356465
I'm not super convinced by the use case (of
enum Environment
). Using static variables in enums would certainly not be great. Methods will behave differently in different environments even if the case is the same. This is certainly less than optimal.On the other hand, I do think that needlessly restricting the language to babysit developers is not good unless there's a technical reason to do so, which doesn't seem to be the case here.
From Larry Garfield, co-author of the enums RFC
https://externals.io/message/114494#114497
Would you be able to provide more real life example? The example in RFC could easily encapsulate current Environment reading in for eg. EnvironmentConfiguration class with static property and method and TBH possibly that would be my preference to solve this.
Cheers, Michał Marcin Brzuchalski
I would agree. Static properties are ugly to begin with. They're globals with extra syntax. I have no desire to see them on enums.
Also a clarification, since it wasn't entirely clear in Tyson's original email: Static methods on Enums are already supported. They were included in the original Enum RFC. The change proposed here is just about static properties.
Counterarguments include:
https://externals.io/message/114494#114506
Personally, I'd prefer to see enums as value objects only, adding static properties allow to implementation of statically conditional behaviour. IMO enums should consist only of pure functions. This is why I'd vote NO on this proposal.
Cheers, Michał Marcin Brzuchalski
This is a Yes/No vote, requiring a 2/3 majority.
Voting started on June 1, 2021 and ends on June 15, 2021
https://externals.io/message/112626#113037 brought up the same suggestion.