You’re a WordPress developer. You might have a few plugins under your belt or a theme or two. You build WordPress sites for clients or just for yourself. You’ve heard of object-oriented programming, but, each time you look into it, it makes no sense!

You tell yourself object-oriented programming isn’t useful or worth the trouble. The goal of this article is to focus on explaining this value to you. At the end of this article, you should have a clear understanding of why you should learn it.

Managing your expectation

Let’s get this out of the way. You won’t be an object-oriented programmer at the end of this article. One of the problems with teaching object-oriented programming is the expectation for immediate payoff. You wouldn’t read an article about the benefits of exercise, go running once and expect to be in shape. Same thing here.

You check the PHP documentation or an article on the subject. You put your functions in a class for your plugins. After all, that’s what the WordPress documentation tells you. Did you feel that made things better? Did you understand why you were doing that? Probably not.

The truth is that it’s not obvious. That’s why a lot of people struggle with it. Even if there are a lot of resources available. On top of that, a lot of people think they’re using object-oriented programming when, in fact, they’re not. This adds to the confusion on the subject. It also makes it a lot harder for you to understand the value of it.

Let’s start with what you know

If I asked you to explain to me how you program right now, could you do it? You write code every day, but you might not be thinking about how you’re writing that code. If you can’t explain what you’re doing, how can you understand what object-oriented programming is?

So let’s start by explaining the coding style you’re using right now.

Coding style, you say? Yes. Object-oriented programming is a coding style and not just about using classes in your code. This is the very misconception that cause so many issues for you and others.

You’re coding in procedural style

Procedural programming is one of the many programming styles in existence. It belongs to the larger family of structured programming. Object-oriented programming also belongs to it. This is why the two share many similarities.

Structured programming is all about organizing your code in a way that makes it stronger and easier to understand. Both procedural programming and object-oriented programming share that same goal. Object-oriented programming just does it better when used properly.

Let’s go back to procedural programming. The idea behind it is simple. You organize your code in a series of ordered steps. Those steps could be a conditional using an if statement or a switch for example. That said, following those ordered steps is how you get your result.

This is how you solve your problems. This is critical to understand. When you write code to solve a problem, you write it as a series of specific steps. Those steps create your desired outcome or functionality.

This isn’t a bad way to program. Sometimes it’s the best solution for a problem. Themes are a place where procedural code makes a lot of sense. You’re going through specific steps rendering HTML.

Procedural programming in the wild

Let’s take a look at the following WordPress code. Your goal is to get a list of all the users who are authors registered after a specific date. You can see the steps taken to get that list.

