rfc:enum_allow_static_properties

This is an old revision of the document!


PHP RFC: Allow static properties in enums

Introduction

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.

Proposal

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.

Use cases

This is useful in cases where shared state involving immutable instances is used, e.g.

  1. https://en.wikipedia.org/wiki/Memoization of expensive operations (use of disk, cpu-intensive, service/db calls
  2. Keeping track of which enum case reflects the current state of a state machine or system

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);

This is better than alternative ways that can be used to store shared state

While I expect that a majority of enum declarations won't need shared state at all, some will benefit from shared state in code involving instances of those enums or static methods of those enums.

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:

  1. Property types can be used and enforced at runtime (and checked by type checkers).
  2. Visibility is easier to enforce and read (compared to global variables or static properties declared in other classes).
  3. It is easier to reset static properties in unit tests (compared to static variables).
  4. This can result in more concise and easier to understand code.

This is also useful because it allows enums to use traits that contain static properties, which was previously a fatal error.

This minimizes the backward compatibility impact of adding static properties to traits

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)

Backward Incompatible Changes

None

Proposed PHP Version(s)

8.1

RFC Impact

To SAPIs

None

To Opcache

None

Unaffected PHP Functionality

Instance properties continue to be forbidden on enums.

Discussion

Rare use case but no technical reason to forbid static properties

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

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.

Proposed Voting Choices

Yes/No, requiring a 2/3 majority

References

https://externals.io/message/112626#113037 brought up the same suggestion.

enums RFC

rfc/enum_allow_static_properties.1621295388.txt.gz · Last modified: 2021/05/17 23:49 by tandre