Back-end
13 minute read

How to Approach Modern WordPress Development (Part 2)

Andrey is a full-stack web developer and WordPress expert. Lately, his focus is on serverless computing and JAMstack.

WordPress is the most widely used site technology on the planet and with good reason. Yet legacy code in its core is a mess, and this problem cascades to third-party developers. Some developers take this as an excuse to cut corners in their own WordPress PHP code, but this approach is more expensive in the long run for all but the most trivial changes.

In Part 1 of our two-part series, we focused on overall project and workflow tools, followed by front-end development. Now it’s time to take the bull by its horns, and wrestle with PHP: Specifically, how to follow best practices when working with back-end WordPress code. You might think of this as a PHP/WordPress tutorial, but a bit more advanced, with the assumption that you’ve already done some WordPress back-end customization.

So, which modern software design principles and PHP features and will give you the best value for your time? Following are 10 WordPress and PHP development practices that I highly recommend.

Modern WordPress Development Best Practice #1: Follow “Separation of Concerns”

Separation of concerns means that parts of WordPress PHP code having different functionality or purposes shouldn’t be mixed together. Instead, they should be organized into distinct sections or modules, passing data to each other through a defined interface. (An interface is a set of defined parameters that a module takes as input, and what it outputs back.) A closely related term is the single responsibility principle: Every code module (or function) should be responsible for one thing only.

The ultimate goal of following these principles is producing code that is modular, and thus maintainable, extensible, and reusable.

That was quite a mouthful, so let’s look at an example of some WordPress PHP (from WordPress core) that gets everything tangled together. This style of coding is often called “spaghetti code” because understanding its inner workings is almost impossible. The excerpt below was redacted for brevity; however, the original style and formatting were preserved.

$id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0;
<table class="form-table">
    <?php
    $blog_prefix = $wpdb->get_blog_prefix( $id );
    $sql         = "SELECT * FROM {$blog_prefix}options
        WHERE option_name NOT LIKE %s
        AND option_name NOT LIKE %s";
    $query       = $wpdb->prepare(
        $sql,
        $wpdb->esc_like( '_' ) . '%',
        '%' . $wpdb->esc_like( 'user_roles' )
    );
    $options     = $wpdb->get_results( $query );
    foreach ( $options as $option ) {
        if ( strpos( $option->option_value, "\n" ) === false ) {
            ?>
            <tr class="form-field">
                <th scope="row"><label for="<?php echo esc_attr( $option->option_name ); ?>"><?php echo esc_html( ucwords( str_replace( '_', ' ', $option->option_name ) ) ); ?></label></th>
                <?php if ( $is_main_site && in_array( $option->option_name, array( 'siteurl', 'home' ) ) ) { ?>
                <td><code><?php echo esc_html( $option->option_value ); ?></code></td>
                <?php } else { ?>
                <td><input class="<?php echo $class; ?>" name="option[<?php echo esc_attr( $option->option_name ); ?>]" type="text" id="<?php echo esc_attr( $option->option_name ); ?>" value="<?php echo esc_attr( $option->option_value ); ?>" size="40" <?php disabled( $disabled ); ?> /></td>
                <?php } ?>
            </tr>
            <?php
            }
    } // End foreach
</table>

First of all, it’s totally incomprehensible. And I love that the only comment is End foreach, which is completely redundant. We have database querying, query results processing, additional processing embedded in HTML (there is an if/else nested there if you hadn’t noticed), output escaping, and HTML templating all mashed together. Another problem is the $id parameter coming straight from the global $_REQUEST as opposed to passing an actual parameter to a function.

Looking at this, it’s perfectly understandable why WordPress core has stayed mostly the same for years. Refactoring this kind of code—especially while maintaining existing behavior—is a truly epic task that no one would want to do.

So how do we do it properly? Well, one thing to remember is that there is no one true way. We mentioned above qualities that we should strive for: We need WordPress custom PHP code to be maintainable and modular. Let’s look at how we can split the code above into modules.

  • SQL queries should be in a separate module, obviously. WordPress already has a nicely abstracted WP_Query class that should be used as an example.
  • All HTML goes into a template. We’ll cover PHP templating further below.
  • The remaining PHP code should be wrapped in a function—several functions if the code is too long or complex for one function. Parameters such as $id are passed via function arguments.

This is a greatly simplified rewrite of the example above:

function betterSiteSettings($args)
{
    $data = WP_Settings_Query($args);
    // process $data here
    $context = array_merge([], $data_processed, $other_data);
    return Template::render('template.name', $context);
}