/** * Get all the authors registered after the given date. * * @param string $date * @param integer $number * * @return array */ function get_authors_registered_after($date, $number = 5) { $results = array(); $authors = get_users('who=authors&orderby=registered&order=desc'); foreach ($authors as $author) { if ($author->user_registered > $date) { $results[] = $author; } if (count($results) >= $number) { break; } } return $results; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /** * Get all the authors registered after the given date. * * @param string $date * @param integer $number * * @return array */ function get_authors_registered_after ( $date , $number = 5 ) { $results = array ( ) ; $authors = get_users ( 'who=authors&orderby=registered&order=desc' ) ; foreach ( $authors as $author ) { if ( $author -> user_registered > $date ) { $results [ ] = $author ; } if ( count ( $results ) >= $number ) { break ; } } return $results ; }

get_users is a helper function around the WP_User_Query class. You could also just use the class to achieve the same outcome.

/** * Get all the authors registered after the given date. * * @param string $date * @param integer $number * * @return array */ function get_authors_registered_after($date, $number = 5) { $results = array(); $query = new WP_User_Query(array( 'who' => 'authors', 'orderby' => 'registered', 'order' => 'desc' )); foreach ($query->results as $author) { if ($author->user_registered > $date) { $results[] = $author; } if (count($results) >= $number) { break; } } return $results; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /** * Get all the authors registered after the given date. * * @param string $date * @param integer $number * * @return array */ function get_authors_registered_after ( $date , $number = 5 ) { $results = array ( ) ; $query = new WP_User_Query ( array ( 'who' = > 'authors' , 'orderby' = > 'registered' , 'order' = > 'desc' ) ) ; foreach ( $query -> results as $author ) { if ( $author -> user_registered > $date ) { $results [ ] = $author ; } if ( count ( $results ) >= $number ) { break ; } } return $results ; }

Both these examples use procedural programming. You achieve your desired outcome by using a set of ordered steps. WordPress is full of examples like these. That is because the WordPress APIs are there to hide the use of classes. It’s one of the strengths of WordPress, but it’s still procedural code.

Now, let’s take a look at this last example.

class Authors { /** * Get all the authors registered after the given date. * * @param string $date * @param integer $number * * @return array */ public function get_registered_after($date, $number = 5) { $results = array(); $query = new WP_User_Query(array( 'who' => 'authors', 'orderby' => 'registered', 'order' => 'desc' )); foreach ($query->results as $author) { if ($author->user_registered > $date) { $results[] = $author; } if (count($results) >= $number) { break; } } return $results; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Authors { /** * Get all the authors registered after the given date. * * @param string $date * @param integer $number * * @return array */ public function get_registered_after ( $date , $number = 5 ) { $results = array ( ) ; $query = new WP_User_Query ( array ( 'who' = > 'authors' , 'orderby' = > 'registered' , 'order' = > 'desc' ) ) ; foreach ( $query -> results as $author ) { if ( $author -> user_registered > $date ) { $results [ ] = $author ; } if ( count ( $results ) >= $number ) { break ; } } return $results ; } }

Wait this isn’t object-oriented programming? No, it’s not. Wrapping your code in a class doesn’t change how you solved the problem. You’re still going through the same steps to get your result.

Hitting your limits

Here is a common situation: you created a small plugin to solve a problem. Over time, your plugin grows more complex. Your users want more features, an admin page, options everywhere, etc.

You start experiencing growing pains. That’s because procedural programming has its limitations. Let’s talk about some of the problems you might run across.

Difficulty organizing your code

Some code, like your admin page(s), is easy to group together. Not all code is that way though. When you divide your code in steps, you might want to reuse a subset of it. You create a new function so that you can reuse it.

“Where do I put this function?”, you ask yourself.

You start by creating a separate PHP file to store them. When do you split that first file into smaller files? How do you name those files? There is an endless list of questions you might ask.

Structured programming makes you seek clarity in your code organization. If you only have functions with steps, it puts everything on the same footing. The distinctions between types of functions becomes arbitrary. This makes the organization of your code hard to manage.

If you work with a team, multiply that by 10.

Hard to reuse your code

Your code is nicely organized now. A client comes to you with a new project. You want to reuse some functionality from your plugin in that project.

How do you reuse dozens of functions acting together to solve a problem? You might just copy paste the code into the new project. Yet you now have that code in two places. Twice the bugs to fix and all that nasty stuff.

A common way to fix this is to create a plugin for that functionality. So you extract code from various files into a separate plugin, repeating the code organization exercise again.

You’re not done yet though. You need extra conditions for this new use case. As you add more logic, your steps get increasingly complex, harder to maintain. Your simple solution is no longer that simple.

Some problems are hard to break down in steps

It’s ok. You managed to break up your code into a plugin. You have functions with lots of conditions, but you have a solution. You use an $args array with defaults.

So now, your function is 100+ lines of code with dozens of conditions. Small changes have unintended consequences because the steps and conditions are complex.

“Boy is this complicated.”, you muse as you look at your function.

When there is a bug, you struggle to figure out what is going on. “What condition or group conditions caused the bug?”

As problems get more complex, you cannot break them down into steps like that. Well, you can do it, but it will bring you a lot of pain.

How object-oriented programming does things

As we said, you use procedural programming to solve problems using ordered steps. Object-oriented programming uses a class or classes working together to solve a problem. These solutions are called a software design patterns.

Another important element to object-oriented programming is the common features of those solutions. That said, these features deserves their own article to be explained properly. The example ahead will highlight some of them.

This brings us to the the usage of classes in object-oriented programming. It’s worth pointing out that their use is deceiving. Classes implement important features that define object-oriented programming, but they’re not mandatory. You can implement object-oriented solutions without ever using classes.

An example using the mediator pattern

The mediator pattern is an important software design pattern. You use it when you need a way for different classes to interact without being reliant of each other.

This reliance between classes is called coupling. Object-oriented programming is always striving to create less coupling between classes. The mediator pattern is one of the solutions to that problem.

The WordPress version

The mediator pattern is fundamental to WordPress. You might not be aware of it, but you use it all the time making plugins. It’s the hooks and filters system.

/** * Hooks a function or method to a specific filter action. */ function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { global $wp_filter, $merged_filters; $idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority); $wp_filter[$tag][$priority][$idx] = array('function' => $function_to_add, 'accepted_args' => $accepted_args); unset( $merged_filters[ $tag ] ); return true; } /** * Call the functions added to a filter hook. */ function apply_filters( $tag, $value ) { global $wp_filter, $merged_filters, $wp_current_filter; $args = array(); // Do 'all' actions first if ( isset($wp_filter['all']) ) { $wp_current_filter[] = $tag; $args = func_get_args(); _wp_call_all_hook($args); } if ( !isset($wp_filter[$tag]) ) { if ( isset($wp_filter['all']) ) array_pop($wp_current_filter); return $value; } if ( !isset($wp_filter['all']) ) $wp_current_filter[] = $tag; // Sort if ( !isset( $merged_filters[ $tag ] ) ) { ksort($wp_filter[$tag]); $merged_filters[ $tag ] = true; } reset( $wp_filter[ $tag ] ); if ( empty($args) ) $args = func_get_args(); do { foreach( (array) current($wp_filter[$tag]) as $the_ ) if ( !is_null($the_['function']) ){ $args[1] = $value; $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $the_['accepted_args'])); } } while ( next($wp_filter[$tag]) !== false ); array_pop( $wp_current_filter ); return $value; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 /** * Hooks a function or method to a specific filter action. */ function add_filter ( $tag , $function_to_add , $priority = 10 , $accepted_args = 1 ) { global $wp_filter , $merged_filters ; $idx = _wp_filter_build_unique_id ( $tag , $function_to_add , $priority ) ; $wp_filter [ $tag ] [ $priority ] [ $idx ] = array ( 'function' = > $function_to_add , 'accepted_args' = > $accepted_args ) ; unset ( $merged_filters [ $tag ] ) ; return true ; } /** * Call the functions added to a filter hook. */ function apply_filters ( $tag , $value ) { global $wp_filter , $merged_filters , $wp_current_filter ; $args = array ( ) ; // Do 'all' actions first if ( isset ( $wp_filter [ 'all' ] ) ) { $wp_current_filter [ ] = $tag ; $args = func_get_args ( ) ; _wp_call_all_hook ( $args ) ; } if ( ! isset ( $wp_filter [ $tag ] ) ) { if ( isset ( $wp_filter [ 'all' ] ) ) array_pop ( $wp_current_filter ) ; return $value ; } if ( ! isset ( $wp_filter [ 'all' ] ) ) $wp_current_filter [ ] = $tag ; // Sort if ( ! isset ( $merged_filters [ $tag ] ) ) { ksort ( $wp_filter [ $tag ] ) ; $merged_filters [ $tag ] = true ; } reset ( $wp_filter [ $tag ] ) ; if ( empty ( $args ) ) $args = func_get_args ( ) ; do { foreach ( ( array ) current ( $wp_filter [ $tag ] ) as $the_ ) if ( ! is_null ( $the_ [ 'function' ] ) ) { $args [ 1 ] = $value ; $value = call_user_func_array ( $the_ [ 'function' ] , array_slice ( $args , 1 , ( int ) $the_ [ 'accepted_args' ] ) ) ; } } while ( next ( $wp_filter [ $tag ] ) !== false ) ; array_pop ( $wp_current_filter ) ; return $value ; }

These are a small subset of the functions used by the hooks and filters system. They are, however, critical for understanding what object-oriented programming is about.

Why is this object-oriented programming

The first thing you might have noticed is that there are no objects used here. Even with no objects, this is still object-oriented programming. This goes back to those features mentioned earlier. The WordPress implementation has some of the features that an object-oriented solution should have.

It decouples plugin code from WordPress internal functions. The mantra “Don’t hack core” highlights the importance of decoupling. The hooks and filters system are the cornerstone of that idea.

The filter functions also maintain an internal list of the hooks using the $wp_filter global. An object-oriented solution always keeps internal information. This idea is part of a larger feature called encapsulation. Encapsulation helps keep your code modular and simple.

Weaknesses of the WordPress version

The critical weakness of the WordPress hook system is that it depends on a global variable. Your information is accessible to anyone who can tamper with it. It isn’t complete encapsulation.

The use of global variables limits the reuse of the code as well. You can copy-paste everything into another PHP project. That said, you can’t use the solution in a programming language that doesn’t use globals that way.

It’s not a universal solution to the problem.

What it could look like as a class

What if WordPress didn’t use globals to store hooks? What would a class implementing the hooks and filters look like?

class WP_Filters { protected $filters = array(); /** * Hooks a function or method to a specific filter action. * * @param string $tag * @param mixed $function * @param integer $priority * @param integer $accepted_args */ public function add($tag, $function, $priority = 10, $accepted_args = 1) { } /** * Call the functions added to a filter hook. * * @param string $tag * @param mixed $value */ public function apply($tag, $value) { } /** * Remove all of the hooks from a filter. * * @param string $tag */ public function clear($tag) { } /** * Removes a function from a specified filter hook. * * @param string $tag * @param mixed $function * @param integer $priority */ public function remove($tag, $function, $priority = 10) { } /** * Build Unique ID for storage and retrieval. * * @param string $tag * @param mixed $function * @param integer $priority */ protected function build_unique_id($tag, $function, $priority) { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class WP_Filters { protected $filters = array ( ) ; /** * Hooks a function or method to a specific filter action. * * @param string $tag * @param mixed $function * @param integer $priority * @param integer $accepted_args */ public function add ( $tag , $function , $priority = 10 , $accepted_args = 1 ) { } /** * Call the functions added to a filter hook. * * @param string $tag * @param mixed $value */ public function apply ( $tag , $value ) { } /** * Remove all of the hooks from a filter. * * @param string $tag */ public function clear ( $tag ) { } /** * Removes a function from a specified filter hook. * * @param string $tag * @param mixed $function * @param integer $priority */ public function remove ( $tag , $function , $priority = 10 ) { } /** * Build Unique ID for storage and retrieval. * * @param string $tag * @param mixed $function * @param integer $priority */ protected function build_unique_id ( $tag , $function , $priority ) { } }

The example is only the skeleton of the class itself. It means to highlight how a class can fix the vulnerabilities of the function version. Your hooks are now free of tampering because you store them internally in the object. That’s what the “protected” keyword does.

It’s also easy to reuse the class in your other projects. If you wanted your plugin to use its own separate hook system, you could do so. Although, there’s no reason to do it.

Object-oriented programming is about solving harder problems

While the example is simple, it has solved all the problems mentioned earlier. You managed to:

Organize your code around the problem you’re solving

Make your code easier to reuse

Solve a complex problem without writing messy code

And that’s really what it’s about. The reason why you should learn object-oriented programming. It’s not because it’s cool or everyone uses it.

It’s about the problems you’re trying to solve. Doing more complicated things without the headaches of using functions, moving ahead and not doing the same thing over and over again.

Notes: I’d like to give thanks to this article by Anthony Ferrara for some of my inspiration. It gives a brief, but enlightening overview of procedural vs object-oriented programming.