7 Tips to Avoid Spaghetti Code in WordPress (and PHP)

PHP is infamous to be a messy language. And WordPress runs on PHP which inherits the same problem. Let's learn how to avoid that

PHP is infamous to be a messy language in the developer world. That’s because it allows bad code to run.

And WordPress runs on PHP which inherits the same problem.

Let’s take a look at several tips to avoid that:

Tip #1: Group Up Filters & Actions

The snippet below is a common mistake I see in themes and plugins. You can’t see what this file does unless you scroll until the very end.

add_action( 'after_setup_theme', 'my_theme_supports' );
function my_theme_supports() {
  // ...
}

add_action( 'wp_enqueue_scripts', 'my_public_assets', 99 );
function my_public_assets() {
  // ...
}

add_action( 'widgets_init', 'my_register_sidebar' );
function my_register_sidebar() {
  // ...
}

This is fixed simply by grouping the add_action and add_filter in one place:

add_action( 'after_setup_theme', 'my_theme_supports' );
add_action( 'wp_enqueue_scripts', 'my_public_assets', 99 );
add_action( 'widgets_init', 'my_register_sidebar' );


function my_theme_supports() {
  // ...
}

function my_public_assets() {
  // ...
}

function my_register_sidebar() {
  // ...
}

Now you only need to read the top part to see everything that file does.

Tip #2: Use Variable for Long Expression

For example, you want to check if a visitor is using Internet Explorer. You might create a function like this:

function is_browser_ie() {
  // is IE10 or IE11
  return preg_match('~MSIE|Internet Explorer~i', $_SERVER['HTTP_USER_AGENT']) ||
    preg_match('~Trident/7.0(; Touch)?; rv:11.0~',$_SERVER['HTTP_USER_AGENT']);
}

There’s nothing wrong with that, but it’s clearer if you write it like this:

function is_browser_ie() {
  $is_ie10 = preg_match('~MSIE|Internet Explorer~i', $_SERVER['HTTP_USER_AGENT']);
  $is_ie11 = preg_match('~Trident/7.0(; Touch)?; rv:11.0~',$_SERVER['HTTP_USER_AGENT']);

  return $is_ie10 || $is_ie11;
}

The general idea is to use less inline comments. Because a lot of inline comments actually made your code less readable.

Tip #3: Conditional Return Early

Let’s say you want to register a new Gutenberg style. You must first check whether Gutenberg is enabled or not. So you might do this:

add_action( 'init', my_register_block_styles );

function my_register_block_styles() {
  // if Gutenberg is active
  if ( function_exists( 'register_block_type' ) ) {
    register_block_style( 'core/heading', [
      'name' => 'has-underline',
      'label' => 'Has Underline'
    ] );

    register_block_style( 'core/buttons', [
      'name' => 'transparent',
      'label' => 'Transparent'
    ] );
  }
}

Seems good? Yes, but there’s a better way:

add_action( 'init', my_register_block_styles );

function my_register_block_styles() {
  // if Gutenberg is not active
  if ( !function_exists( 'register_block_type' ) ) { return; }
  
  register_block_style( 'core/heading', [
    'name' => 'has-underline',
    'label' => 'Has Underline'
  ] );

  register_block_style( 'core/buttons', [
    'name' => 'transparent',
    'label' => 'Transparent'
  ] );
}

You break the function early when the condition is not met. This way you keep the indentation as low as possible, making it easier to read.

Tip #4: Split functions.php into Several Files

Your functions file can easily grow big. It is best if you split the related chunk of codes into its own file.

It’s up to you how you organize it. My personal preference is to create a folder named functions/ and split them likes this:

  • admin.php – Hooks that affect the admin panel.
  • filters.php – Hooks that affect the front-end site.
  • helpers.php – Reusable utility functions.
  • setup.php – Hooks that initialize the site. It includes: theme support, custom post type, enqueue assets, register widget.
  • timber.php – If you’re using Timber plugin. Contains the global variable and custom filter (Learn How »)
  • shop.php – If you’re using WooCommerce.

Then your functions.php only acts as a gateway:

$function_dir = __DIR__ . '/functions';

require_once $function_dir . '/helpers.php';
require_once $function_dir . '/setup.php';
require_once $function_dir . '/filters.php';