Modern WordPress Development Best Practice #2: Avoid Global Variables

WordPress has way too many global variables. Why are global variables bad? They make your WordPress PHP code hard to follow and make application state unreliable. Any piece of PHP code—and that means any plugin that is installed in WordPress—can read and write global variables, so there is no guarantee that they contain valid data. Trying to understand what global variables are used in something like the Loop is not a trivial task either.

Let’s look at this from a practical angle. This example comes from WooCommerce. Probably every WordPress developer knows what it is—the Loop:

<?php while ( have_posts() ) : the_post(); ?>
<?php wc_get_template_part( 'content', 'single-product' ); ?>
<?php endwhile; // end of the loop. ?>

The snippet above renders a product template. How does it know what product to show, given that there are no parameters passed to wc_get_template_part? Looking at the template, we see that it starts with global $product;, so that’s where the current product object is stored.

Now imagine we have a catalog page that searches and filters products, and we want to show a “product details” popup while staying on the same page. Behind the scenes, the front-end script does an AJAX request to get that particular product template. We can’t simply call wc_get_template_part('content', 'single-product') because it doesn’t use parameters, so we need to set a couple of global variables for this to work.

More complex use cases would involve more than one template, hooks triggered in those templates, and third-party plugins adding their callbacks to those hooks. It can escalate quickly. We have no way of knowing what global state those callbacks rely upon. Third-party plugins are free to modify any global variable in their callbacks. Instead of using the system, we start fighting with the system, encountering weird bugs that come from unreliable global state.

Wouldn’t it be more sensible to pass that product ID as a parameter? Then we could reuse that template without worrying about messing up global variables used by WordPress.

Modern WordPress Development Best Practice #3: Use Object-oriented Programming (OOP)

Modularity leads to the concept of objects and object-oriented programming. At the very basic level, OOP is a way of organizing code. Functions and variables are bundled together into classes and are called class methods and properties respectively. The WordPress Plugin Handbook recommends using OOP for organizing your WordPress custom PHP code.

An important principle in OOP is limiting access to methods and properties—or in PHP terms, denoting them as private or protected—so only other class methods can access and change them. An OOP term for this is encapsulation: Data is encapsulated inside the class, and the only way to change that data is by using provided class methods.

This makes debugging and maintaining your code much easier than it is when using global variables that can be modified anywhere in the whole codebase. Consider the global WordPress post variable. You can access it anywhere in your code, and lots of functionality depends on using it. What if you could restrict modifications to only WordPress core functions, but reading would be allowed for anyone? Hiding or encapsulating the global post variable in a class and building an interface around it would make this possible.

This is only a very basic description of OOP and how it can be used in modern WordPress development. For further study, I thoroughly recommend Carl Alexander’s e-book, Discover object-oriented programming using WordPress, which has the most comprehensive and useful content available on the topic of OOP in WordPress.

It’s important to remember that OOP is not a silver bullet: Bad code can be written with OOP as easily as with any other programming paradigm.


Let’s dive into some specific advice about using PHP for WordPress development.

Modern PHP Best Practice #1: Target PHP 7.0+

Using modern PHP features requires, well, a modern version of PHP. There is simply no reason for supporting PHP versions lower than 7.0. Even WordPress core will require PHP 7.0 as early as the end of 2019.

Nevertheless, it’s a good practice to check for your minimum version to avoid the “white screen of death” in incompatible environments. The snippet below shows using a plugin header to declare a minimum PHP version with a guard condition in the code.

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Requires PHP: 7.0
 */

// bails if PHP version is lower than required
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
    // add admin notice here
    return;
}

// the rest of the actual plugin here

Modern PHP Best Practice #2: Adopt PHP Industry Standards (PSR-2 Coding Style Guide)

PSRs are recommendations published by the PHP Framework Interop Group. They are the de-facto industry standards in any modern PHP workflow, and it can be safely said that PHP community as a whole follows these standards. PSR-2 is a recommendation that describes coding style. Popular PHP frameworks such as Symfony and Laravel follow PSR-2.

Why would you use PSR-2 instead of the WordPress coding standard? Mainly because WordPress standards are obsolete and don’t use any of the newer language features. That’s understandable because WordPress core has to follow its own standards. It had to support PHP 5.2 until very recently, and PSR-2 is not compatible with PHP 5.2.

It may not be obvious, but there is no requirement to use WordPress coding standards unless you are committing to the core. There would be no problem with submitting a plugin that follows the PSR-2 standard to the WordPress plugin directory. In fact, there are some very good arguments for doing so.

