Created: 2017.04.19
PHP's extract() function dynamically creates variables, making debugging harder.
extract()
creates a variable for each key in an associative array, checking that it is a valid variable name, and that it does not collide with any existing variables.
Some WordPress tutorials make use of extract()
, to minimise the code required to reference array items in HTML templating.
For example, in WordPress there is a WP_Widget
class. It has several methods including widget
, which is passed a standard array of $args
. These arguments allow users to customise the widget title, and the HTML that wraps the widget.
function widget( $args, $instance ) {
echo $args['before_widget'];
echo $args['before_title'] . $args['title'] . $args['after_title'];
echo "<p>My widget content.</p>";
echo $args['after_widget'];
}
function widget( $args, $instance ) {
extract( $args );
echo $before_widget;
echo $before_title . $title . $after_title;
echo "<p>My widget content.</p>";
echo after_widget;
}
An unwritten rule of development life is: laziness on your part usually results in extra work for someone else.
Here, the extracted variable names are much easier to write, but as they're not declared anywhere, it is very difficult to figure out where they came from:
function widget( $args, $instance ) {
extract( $args );
require('inc/views/front-end-widget.php');
}
<!--front-end-widget.php-->
echo $before_widget; // ??
echo $before_title . $title . $after_title; // ??
echo "<p>My widget content.</p>";
echo after_widget; // ??
Searching the codebase for $before_widget
won't show any results, because it was dynamically created. A developer just needs to know that WP_Widget::widget
is passed an array of $args - which always contains before_widget
.
extract()
is a terrible function that makes code harder to debug and harder to understand. We should discourage it’s [sic] use and remove all of our uses of it.
When looking at PHP code that I’m not familiar with a common task is back tracking what operations were done to a variable for it to end up in it’s current state. More often than not this means looking through dozens of lines of code in a function or method. First on my list is often figuring out how a variable came to be in the function in the first place. Was it a global, function argument, class variable or a return value from another function? Knowing this helps me determine if I need to start looking outside the function.
Then there are the times that I can’t find any reference to where a variable came from at all. It’s just suddenly being used. One way that happens is through the
extract
function. You pass it an array and it injects the array items into the current symbol table.
Predeclaring the variables that extract()
will dynamically create, provides a way to trace these back to the extract
function.
In addition, using EXTR_IF_EXISTS
will only overwrite the variables that we've created. This means that if we don't predeclare a variable, it won't be available in our HTML templating.
function widget( $args, $instance ) {
// predeclare the variables
$before_widget = $before_title = $title = $after_title = $after_widget = null;
// only overwrite the predeclared variables
extract($args, EXTR_IF_EXISTS);
require('inc/views/front-end-widget.php');
}
Adding a little extra code upfront, goes a long way towards keeping the code readable and maintainable.