I have been writing code in a variety of programming languages since the early 1980s. Today, I spend my time developing plugins for WordPress. That means PHP. On the whole, I like PHP. One of my favorite things about it is that whenever I need something special, there is often a built-in function that already does it. I find that to be very convenient. Unfortunately, there are a lot of things that PHP doesn’t do for you.
In PHP you can build classes. You can instantiate those classes to access their properties and methods. You can even access classes statically. All good things.
Unfortunately, there is a huge difference between classes that are instantiated and those that are called statically. For example, consider the following block of code.
Examples shown herein are meant to demonstrate what is being discussed. Do not take my code and blindly insert it into your code. It might work, but do not depend upon it.
For example, the block of code below, blindly accepts the arguments passed to the class instantiation without verifying that the right arguments were passed, nor verifying that no extra arguments were passed. By simply adding another array element to $args, you could crash the class constructor, as it is written below.
So take my advice, not the examples, as shown. Examples take shortcuts that you should not take.
<?PHP
class dog {
private $name;
private $breed;
private $sex;
public function __construct($args) {
foreach ($args as $key => $value) {
$this->$$key = $value;
}
public function dog_bio() {
$sex = ($this->sex == 'Male' ? 'his' : 'her');
return "My dog is a {$this->breed}. {$sex} name is {$this->name}.";
}
}
$my_dog = new dog([ 'name' => 'Cody', 'breed' => 'Golden Retriever', 'sex' => 'Male']);
echo $my_dog->dog_bio();
?>
The above example would produce:
When we instantiate the class….
…the class’ __construct() function is called to initialize the class. In this example, the constructor also grabbed the passed-in information and stored it for later use..
The Problem
For classes that are never instantiated and their methods and properties are only accessed statically, the constructor function is never called. The developer who accesses class methods or properties statically is left to do their own initialization on the class.
For example:
<?PHP
echo dog::dog_bio();
?>
…will write out unpredictable results because the properties $name, $breed, and $sex have never been initialized.
That is a pain. I believe that the PHP developers, i.e., those who develop PHP, not those who develop in PHP, should create an automatically called, static constructor upon the first use of the class.
There are probably as many workarounds to this problem as there are developers. I like my workaround because it eliminates the need for the calling function, really the developer, to remember to initialize a class before accessing its elements statically.
My Early Solution
My first attempt to deal with this meant that every function that could be called statically, had to first look and see whether the class had been initialized.
The example below is a snippet from one of the classes that I use in my plugins. The properties and methods change for each different plugin, but the purpose remains the same — handle all plugin options in one place, and access them statically. This lets me reference a plugin option anywhere as:
In the following example, note the functions is_first_use() and initialize_class(). Every function that will be called statically starts with:
…which determines whether the class has been initialized. If it has not been initialized, then it calls the method to initialize it.
<?PHP
class plugin_options {
private static $class_initialized = false;
private $options = Array();
private $default_options;
private $class_vars;
public $expert_mode;
public $consolidate_styles;
public $consolidate_scripts;
private static function is_first_use() {
if ( ! self::$class_initialized ) {
self::initialize_class();
self::$class_initialized = true;
}
}
private static function initialize_class() {
self::$class_vars = get_class_vars(__CLASS__);
unset(self::$class_vars['options']);
unset(self::$class_vars['default_options']);
unset(self::$class_vars['options']);
// Get the meta data from the wp_options table.
self::$options =
\get_option('pas_aib_options', self::$default_options);
foreach (self::$options as $key => $value) {
if (array_key_exists($key, self::$class_vars)) {
self::$$key = $value;
}
}
}
public static function get_option($option, $default = null) {
self::is_first_use();
if ($default == null) {
$default =
(array_key_exists($option, self::$default_options) ?
self::$default_options[$option] :
null);
}
return
(array_key_exists($option, self::$options) ?
self::$options[$option] :
$default);
}
public static function update_option($option, $value) {
self::is_first_use();
self::$options[$option] = $value;
if (array_key_exists($option, self::$class_vars)) {
self::$$option = $value;
}
// write the changed meta data back to the wp_options table.
\update_option('pas_aib_options', self::$options);
}
}
?>
Notice that every public function calls is_first_use() before it does what it was designed to do. If your software is going to call methods in this class, just, a few times, then checking for whether the class has been initialized with each call probably won’t be a big deal.
Reduce the Waste
My plugin has nearly two dozen options that are checked and referenced throughout the dozen or so classes that make up my plugin. The method
might get called hundreds of times for a single page load. That is a huge waste of resources — get_option() calls the is_first_use() function every time it is called, over and over and over again, just to be told, “nope, it’s not the first use”.
A few lines of code in the plugin_options class address that issue, storing the values that are used most frequently in properties. Note the call to the….
….built-in function. This function returns, in an associative array, a list of the class’ properties — both public and private.
A few lines of code in the initialize_class() function save the most used plugin_options as properties.
foreach (self::$options as $key => $value) {
if (array_key_exists($key, self::$class_vars)) {
self::$$key = $value;
}
}
If there is a property named $option, then store the value there, upon class initialization. Here is a good write-up on variable variables. Note the use of $$.
Now, those plugin_options that are referenced most often, may be referenced by a property rather than by calling get_option() which calls is_first_use() every time.
There is a better way. And I still use the properties to hold those options that are called most frequently, because I still don’t want to call get_option() a few hundred times.
A Better Way
One alternative is to change the function initialize_class() from private to public and call it before calling get_option() or update_option() or accessing any of the class’ properties. But that raises another issue. When do you call initialize_class()? And how do you remember that you’ve already initialized the class?
My Solution
The answer to the question, when ‘do you call the initialize_class() function?’ is really the key to my solution. The requirements are that you call it after the class file has been loaded, and after any classes that it will depend on have been loaded, but before you actually need any of the properties or methods.
My solution removes all of the is_first_use() function calls and calls plugin_options::init() once, and only once.
Each class exists in its own file. For my plugin, it exists here:
I use the PHP directive require() or require_once() to load the class.
I put the call:
…at the bottom of the class file, outside the final closing bracket. When PHP loads the file, first it creates the class in its memory, and then it calls the init() function.
My code from the example above becomes….
<?PHP
class plugin_options {
private static $class_initialized = false;
private $options = Array();
private $default_options;
private $class_vars;
public $expert_mode;
public $consolidate_styles;
public $consolidate_scripts;
public static function init() {
if ( ! $class_initialized ) {
self::initialize_class();
$class_initialized = true;
}
}
private static function initialize_class() {
self::$class_vars = get_class_vars(__CLASS__);
unset(self::$class_vars['options']);
unset(self::$class_vars['default_options']);
unset(self::$class_vars['options']);
// Get the meta data from the wp_options table.
self::$options =
\get_option('pas_aib_options', self::$default_options);
foreach (self::$options as $key => $value) {
if (array_key_exists($key, self::$class_vars)) {
self::$$key = $value;
}
}
}
public static function get_option($option, $default = null) {
if ($default == null) {
$default =
(array_key_exists($option, self::$default_options) ?
self::$default_options[$option] :
null);
}
return
(array_key_exists($option, self::$options) ?
self::$options[$option] :
$default);
}
public static function update_option($option, $value) {
self::$options[$option] = $value;
if (array_key_exists($option, self::$class_vars)) {
self::$$option = $value;
}
// write the changed meta data back to the wp_options table.
\update_option('pas_aib_options', self::$options);
}
}
plugin_options::init();
…note that the calls to is_first_use() are gone. The
has been replaced with
and the initialize_class() function is still private. There is a call to the plugin_options::init() method outside the class definition, at the very bottom of the class file.
The call to plugin_options::init() initializes the plugin immediately after PHP loads the file. I never need to worry about whether a statically called class has been initialized or not. Additionally, I don’t have to worry about where in the code I stuck that call. It’s always right there at the bottom of the class file. The init() function checks the statically defined, private, $class_initialized property. If it’s false, then init() calls the private method initialize_class() and then resets the $class_initialized = true, which protects the class from being reinitialized.
It seems simple enough. I wish I hadn’t had to go through all of the other workarounds first. But this solves all of my problems with regard to no static constructor for PHP classes.
In my examples, you will find the occasional use of a back slash ( ‘\’ ) character preceding a few built-in function calls. This is intentional. I use namespaces in my plugins. Using a naked backslash ensures that I am calling the built-in function, not a same-named function from one of my classes.
Two examples come to mind. 1) I use get_option() in my plugin_options() class that retrieves a plugin option, rather than retrieving a meta option from the wp_options table. 2) I have an error_log() function that is global throughout my plugin, but not throughout WordPress. My error_log() function, when the appropriate option is set, writes out the file and line number where an error_log() call is made. This lets me easily find and remove any leftover error_log() calls before releasing a plugin to the public.
© 2020, PaulSwarthout. All rights reserved.