Modern PHP Best Practice #3: Use a PHP Template Engine

PHP is not a template engine. It started as one, but then evolved into a fully-featured programming language, and there is no reason to keep using it for templating. The two most popular template engines for PHP are Twig and Blade, which are used by Symfony and Laravel, respectively. This article is going to use Twig as an example templating engine; however, Blade has comparable features and functionality. I implore you to look into both and decide for yourself which suits you best.

The example below compares a PHP template and its corresponding Twig template. Displaying and escaping output is particularly verbose in the PHP example:

foreach ( $options as $option ) {
    ?>
    <tr class="form-field">
        <th scope="row">
            <label for="<?php echo esc_attr( $option->option_name ); ?>">
                <?php echo esc_html( strtolower( $option->option_name ) ); ?>
            </label>
        </th>
    </tr>
    <?php
} // End foreach

In Twig, this is more concise and readable:

{% for option in options %}
    <tr class="form-field">
        <th scope="row">
            <label for="{{ option.option_name }}">
                {{ option.option_name }}
            </label>
        </th>
    </tr>
{% endfor %}

The main advantages of Twig are:

  • Readable and concise syntax
  • Automatic output escaping
  • Template extension via inheritance and blocks

Performance-wise, Twig compiles to PHP templates and has virtually no overhead. Twig has only a subset of PHP language constructs limited to templating only. This forces developers to remove business logic from templates, thus enforcing separation of concerns.

There even exists Twig for WordPress. It’s called Timber, and it’s a great way to get started with creating better templates. The Timber starter theme is a perfect example of organizing themes the OOP way.

Modern PHP Best Practice #4: Use Composer

Composer is a dependency manager for PHP. It’s a tool that allows declaring libraries that a project is using, and then it automating their downloads, installations, and updates. Then you just need to include Composer’s autoload file vendor/autoload.php instead of manually requiring each library.

WordPress plugins and themes don’t often use any third-party libraries. This is partly because WordPress has an extensive API that fills almost any need, and partly because of possible version conflicts. Consider two plugins requiring the same PHP library but different versions of it. The plugin that runs first gets the correct version and the second plugin gets that version also. This is quite possibly another white-screen-of-death situation.

To avoid conflicts, dependency management should be used at the application level, i.e., the WordPress site as a whole. This is what Roots (Bedrock, more specifically) does. When used at the package (plugin or theme) level, Composer may cause conflicts when using third-party libraries. It’s a known issue. The only solution that exists so far is renaming that external library’s namespaces to something unique, and that’s not a trivial task.

There is still a use for Composer, though: the autoloading of your own classes. But before we go further with autoloading, we need to get up to speed with PHP namespaces.

Modern PHP Best Practice #5: Use Namespaces

WordPress core being a legacy project, it uses a global namespace or, said differently, no namespace at all. Any classes or functions declared globally (meaning not inside another class or function) are visible anywhere in the whole codebase. Their names must be unique not only within your codebase but for all plugins and themes that are used now or may be used in the future.

Naming collision (e.g., declaring a function with a name that already exists) usually leads to the white screen of death, and we don’t want that. The WordPress Codex advises prefixing all your functions and classes with something unique. So instead of having simple class names like Order, we get something like Akrte_Awesome_Plugin_Order where “Akrte” is the unique prefix that I just made up.

Namespaces can be viewed as groups—or folders, if we use a filesystem analogy—that help to organize code and avoid name collision. You can have complex namespaces that are separated with a slash, just like nested folders. (PHP namespaces use a backslash in particular.)

Those namespace parts are called sub-namespaces. Our example class Akrte_Awesome_Plugin_Order would be Akrte\Awesome_Plugin\Order if done using namespaces. Here Akrte and Awesome_Plugin are namespace parts (or sub-namespaces) and Order is the class name. Then you can add a use statement and use only the class name afterward. It definitely looks better:

use Akrte\Awesome_Plugin\Order;

$a = new Order;

Obviously, namespaces should be unique; thus, we should give the first “root” sub-namespace a unique name, and that is usually a vendor name. As an example, the WooCommerce class WC_REST_Order_Notes_V2_Controller could be re-done with namespaces like this:

namespace WooCommerce\RestApi\V2\Controllers;
class OrderNotes {}

The WooCommerce codebase does nowadays use namespaces; for example, in the WooCommerce REST API version 4.

Modern PHP Best Practice #6: Use an Autoloader

