Happy New Year Internals! One of the areas I have been agonizing over in 2019 has been constants — especially class constants — and Mark Randall's following comment from the other thread cause me to remember that I had been planned to ask the list to discuss for a while.
> Begin forwarded message: > From: Mark Randall <marand...@php.net> > Subject: [PHP-DEV] Re: [RFC] "use global functions/consts" statement > Date: January 1, 2020 at 11:31:48 PM EST > To: internals@lists.php.net > > That doesn't cover defined constants, but IMO we should be pushing those to > be moved into class constants, simply because in the absense of function / > constant / anything-but-class level autoloading, things might as well be in a > readily available location. I have found namespaced class constants as a great usable syntax for DRYing out code during refactoring related to typically hardcoded literal values, or when writing code initially. Once such example might be: namespace Example; class API { const BASE_URL = 'http://api.example.com'; } With this a developer can use like so: namespace Example; .... $api_url = sprintf( '%s/%s', API::BASE_URL, '/widgets' ); Instead of writing this 20 different places across a codebase: $api_url = 'http://api.example.com/widgets'; The problem with using constants is that the value is still hardcoded and if we later want to change to pulling the data from a config file we have to change all the code that uses those constants. Yes we could use methods instead, but frankly constants feel more natural for things that typically get hardcoded. We all know that constants need to be, by definition, constant (a.k.a. immutable), but is there any reason we could not allow constants a one-time initialization via code? That way we would allow a developer to hardcode a constant and then later come back and convert that constant to be initialized from a configuration file without having to modify any code that uses the constant? If others agree with this concern then three (3) approaches come to mind, either of which would satisfactory to me although the first would be best IMO. That said, I am open to any approach that allows constants to be one-time initialized with code. Approach #1: Extend the const keyword to allow function-like syntax Extend the constant keyword to support all the syntax supported by methods except parameters, but only run the code the first time the constant is accessed, on demand: namespace Example; class API { const BASE_URL:string { return Config::get_options('api_base_url'); } } $api_url = sprintf( '%s/%s', API::BASE_URL, '/widgets' ); Approach #2: Allow one-time assignment to a constant Alternately we could allow one-time assignment to a constant, although this would be much less preferable because it would require the initialization to be explicitly called by a developer, and the developer would have to implement all their own on demand logic, or just initialize all classes on page load which most people would probably fall back to doing: namespace Example; class API { const BASE_URL; static function initialize() { self::BASE_URL = Config::get_options('api_base_url'); } } API::initialize(); $api_url = sprintf( '%s/%s', API::BASE_URL, '/widgets' ); Approach #3:Magic methods! A magic method could work too, but I know many others have reasons to dislike them no the least of which is lack of static typing. I include here more for completeness rather than as a serious suggestion: namespace Example; class API { static function __const(string $const_name) { $value = null; if ( 'BASE_URL' === $const_name ) { $value = Config::get_options('api_base_url'); } else { trigger_error( sprintf("constant %s::%s has not been defined.", get_called_class(), $const_name )); } return $value; } } $api_url = sprintf( '%s/%s', API::BASE_URL, '/widgets' ); Thank you for considering and I look forward to your thoughts. -Mike