if( is_admin() ) {
  require_once $function_dir . '/admin.php';
} else {
  require_once $function_dir . '/timber.php';
}

if( class_exists('WooCommerce') ) {
  require_once $function_dir . '/shop.php';
}

Tip #5: Utilize WordPress Native Functions

I often see themes and plugins re-create a function that is already provided by WordPress. Most likely because they’re not aware of them.

So I recommend scanning through the list of functions here codex.wordpress.org/Function_Reference

One day when you need it, you will vaguely remember that there’s a function for that. Then you can find it with Google search.

Below are some useful native WP functions:

Tip #6: Define Available Arguments

If you create a function that accepts arguments, define the available one with wp_parse_args. This will also set the default value.

For example, take a look at the native get_posts() function:

function get_posts( $args = null ) {
  
  $parsed_args = wp_parse_args( $args, [
    'numberposts'      => 5,
    'category'         => 0,
    'orderby'          => 'date',
    'order'            => 'DESC',
    'include'          => [],
    'exclude'          => [],
    'meta_key'         => '',
    'meta_value'       => '',
    'post_type'        => 'post',
    'suppress_filters' => true,
  ] );

  // ...
}

Having all the possible arguments laid out like that will make it easy for another developer to know what to pass on. It’s also a good reminder for you.

Tip #7: Minimize the Use of Opening / Closing PHP Tag

This is the top cause of spaghetti code in WordPress. Mixing PHP with HTML usually leads to many <?php and ?> which never tidy.

One way to avoid this is to use a templating engine like the Timber plugin. But even without it, you can still minimize the use of PHP tags.

For example, you’re making a function to display Related Posts like this:

<?php
function output_related_posts() {
  global $post;

  // get categories
  $category = get_the_category( $post->ID );
  $category_ids = [];
  foreach( $category as $c ) {
    $category_ids[] = $c->term_id;
  }

  // get posts with same categories, randomly
  $posts = get_posts([
    'category' => $category_ids,
    'posts_per_page' => 3,
    'orderby' => 'rand',
    'post__not_in' => [$post->ID]
  ]);

  ?>
  <ul class="recent-posts">
  <?php foreach( $posts as $p ): ?>
    
    <li class="post-thumbnail">
      <a href="<?php echo get_permalink( $p ); ?>">
        <?php echo get_the_post_thumbnail( $p, 'thumbnail' ); ?>
        <h3><?php echo $p->post_title; ?></h3>
        <time><?php echo get_the_date( '', $p ); ?></time>
        <p><?php echo $p->post_excerpt; ?></p>
      </a>
    </li>

  <?php endforeach; ?>
  </ul>
  <?php
}

Yes, that code works. But there’s a better way of writing that:

<?php
function output_related_posts() {
  global $post;

  // get categories
  $category = get_the_category( $post->ID );
  $category_ids = [];
  foreach( $category as $c ) {
    $category_ids[] = $c->term_id;
  }

  // get posts with same categories, randomly
  $posts = get_posts([
    'category' => $category_ids,
    'posts_per_page' => 3,
    'orderby' => 'rand',
    'post__not_in' => [$post->ID]
  ]);

  $content = '';

  foreach( $posts as $p ) {
    $title = $p->post_title;
    $excerpt = $p->post_excerpt;
    $link = get_permalink( $p );
    $thumbnail = get_the_post_thumbnail( $p, 'thumbnail' );
    $time = get_the_date( '', $p );

    $content .= "<li class='post-thumbnail'>
      <a href='$link'>
        $thumbnail
        <h3> $title </h3>
        <time> $time </time>
        <p> $excerpt </p>
      </a>
    </li>";
  }

  echo "<ul class='related-posts'> $content </ul>";
}

By utilizing string concatenation, it looks more seamless and tidier than the first.


Conclusion

There are a lot of themes and plugins with messy codes out there. We can’t control them, but at the very least our code shouldn’t fall into the same pit hole.

After this, I suggest reading phptherightway.com. It has a complete collection of popular PHP coding standards.

Do you have a best practice that you want to share? Feel free to share it in the comment section below 🙂

Default image
Henner Setyono
A web developer who mainly build custom theme for WordPress since 2013. Young enough to never had to deal with using Table as layout.
Leave a Reply