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



 

Reply via email to