In most PHP workflows, the usual way of linking PHP files together is by using require or include statements. As a project grows, you get dozens of require statements in your main plugin file. An autoloader automates including files and does so only as needed. Technically it’s a function that requires a file containing a class or a function when it is first encountered in the code. There is no need to add any require statements manually anymore.

Often there is also a significant performance gain since an autoloader only loads modules that are used in a particular request. Without an autoloader, your whole codebase is included even if a request uses only, say, 10 percent of your code.

An autoloader function needs to know in which files your classes and functions live. And there is a PHP-FIG standard, PSR-4, for that.

It says that a part of a namespace, the prefix, is designated to correspond to a base folder. The sub-namespaces that follow it correspond to folders inside the base folder. Finally, the class name corresponds to the file name. An example class Akrte\AwesomePlugin\Models\Order would need the folder structure below:

/awesome-plugin
    awesome-plugin.php
    /includes
        /Models
            Order.php

The namespace prefix Akrte\AwesomePlugin\ corresponds to the includes folder as specified in the autoloader configuration discussed below. Models sub-namespace has a corresponding Models folder, and the Order class is contained in Order.php.

Luckily, there is no need to implement an autoloader function yourself. Composer can create an autoloader for you:

  1. Install Composer
  2. Create a composer.json file in the root folder of your project. It should contain these lines:
{
    "name": "vendor-name/plugin-name",
    "require": {},
    "autoload": {
        "psr-4": {
            "Akrte\\AwesomePlugin\\": "includes/"
        }
    }
}
  1. Run composer install.
  2. Include vendor/autoload.php at the top of your main plugin PHP file like this:
    <?php
    /**
     * Plugin Name: My Awesome Plugin
     */

    defined('ABSPATH') || exit;

    require __DIR__ . '/vendor/autoload.php';

    // do stuff

Besides namespaces, WooCommerce’s latest codebase also uses a Composer autoloader.


With these PHP design principles covered, it’s time to tie all our PHP lessons back to WordPress back-end customization with one final recommendation.

Modern WordPress Development Best Practice #4: Consider Using the Roots Stack

Roots is the most comprehensive modern WordPress development workflow there is. However, I would say that it shouldn’t necessarily be used in every WordPress project, because:

  • Roots has to be used from the start. The most common reason, really. Refactoring an existing project would be too costly.
  • It’s opinionated. Good when you agree, bad when you don’t. You may prefer other ways of organizing your theme, for example. Opinionated projects also require time to learn “their way.”
  • Not everyone knows it. The developer that will come after you to maintain the site you built with the Roots stack may have no idea what it is and wonder what happened with WordPress folders. We should think about our fellow WordPress developers.

In general, you would want to fully understand all the benefits and drawbacks of any strongly opinionated project before committing to using it.

Modern PHP and Software Principles: Making WordPress Back-end Development Robust

It’s fairly obvious that there is no one true way of writing software. The concepts, such as separation of concerns, are decades old; however, what it means practically has always been contested. Take, for example, CSS. In the beginning, we inlined it as style in HTML, then we decided that separate CSS sheets is what separation of concerns is about.

Fast forward a decade: Present-day JavaScript apps use components as a separation-of-concerns implementation. Front-end developers gravitate towards CSS-in-JS, and that basically means inlining CSS in HTML again (well, it’s not that simple, but you get the idea). The circle is complete!

Best practices have always been about improving developer experience:

Programs must be written for people to read, and only incidentally for machines to execute.

Abelson & Sussman, Structure and Interpretation of Computer Programs

Some of the practices in this PHP WordPress tutorial are fast and easy to implement in your project. For example, autoloader: Do it one time per project, and just enjoy. On the other hand, new software architecture ideas need time, practice, and numerous iterations to get handy and comfortable with. The rewards are much greater, though. You will not only be more efficient at what you do but also enjoy what you do more. And regular enjoyment of the work you do for clients is perhaps the only way it can be sustainable.

Understanding the basics

Can you use PHP in WordPress?

WordPress is written using PHP as a programming language. You definitely can use PHP if you want to develop for WordPress, but you don't have to.

What is the use of PHP in WordPress?

PHP is the programming language that most of the WordPress codebase is written in. Other languages and technologies used include JavaScript, CSS, and HTML.

How do I edit PHP in WordPress?

PHP can be edited using any text editor since a PHP file is just a plain text file. However, the proper approach would be using specialized code editors and version controlling your codebase.

Should I learn PHP before WordPress?

WordPress is a CMS that is easy and user-friendly. You don’t need to learn coding at all in order to set up and run your own site using WordPress. Most people leave coding to professionals the same way you leave changing the oil in your car to mechanics.