Background
I recently commented on Tom McFarlin's blog post entitled "Updating a WordPress Post in the Save Post Action" and suggested the use of a simple static variable rather than go through the hassle of removing and re-adding the save post hook. Tom's response was he hadn't thought of it but that he avoids recursion unless he absolutely needs it.
Now I hadn't focused on the recursive aspect of my solution because the goal in this case was to avoid recursion and not to use recursion to solve a problem, but indeed Tom was correct, it was using recursion (and I even labeled the controlling variable $recurse
; doh!) And that comment of mine resulted in several more comments back and forth with Tom, which lead me to this post.
And part of Tom's last comment included this paragraph:
The thing about recursion that I remember from my undergrad days is how it’s almost formulaic in terms of checking for the base case, recurse until the final condition is met, then set a flag to stop it. Generalized a bit, but you get the point, I’m sure :).
Ah, that brought back memories. They even used to explain recursion that way back in the 80's when I got my undergrad at Georgia Tech as well (Tom and I share the same alma mater.) I had forgotten that they taught us to think about recursion that way. But it's unfortunate that so many professors still teach recursion that way; as with many things in academics they focused on the mechanisms but not the use-cases where it would be helpful to apply.
Tom's comment made me think about recursion and best practices for PHP development in the WordPress ecosystem. Tom McFarlin is one of the best programmers who I know personally that specializes in WordPress (even though we often disagree about the minutiae!), and if he avoids recursion even when it's the easiest and most elegant solution than maybe many other WordPress developers do as well. So this being HardcoreWP, it became clear that I should write about it.
At this point if I'm using recursion I'm not even thinking about setting a flag to stop it, the flag just naturally gets set as part of implementing the logic for the use-case. Let me give you an example for something I'm working on.
A Use-case for Recursion
But let me explain the use case. I've been working on the WP_Metadata
Feature-as-a-Plugin which will allows (among other things) defining custom fields in code so that HTML inputs can be displayed in a post admin similar to the functionality for Advanced Custom Fields. In our work-in-progress code currently have a register_field_type()
that allows a developer to associate a string representing the field "type" with a class to implement it and it can be used to register standard field types like this:
-
register_field_type( 'text', 'WP_Text_Field' );
-
register_field_type( 'date', 'WP_Date_Field' );
register_field_type( 'text', 'WP_Text_Field' ); register_field_type( 'date', 'WP_Date_Field' );
As an aside plugin devs, themers and site builders could add their own types like this, assuming they are comfortable with the OOP API planned to adding new field types:
-
register_field_type( 'mysite_headshot', 'Mysite_Headshot_Field' );
register_field_type( 'mysite_headshot', 'Mysite_Headshot_Field' );
(WP_Text_Field/WP_Date_Field
and WP_Date_Field
are PHP classes defined in WP_Metadata
and a site builder's theme, respectively.)
Then the plugin dev, themer or site builder can add fields to post types using code like this (we're simplifying the parameters that we might pass to register_post_field()
to keep the example digestable):
-
register_post_type( 'mysite_person', array (
-
...
-
));
-
register_post_field( 'fullname', 'mysite_person', array(
-
'type' => 'text',
-
'label' => __( 'Full name', 'mysite' ),
-
));
-
register_post_field( 'birthday', 'mysite_person', array(
-
'type' => 'date',
-
'label' => __( 'Birthday', 'mysite' ),
-
));
-
register_post_field( 'photo', 'mysite_person', array(
-
'type' => 'headshot',
-
'label' => __( 'Photo (Headshot)', 'mysite' ),
-
));
register_post_type( 'mysite_person', array ( ... )); register_post_field( 'fullname', 'mysite_person', array( 'type' => 'text', 'label' => __( 'Full name', 'mysite' ), )); register_post_field( 'birthday', 'mysite_person', array( 'type' => 'date', 'label' => __( 'Birthday', 'mysite' ), )); register_post_field( 'photo', 'mysite_person', array( 'type' => 'headshot', 'label' => __( 'Photo (Headshot)', 'mysite' ), ));
However, wouldn't it be nice if we could allow site builders and themers who are not interested in learning OOP to compose their own field types for reusability? For example, what if they wanted to add an "SEO Title" field for all post types? We can do that by allowing use of register_field_type()
to associate an array of $args
with a type:
-
register_field_type( 'seo_title', array(
-
'type' => 'text',
-
'label' => __( 'SEO Title', 'mysite' ),
-
'size' => 60,
-
'placeholder' => __( 'Leave blank for auto-optimization', 'mysite' ),
-
));
register_field_type( 'seo_title', array( 'type' => 'text', 'label' => __( 'SEO Title', 'mysite' ), 'size' => 60, 'placeholder' => __( 'Leave blank for auto-optimization', 'mysite' ), ));
Then to add an 'seo_title'
field with a post type they would only need reference it like so:
-
register_post_field( 'seo_title', 'mysite_person', array(
-
'type' => 'seo_title',
-
));
register_post_field( 'seo_title', 'mysite_person', array( 'type' => 'seo_title', ));
The above would behave as if the arguments originally passed to register_field_type ()
to define the 'seo_title'
"type" were merged in everywhere the 'type'=>'seo_title'
is used to register a post field:
Recursion Applied
Now that we've set up the use-case, here is a version of wp_get_post_field()
that I simplified to illustrate how we can use recursion to achieve our use-case goal (it assumes wp_get_field_type()
exist that should hopefully be obvious by its name what its purpose is, and it is only a facsimile of the code we'll actually be using[^post_type] ):
-
function wp_get_post_field( $field_name, $post_type, $args ) {
-
if ( empty( $args['type'] ) ) {
-
$args['type'] = 'text';
-
}
-
$field_type = wp_get_field_type( $args['type'] );
-
if ( is_array( $field_type ) ) {
-
// If an array, merge with $args and call wp_get_post_field() again.
-
$args = WP_parse_args( $args, $field_type );
-
// RECURSION HERE
-
$field = wp_get_post_field( $field_name, $post_type, $args );
-
} else if ( class_exists( $field_type ) ) {
-
$args['post_type'] = $post_type;
-
$field = new $field_type( $field_name, $args );
-
}
-
return $field;
-
}
function wp_get_post_field( $field_name, $post_type, $args ) { if ( empty( $args['type'] ) ) { $args['type'] = 'text'; } $field_type = wp_get_field_type( $args['type'] ); if ( is_array( $field_type ) ) { // If an array, merge with $args and call wp_get_post_field() again. $args = WP_parse_args( $args, $field_type ); // RECURSION HERE $field = wp_get_post_field( $field_name, $post_type, $args ); } else if ( class_exists( $field_type ) ) { $args['post_type'] = $post_type; $field = new $field_type( $field_name, $args ); } return $field; }
The above uses one level of recursion and is very elegant compared to trying to do it without recursion. And yet when you implement it there isn't even a need to think about the "flag" because getting the obvious logic correct ensure that the flag is handled.
The Universe Wants You to Use Recursion
If I spend a day of new coding I typically use recursion at least once during that day. Sometimes recursion is used because a data-structure demands it, such as with Menus and Menu items, Pages and subpages (where $post_type == 'page'
), and hierarchical Taxonomies including Categories. Other times, however, recursion is simply the most elegant coding solution, as shown above.
Embrace the Circularity
So in summary, embrace the circularity, it could just become your new best friend!
[footnotes]
[^post_type]: The wp_get_post_field()
function shown above will not match what WP_Metadata
ships with when it is released. Minimally it will differ in that it will have an $object_type
parameter instead of a $post_type
parameter. I mention this in case someone reads this post after WP_Metadata
goes mainstream, and with different syntax. Assuming it does go mainstream.
[/footnotes]
Pingback: Embrace the Circularity! Recursion in PHP for WordPress | The WordPress C(h)ronicle
I’ve used a similar pattern of recursion for some plugins I’ve built. I honestly felt a little bit…dirty using it for some reason. I’m glad to see someone else using it; now I can feel OK with it in my own code (provided it’s justified). 🙂
Hi @Josh,
Thanks for the comment.
Not dirty at all. I was taught recursion back in my computer science courses in the 80’s. It was mind bending for me back then when I was new to recursion so I can see how some people might struggle with it.
But for many problem there is not better algorithm than recursion; use it and be proud!
-Mike