PHP Can (Still) Be The Worst At Object And Memory Management
More than 5 years after I first started delving into the PHP world thanks to client demands and our first foray into WordPress programming, one universal truth remains the same. PHP is still one of the worst programming languages I’ve worked with when it comes to both object and memory management. Yes, it has gotten a LOT better. PHP7 is much faster and generally a superior code and object wrangler than many of its predecessors.
But, 5 years later, even with PHP7 at work, it can be absolutely horrid with memory management. Case in point: today I wrapped up nearly TWO DAYS of debugging and profiling a WordPress plugin that has been running on tens-of-thousands of websites without any complaints of memory consumption for 4 years. But today, on my PHP7 development box, I re-discovered those old lingering issues with PHP. The culprit? Trying to access an array index for a named array that did not exist.
The short version is that $x = $y[ ‘z’] is throwing PHP into a tailspin if the ‘z’ index does not exist in $y. I’m not talking a warning about an undefined variable but an insatiable appetite for RAM. Memory consumption goes immediately from 3.5MB to 258MB and breaks the web server thanks to a PHP memory limitation. One statement and PHP bleeds memory all over the place.
The more complex form:
$value = $plugin->{$option_name}[ $setting ];
Here $plugin is a PHP object with a property named ‘options’. $plugin->options is a named array already in place. The problem is that $setting holds the string for an element of the array. When parsed it should read something like $plugin->options[ ‘tag_description’] but $setting is not setup properly due to a typo. While $plugin->options[‘tag_description’] keeps things humming along smoothly, using $plugin->options[‘t_tag-description’] causes PHP to have a fit.
To keep errant typos from completely breaking the application and instead manifest itself in more obscure “why the heck is that setting wrong everywhere” code-hunts you need to prefix things like this; which is ridiculous considering how PHP handles this situation 99.99% of the time:
$value = isset( $plugin->{$option_name}[ $setting ] ) ? $plugin->{$option_name}[ $setting ] : null;
To make things even more interesting you cannot do a simple empty() test if you are doing “this” when the value is empty OR the index key $setting does not exist and ‘that’ otherwise. If you have code like this things will crash with the same memory issue:
if ( empty( $plugin->{$option_name}[ $setting ] ) { $value = $this->get_a_new_value(); } else { $value = $plugin->{$option_name}[ $setting ]; }
If you inverse the logic you’d be OK due to the order of precedence of operations:
if ( ! empty( $plugin->{$option_name}[ $setting ] ) { $value = $plugin->{$option_name}[ $setting ]; } else { $value = $this->get_a_new_value(); }
And a note for my own benefit… this broke the SLP app until 4.6.5 worked around PHP’s limitation:
$this->Settings->add_ItemToGroup( array( 'group_params' => $this->group_params, 'option' => 'tagtag_dropdown_first_entry_label', 'label' => __( 'Tag Select All Text', 'slp-power' ), 'description' => __( 'What should the "any" tag say? ', 'slp-power' ) . __( 'The first entry on the search by tag pulldown.', 'slp-power' ), ) );
but this worked:
$this->Settings->add_ItemToGroup( array( 'group_params' => $this->group_params, 'option' => 'tag_dropdown_first_entry_label', 'label' => __( 'Tag Select All Text', 'slp-power' ), 'description' => __( 'What should the "any" tag say? ', 'slp-power' ) . __( 'The first entry on the search by tag pulldown.', 'slp-power' ), ) );
Seriously, PHP, WTF.
You mention you are using PHP7: try the calescing operator http://php.net/manual/en/migration70.new-features.php#migration70.new-features.null-coalesce-op
Anyway, the good approach would be to fix typos, not working around them and tolertae they exist (as I’m understanding from your words)…
That is a great one to know about. Unfortunately the code is a WordPress plugin that is not necessarily running on PHP7. I’ll remember that for our SaaS service though.
As an aside – the issue is not with typos it is with variable-assigned keys that may not exist in the array for a number of reasons. The fact that isset( ) prevents a memory leak that empty( ) does not is the real issue here. Especially since empty() is supposed to be running the isset() logic first. A complex environment many layers deep to be sure. As with any language, find the cracks and patch over them.