Note: This was originally a post to the Seattle PHP Meetup mailing list, but I’m reproducing it here to generate a discussion about the topic with people outside the meetup.
* * * *
I write a lot plugins for WordPress, and I like to make them object-oriented, even though WordPress itself is mostly procedural. I think it helps keep them clean and organized, and I also do it as a way to improve my programming skills. I’m having trouble understanding the correct way to implement OOP in this kind of procedural context, though. No matter what I try, it feels like I’m doing it wrong.
The first method I’ve tried is just create the kinds of mutable objects that I’m used to seeing in general PHP classes, and in other WordPress plugins. As an example, I’ll use a stripped down version of a class that creates a custom post type for destinations on a road trip.
class RoadTripDestination { public function __construct() { add_action( 'init', array( $this, 'createPostType' ) ); add_action( 'save_post', array( $this, 'savePost' ) ); } public function createPostType() { if( !post_type_exists( self::POST_TYPE ) ) { $postTypeParams = array( 'labels' => $labels, 'singular_label' => 'Road Trip Destination', 'rewrite' => array( 'slug' => 'road-trip-destination', 'with_front' => false ), 'supports' => array( 'title', 'editor', 'author', 'revisions' ) ); register_post_type( self::POST_TYPE_SLUG, apply_filters( self::PREFIX . 'post-type-params', $postTypeParams ) ); } } public function savePost( $postID ) { global $post; if( did_action( 'save_post' ) !== 1 ) return; if( isset( $_GET[ 'action' ] ) && ( $_GET[ 'action' ] == 'trash' || $_GET[ 'action' ] == 'untrash' ) ) return; if( !$post || $post->post_type != self::POST_TYPE || !current_user_can( 'edit_posts' ) ) return; if( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $post->post_status == 'auto-draft' ) return; $this->saveCustomFields( $postID, $_POST ); } protected function saveCustomFields( $postID, $newValues ) { update_post_meta( $postID, self::PREFIX . 'street-address', $newValues[ 'street-address' ] ); update_post_meta( $postID, self::PREFIX . 'city', $newValues[ 'city' ] ); update_post_meta( $postID, self::PREFIX . 'state', $newValues[ 'state' ] ); } } // end RoadTripDestination
It has methods to register the new post type with WordPress, and save it’s data when the post is created/updated. Since I’m hooking into an existing procedural application, I’m mostly just doing my domain logic and then calling procedural API functions to let WordPress do the heavy lifting.
To register the new post type with WordPress, there isn’t any kind of PostBase class that I can extend; I just call register_post_type() and pass in data that specifies the attributes of the new type.
Similarly, when saving data I don’t have an object representing a row in the database, so even though I have this concept of a “road trip destination”, I don’t have an object that I can assign properties to and then call $this->save(). Instead I just pass data to update_post_meta().
In a real world plugin, the majority of methods — like createPostType() in the example — aren’t really affecting the state of their object, they’re just interacting with the API, and I’d never want multiple instances of them, so it feels wrong to use them like objects.
The second method I’ve tried uses static methods extensively, almost exclusively. Here’s that same example from above with static methods:
class RoadTripDestination { public static function registerCallbacks() { add_action( 'init', __CLASS__ . '::createPostType' ); add_action( 'save_post', __CLASS__ . '::savePost' ); } public static function createPostType() { if( !post_type_exists( self::POST_TYPE ) ) { $postTypeParams = array( 'labels' => $labels, 'singular_label' => 'Road Trip Destination', 'rewrite' => array( 'slug' => 'road-trip-destination', 'with_front' => false ), 'supports' => array( 'title', 'editor', 'author', 'revisions' ) ); register_post_type( self::POST_TYPE_SLUG, apply_filters( self::PREFIX . 'post-type-params', $postTypeParams ) ); } } public static function savePost( $postID ) { global $post; if( did_action( 'save_post' ) !== 1 ) return; if( isset( $_GET[ 'action' ] ) && ( $_GET[ 'action' ] == 'trash' || $_GET[ 'action' ] == 'untrash' ) ) return; if( !$post || $post->post_type != self::POST_TYPE || !current_user_can( 'edit_posts' ) ) return; if( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || $post->post_status == 'auto-draft' ) return; self::saveCustomFields( $postID, $_POST ); } protected static function saveCustomFields( $postID, $newValues ) { update_post_meta( $postID, self::PREFIX . 'street-address', $newValues[ 'street-address' ] ); update_post_meta( $postID, self::PREFIX . 'city', $newValues[ 'city' ] ); update_post_meta( $postID, self::PREFIX . 'state', $newValues[ 'state' ] ); } } // end RoadTripDestination
(For an example of an entire plugin done this way, including a custom post type class, see my plugin skeleton on GitHub)
This feels more correct because I’m not instantiating an object that doesn’t really represent the concept of a road trip destination. Instead, the static methods just provide a stateless way of interacting with the API. That’s really all the non-static methods were doing, even though they written as stateful objects.
But, I have a nagging suspicious that this is Very Wrong. It kind of feels like using object-oriented syntax to write procedural functions.
So my question is, is it appropriate to use static methods in this way? If not, what would be the right way to do it? Is it futile to even try using OOP in a context like this, where the parent application is procedural? Or is this just the best approach possible given the inherint limitations of the situation?
Re: So my question is, is it appropriate to use static methods in this way?
I’m basically a java guy … when I got to the line ‘The second method I’ve tried uses static methods extensively, almost exclusively.’ … I thought ‘Why?’
In java you would HAVE to have static methods to call methods on un-instantiated objects …
but php isn’t compiled … it’s interpreted … so it’s quite pointless, verbose and confusing to declare static methods for an object that is already static-by-design … (make sense?) The general rule of thumb (at least in the java world) is to reserve static classes and methods for utility purposes … not instantiated objects … I.E. api objects … Math, Map, String, Regex … etc … utils … HTH
Hey Edward, is it possible you’re combining “static” with “persistent”? In the PHP world, we don’t expect that the former implies the latter. See http://stackoverflow.com/questions/4977162/php-static-not-so-static
My latest approach (https://github.com/iandunn/WordPress-Plugin-Skeleton) doesn’t use static methods nearly as often as I discussed in this post, but it does still use them when a method is not tied to an object’s state.
It seems to me that if a method can be static (i.e., without causing a fatal error by accessing stateful information), then it should be, because that properly classifies it. Defining it as an instance method when it has no need to act on a specific instance would be inaccurate, even if it is slightly less verbose.