Search code examples
phpenumsconstants

Are string-backed enums useful as string constant dumps in PHP?


In order to get in touch with PHP, I began a little website project from scratch.

The project steadily grew so that now, I'm refactoring my codebase.

Up until now, I made use of constants which I use to process HTML forms, e.g. for a web shop:

The input fields' names are generated by PHP, and there would be a field <input name="<?= OrderFirstName ?>">.

The user input is stored in the $_SESSION by the same key: $_SESSION[OrderFirstName] = $_REQUEST[OrderFirstName].

So I ended up with lots of constants in the global namespace, also for other purposes than the web shop, e.g. const StartPage = 'start'; const NewsPage = 'news';.

To tidy things up, I thought of organizing constants in backed enums:

enum Pages : string {
    case Start = 'start';
    case News = 'news';
}

enum Order : string {
    case FirstName = 'firstname';
    // and lots of others …
}

At first glace, it seemed to be the more readable and maintainable option.

However, now whenever I access the constants, I need to type Pages::Start->value instead of StartPage. In another question, a solution was to use an abstract class like so:

abstract class Pages {
    const Start = 'start';
    const News = 'news';
}

This lets me write Pages::Start to get the intended string, yet from a design point of view, this seems wrong given that enums now exist in PHP.

Am I missing something here? The enum solution certainly appears more maintainable to me than a dump of constants in the global namespace, then why is it such hardship to write it out? Funny enough, with all its dynamic typing, PHP won't let me use Pages::Start, because it is not implicitly replaced by the value.


Solution

  • Enums, in the way that PHP has implemented them, are not just a special type of constant; rather, they are a way of defining a custom type, that the language can keep track of. As a comparison, consider the "boolean" type: it has two values, true and false; in the same way, a custom "Month" type might have 12 values, Month::January, Month::February, etc. The idea is that you can have functions which expect a Month, and accidentally passing some other value will be an error.

    The "backed enum" functionality is just a shorthand way of defining a conversion from or to some "standard" representation of the value as a string or integer, defining ->value, from and tryFrom for you. You can also define whatever other conversions you want.

    If the values you're using do form a natural type - e.g. if a Page value can only ever be one of a fixed list, then an enum might be appropriate. The conversion to and from string is just part of the display and input processing logic, just as with any other complex value (e.g. a date, or a price with attached currency).

    If you just want to put lots of thematically linked constants together, but they aren't "all the values" of anything, you can just use a class, or even a namespace, e.g.

    namespace Hschmauder\SomeApp\Fields\Order {
       const FirstName = 'firstname';
    }
    

    On the other hand, maybe the long list of strings is a smell that you're overusing strings and arrays in contexts where objects could be used:

    $_SESSION['order'] = Order::fromFormArray($_POST);
    

    The knowledge of what string keys are used in the form would be isolated in a couple of methods of the class, and most of the application would access fully-typed properties of the object.