Improving WordPress Code With Modern PHP

About The Author

Leonardo Losoviz is a freelance developer and writer, with an ongoing quest to integrate innovative paradigms (Serverless PHP, server-side components, GraphQL) … More about Leonardo ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

Due to backwards compatibility, WordPress hasn’t taken advantage of new PHP features released after PHP 5.2.4. Fortunately, WordPress will soon require PHP 5.6+ and even PHP 7.0+ not long after that. The recent release of Gutenberg could be a sign of the good times to come. In this article, Leonardo Losoviz makes a tour of the PHP features newly-available to WordPress, and attempts to suggest how these can be used to produce better software.

WordPress was born fifteen years ago, and because it has historically preserved backwards compatibility, newer versions of its code couldn’t make full use of the latest capabilities offered by the newer versions of PHP. While the latest version of PHP is 7.3.2, WordPress still offers support up to PHP 5.2.4.

But those days will soon be over! WordPress will be upgrading its minimum PHP version support, bumping up to PHP 5.6 in April 2019, and PHP 7 in December 2019 (if everything goes according to plan). We can then finally start using PHP’s imperative programming capabilities without fear of breaking our clients’ sites. Hurray!

Because WordPress’ fifteen years of functional code have influenced how developers have built with WordPress, our sites, themes and plugins may be littered with less-than-optimal code that can gladly receive an upgrade.

This article is composed of two parts:

  1. Most relevant new features
    Further features have been added to PHP versions 5.3, 5.4, 5.5, 5.6 and 7.0 (notice that there is no PHP 6) and we’ll explore the most relevant ones.
  2. Building better software
    We’ll take a closer look through these features and how they are able to help us build better software.

Let’s start by exploring PHP’s “new” features.

Classes, OOP, SOLID And Design Patterns

Classes and objects were added to PHP 5, so WordPress already makes use of these features, however, not very extensively or comprehensively: The paradigm of coding in WordPress is mostly functional programming (performing computations by calling functions devoid of application state) instead of object-oriented programming (OOP) (performing computations by manipulating objects’ state). Hence I also describe classes and objects and how to use them through OOP.

OOP is ideal for producing modular applications: Classes allow the creation of components, each of which can implement a specific functionality and interact with other components, and can provide customization through its encapsulated properties and inheritance, enabling a high degree of code reusability. As a consequence, the application is cheaper to test and maintain, since individual features can be isolated from the application and dealt with on their own; there is also a boost of productivity since the developer can use already-developed components and avoid reinventing the wheel for each application.

A class has properties and functions, which can be given visibility by usingprivate (accessible only from within the defining class), protected (accessible from within the defining class and its ancestor and inheriting classes) and public (accessible from everywhere). From within a function, we can access the class’ properties by prepending their names with $this->:

class Person {

  protected $name;

  public function __construct($name) {
    $this->name = $name;
  }

  public function getIntroduction() {
    return sprintf(
      __('My name is %s'),
      $this->name
    );
  }
}

A class is instantiated into an object through the new keyword, after which we can access its properties and functions through ->:

$person = new Person('Pedro');
echo $person->getIntroduction();
// This prints "My name is Pedro"

An inheriting class can override the public and protected functions from its ancestor classes, and access the ancestor functions by prepending them with parent:::

class WorkerPerson extends Person {

  protected $occupation;

  public function __construct($name, $occupation) {

    parent::__construct($name);
    $this->occupation = $occupation;
  }

  public function getIntroduction() {
    return sprintf(
      __('%s and my occupation is %s'),
      parent::getIntroduction(),
      $this->occupation
    );
  }
}

$worker = new WorkerPerson('Pedro', 'web development');
echo $worker->getIntroduction();
// This prints "My name is Pedro and my occupation is web development"

A method can be made abstract, meaning that it must be implemented by an inheriting class. A class containing an abstract method must be made abstract itself, meaning that it cannot instantiated; only the class implementing the abstract method can be instantiated:

abstract class Person {

  abstract public function getName();

  public function getIntroduction() {
    return sprintf(
      __('My name is %s'),
      $this->getName()
    );
  }
}
// Person cannot be instantiated

class Manuel extends Person {

  public function getName() {
    return 'Manuel';
  }
}

// Manuel can be instantiated
$manuel = new Manuel();

Classes can also define static methods and properties, which live under the class itself and not under an instantiation of the class as an object. These are accessed through self:: from within the class, and through the name of the class + :: from outside it:

class Factory {

  protected static $instances = [];
  public static function registerInstance($handle, $instance) {
    self::$instances[$handle] = $instance;
  }
  public static function getInstance($handle) {
    return self::$instances[$handle];
  }
}

$engine = Factory::getInstance('Engine');

To make the most out of OOP, we can use the SOLID principles to establish a sound yet easily customizable foundation for the application, and design patterns to solve specific problems in a tried-and-tested way. Design patterns are standardized and well documented, enabling developers to understand how different components in the application relate to each other, and provide a way to structure the application in an orderly fashion which helps avoid the use of global variables (such as global $wpdb) that pollute the global environment.

Namespaces

Namespaces were added to PHP 5.3, hence they are currently missing altogether from the WordPress core.

Namespaces allow organizing the codebase structurally to avoid conflicts when different items have the same name — in a fashion similar to operating system directories which allow to have different files with the same name as long as they are stored in different directories. Namespaces do the same encapsulation trick for PHP items (such as classes, traits, and interfaces) avoiding collisions when different items have the same name by placing them on different namespaces.

Namespaces are a must when interacting with third-party libraries since we can’t control how their items will be named, leading to potential collisions when using standard names such as “File”, “Logger” or “Uploader” for our items. Moreover, even within a single project, namespaces prevent class names from becoming extremely long as to avoid clashes with other classes, which could result in names such as “MyProject_Controller_FileUpload”.

Namespaces are defined using the keyword namespace (placed on the line immediately after the opening <?php) and can span several levels or subnamespaces (similar to having several subdirectories where placing a file), which are separated using a \:

<?php
namespace CoolSoft\ImageResizer\Controllers;

class ImageUpload {

}

To access the above class, we need to fully qualify its name including its namespace (and starting with \):

$imageUpload = new \CoolSoft\ImageResizer\Controllers\ImageUpload();

Or we can also import the class into the current context, after which we can reference the class by its name directly:

use CoolSoft\ImageResizer\Controllers\ImageUpload;
$imageUpload = new ImageUpload();

By naming namespaces following established conventions, we can get additional benefits. For instance, by following the PHP Standards Recommendation PSR-4, the application can use Composer’s autoloading mechanism for loading files, thus decreasing complexity and adding frictionless interoperability among dependencies. This convention establishes to include the vendor name (e.g. the company’s name) as the top subnamespace, optionally followed by the package name, and only then followed by an internal structure in which each subnamespace corresponds to a directory with the same name. The result maps 1 to 1 the physical location of the file in the drive with the namespace of the element defined in the file.

Traits

Traits were added to PHP 5.4, hence they are currently missing altogether from the WordPress core.

PHP supports single inheritance, so a subclass is derived from a single parent class, and not from multiple ones. Hence, classes that do not extend from one another can’t reuse code through class inheritance. Traits is a mechanism that enables horizontal composition of behavior, making it possible to reuse code among classes which live in different class hierarchies.

A trait is similar to a class, however, it can’t be instantiated on its own. Instead, the code defined inside a trait can be thought of as being “copied and pasted” into the composing class on compilation time.

A trait is defined using the trait keyword, after which it can be imported to any class through the use keyword. In the example below, two completely unrelated classes Person and Shop can reuse the same code through a trait Addressable:

trait Addressable {

  protected $address;

  public function getAddress() {
    return $this->address;
  }

  public function setAddress($address) {
    $this->address = $address;
  }
}

class Person {

  use Addressable;
}

class Shop {

  use Addressable;
}

$person = new Person('Juan Carlos');
$person->setAddress('Obelisco, Buenos Aires');

A class can also compose more than one trait:

trait Exportable {

  public class exportToCSV($filename) {
    // Iterate all properties and export them to a CSV file
  }
}

class Person {

  use Addressable, Exportable;
}

Traits can also be composed of other traits, define abstract methods, and offer a conflict resolution mechanism when two or more composed traits have the same function name, among other features.

Interfaces

Interfaces were added to PHP 5, so WordPress already makes use of this feature, however, extremely sparingly: the core includes less than ten interfaces in total!

Interfaces allow creating code which specifies which methods must be implemented, yet without having to define how these methods are actually implemented. They are useful for defining contracts among components, which leads to better modularity and maintainability of the application: A class implementing an interface can be a black box of code, and as long as the signatures of the functions in the interface do not change, the code can be upgraded at will without producing breaking changes, which can help prevent the accumulation of technical debt. In addition, they can help reduce vendor lock-in, by allowing to swap the implementation of some interface to that of a different vendor. As a consequence, it is imperative to code the application against interfaces instead of implementations (and defining which are the actual implementations through dependency injection).

Interfaces are defined using the interface keyword, and must list down just the signature of its methods (i.e. without having their contents defined), which must have visibility public (by default, adding no visibility keyword also makes it public):

interface FileStorage {

  function save($filename, $contents);
  function readContents($filename);
}

A class defines that it implements the interface through the implements keyword:

class LocalDriveFileStorage implements FileStorage {

  function save($filename, $contents) {
    // Implement logic
  }
  function readContents($filename) {
    // Implement logic
  }
}

A class can implement more than one interface, separating them with ,:

interface AWSService {
  function getRegion();
}

class S3FileStorage implements FileStorage, AWSService {

  function save($filename, $contents) {
    // Implement logic
  }
  function readContents($filename) {
    // Implement logic
  }
  function getRegion() {
    return 'us-east-1';
  }
}

Since an interface declares the intent of what a component is supposed to do, it is extremely important to name interfaces appropriately.

Closures

Closures were added to PHP 5.3, hence they are currently missing altogether from the WordPress core.

Closures is a mechanism for implementing anonymous functions, which helps declutter the global namespace from single-use (or seldom-used) functions. Technically speaking, closures are instances of class Closure, however, in practice, we can most likely be blissfully unaware of this fact without any harm.

Before closures, whenever passing a function as an argument to another function, we had to define the function in advance and pass its name as the argument:

function duplicate($price) {
  return $price*2;
}
$touristPrices = array_map('duplicate', $localPrices);

With closures, an anonymous (i.e. without a name) function can already be passed directly as a parameter:

$touristPrices = array_map(function($price) {
  return $price*2;
}, $localPrices);

Closures can import variables to its context through the use keyword:

$factor = 2;
$touristPrices = array_map(function($price) use($factor) {
  return $price*$factor;
}, $localPrices);

Generators

Generators were added to PHP 5.5, hence they are currently missing altogether from the WordPress core.

Generators provide an easy way to implement simple iterators. A generator allows to write code that uses foreach to iterate over a set of data without needing to build an array in memory. A generator function is the same as a normal function, except that instead of returning once, it can yield as many times as it needs to in order to provide the values to be iterated over.

function xrange($start, $limit, $step = 1) {
    for ($i = $start; $i <= $limit; $i += $step) {
        yield $i;
    }
}
foreach (xrange(1, 9, 2) as $number) {
    echo "$number ";
}
// This prints: 1 3 5 7 9

Argument And Return Type Declarations

Different argument type declarations were introduced in different versions of PHP: WordPress is already able to declare interfaces and arrays (which it does not: I barely found one instance of a function declaring an array as parameter in core, and no interfaces), and will soon be able to declare callables (added in PHP 5.4) and scalar types: bool, float, int and string (added in PHP 7.0). Return type declarations were added to PHP 7.0.

Argument type declarations allow functions to declare of what specific type must an argument be. The validation is executed at call time, throwing an exception if the type of the argument is not the declared one. Return type declarations are the same concept, however, they specify the type of value that will be returned from the function. Type declarations are useful to make the intent of the function easier to understand and to avoid runtime errors from receiving or returning an unexpected type.

The argument type is declared before the argument variable name, and the return type is declared after the arguments, preceded by ::

function foo(boolean $bar): int {

}

Scalar argument type declarations have two options: coercive and strict. In coercive mode, if the wrong type is passed as a parameter, it will be converted to the right type. For example, a function that is given an integer for a parameter that expects a string will get a variable of type string. In strict mode, only a variable of the exact type of declaration will be accepted.

Coercive mode is the default. To enable strict mode, we must add a declare statement used with the strict_types declaration:

declare(strict_types=1);
function foo(boolean $bar) {

}

New Syntax And Operators

WordPress can already identify variable-length argument lists through function func_num_args. Starting from PHP 5.6, we can use the ... token to denote that the function accepts a variable number of arguments, and these arguments will be passed into the given variable as an array:

function sum(...$numbers) {
  $sum = 0;
  foreach ($numbers as $number) {
    $sum += $number;
  }
  return $sum;
}

Starting from PHP 5.6, constants can involve scalar expressions involving numeric and string literals instead of just static values, and also arrays:

const SUM = 37 + 2; // A scalar expression
const LETTERS = ['a', 'b', 'c']; // An array

Starting from PHP 7.0, arrays can also be defined using define:

define('LETTERS', ['a', 'b', 'c']);

PHP 7.0 added a couple of new operators: the Null coalescing operator (??) and the Spaceship operator (<=>).

The Null coalescing operator ?? is syntactic sugar for the common case of needing to use a ternary in conjunction with isset(). It returns its first operand if it exists and is not NULL; otherwise, it returns its second operand.

$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
// $username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

The Spaceship operator <=> is used for comparing two expressions, returning -1, 0 or 1 when the first operand is respectively less than, equal to, or greater than the second operand.

echo 1 <=> 2; // returns -1
echo 1 <=> 1; // returns 0
echo 2 <=> 1; // returns 1

These are the most important new features added to PHP spanning versions 5.3 to 7.0. The list of the additional new features, not listed in this article, can be obtained by browsing PHP’s documentation on migrating from version to version.

Next, we analyze how we can make the most out of all these new features, and from recent trends in web development, to produce better software.

PHP Standards Recommendations

The PHP Standards Recommendations was created by a group of PHP developers from popular frameworks and libraries, attempting to establish conventions so that different projects can be integrated more seamlessly and different teams can work better with each other. The recommendations are not static: existing recommendations may be deprecated and newer ones created to take their place, and new ones are released on an ongoing basis.

The current recommendations are the following:

GroupRecommendationDescription
Coding Styles
Standardized formatting reduces the cognitive friction when reading code from other authors
PSR-1Basic Coding Standard
PSR-2Coding Style Guide
Autoloading
Autoloaders remove the complexity of including files by mapping namespaces to file system paths
PSR-4Improved Autoloading
Interfaces
Interfaces simplify the sharing of code between projects by following expected contracts
PSR-3Logger Interface
PSR-6Caching Interface
PSR-11Container Interface
PSR-13Hypermedia Links
PSR-16Simple Cache
HTTP
Interoperable standards and interfaces to have an agnostic approach to handling HTTP requests and responses, both on client and server side
PSR-7HTTP Message Interfaces
PSR-15HTTP Handlers
PSR-17HTTP Factories
PSR-18HTTP Client

Think And Code In Components

Components make it possible to use the best features from a framework without being locked-in to the framework itself. For instance, Symfony has been released as a set of reusable PHP components that can be installed independently of the Symfony framework; Laravel, another PHP framework, makes use of several Symfony components, and released its own set of reusable components that can be used by any PHP project.

All of these components are published in Packagist, a repository of public PHP packages, and can be easily added to any PHP project through Composer, an extremely popular dependency manager for PHP.

WordPress should be part of such a virtuous development cycle. Unfortunately, the WordPress core itself is not built using components (as evidenced by the almost total absence of interfaces) and, moreover, it doesn’t even have the composer.json file required to enable installing WordPress through Composer. This is because the WordPress community hasn’t agreed whether WordPress is a site’s dependency (in which case installing it through Composer would be justified) or if it is the site itself (in which case Composer may not be the right tool for the job).

In my opinion, if we are to expect WordPress to stay relevant for the next fifteen years (at least WordPress as a backend CMS), then WordPress must be recognized as a site’s dependency and made available for installation through Composer. The reason is very simple: with barely a single command in the terminal, Composer enables to declare and install a project’s dependencies from the thousands of packages published in Packagist, making it possible to create extremely-powerful PHP applications in no time, and developers love working this way. If WordPress doesn’t adapt to this model, it may lose the support from the development community and fall into oblivion, as much as FTP fell out of favor after the introduction of Git-based deployments.

I would argue that the release of Gutenberg already demonstrates that WordPress is a site dependency and not the site itself: Gutenberg treats WordPress as a headless CMS, and can operate with other backend systems too, as Drupal Gutenberg exemplifies. Hence, Gutenberg makes it clear that the CMS powering a site can be swappable, hence it should be treated as a dependency. Moreover, Gutenberg itself is intended to be based on JavaScript components released through npm (as explained by core committer Adam Silverstein), so if the WordPress client is expected to manage its JavaScript packages through the npm package manager, then why not extend this logic to the backend in order to manage PHP dependencies through Composer?

Now the good news: There is no need to wait for this issue to be resolved since it is already possible to treat WordPress as a site’s dependency and install it through Composer. John P. Bloch has mirrored WordPress core in Git, added a composer.json file, and released it in Packagist, and Roots’ Bedrock provides a package to install WordPress with a customized folder structure with support for modern development tools and an enhanced security. And themes and plugins are covered too; as long as they have been listed on the WordPress theme and plugin directories, they are available under WordPress Packagist.

As a consequence, it is a sensible option to create WordPress code not thinking in terms of themes and plugins, but thinking in terms of components, making them available through Packagist to be used by any PHP project, and additionally packaged and released as themes and plugins for the specific use of WordPress. If the component needs to interact with WordPress APIs, then these APIs can be abstracted behind an interface which, if the need arises, can be implemented for other CMSs too.

Adding A Template Engine To Improve The View Layer

If we follow through the recommendation of thinking and coding in components, and treat WordPress as a site’s dependency other than the site itself, then our projects can break free from the boundaries imposed by WordPress and import ideas and tools taken from other frameworks.

Rendering HTML content on the server-side is a case in point, which is done through plain PHP templates. This view layer can be enhanced through template engines Twig (by Symfony) and Blade (by Laravel), which provide a very concise syntax and powerful features that give it an advantage over plain PHP templates. In particular, Gutenberg’s dynamic blocks can easily benefit from these template engines, since their process to render the block’s HTML on the server-side is decoupled from WordPress’ template hierarchy architecture.

Architect The Application For The General Use

Coding against interfaces, and thinking in terms of components, allows us to architect an application for general use and customize it for the specific use that we need to deliver, instead of coding just for the specific use for each project we have. Even though this approach is more costly in the short term (it involves extra work), it pays off in the long term when additional projects can be delivered with lower efforts from just customizing a general-use application.

For this approach to be effective, the following considerations must be taken into account:

Avoid Fixed Dependencies (As Much As Possible)

jQuery and Bootstrap (or Foundation, or <–insert your favorite library here–>) could’ve been considered must-haves a few years ago, however, they have been steadily losing ground against vanilla JS and newer native CSS features. Hence, a general-use project coded five years ago which depended on these libraries may not be suitable nowadays anymore. Hence, as a general rule of thumb, the lower the amount of fixed dependencies on third-party libraries, the more up-to-date it will prove to be for the long term.

Progressive Enhancement Of Functionalities

WordPress is a full-blown CMS system which includes user management, hence support for user management is included out of the box. However, not every WordPress site requires user management. Hence, our application should take this into account, and work optimally on each scenario: support user management whenever required, but do not load the corresponding assets whenever it is not. This approach can also work gradually: Say that a client requires to implement a Contact us form but has no budget, so we code it using a free plugin with limited features, and another client has the budget to buy the license from a commercial plugin offering better features. Then, we can code our functionality to default to a very basic functionality, and increasingly use the features from whichever is the most-capable plugin available in the system.

Continuous Code And Documentation Review

By periodically reviewing our previously-written code and its documentation, we can validate if it is either up-to-date concerning new conventions and technologies and, if it is not, take measures to upgrade it before the technical debt becomes too expensive to overcome and we need to code it all over again from scratch.

Recommended reading: Be Watchful: PHP And WordPress Functions That Can Make Your Site Insecure

Attempt To Minimize Problems But Be Prepared When They Happen

No software is ever 100% perfect: the bugs are always there, we just haven’t found them yet. As such, we need to make sure that, once the problems arise, they are easy to fix.

Make It Simple

Complex software cannot be maintained in the long term: Not just because other team members may not understand it, but also because the person who coded it may not understand his/her own complex code a few years down the road. So producing simple software must be a priority, more since only simple software can be correct and fast.

Failing On Compile Time Is Better Than On Runtime

If a piece of code can be validated against errors at either compile time or runtime time, then we should prioritize the compile time solution, so the error can arise and be dealt with in the development stage and before the application reaches production. For instance, both const and define are used for defining constants, however, whereas const is validated at compile time, define is validated at runtime. So, whenever possible, using const is preferable over define.

Following this recommendation, hooking WordPress functions contained in classes can be enhanced by passing the actual class as a parameter instead of a string with the class name. In the example below, if class Foo is renamed, whereas the second hook will produce a compilation error, the first hook will fail on runtime, hence the second hook is better:

class Foo {
  public static function bar() {

  }
}

add_action('init', ['Foo', 'bar']); // Not so good
add_action('init', [Foo::class, 'bar']); // Much better

For the same reason as above, we should avoid using global variables (such as global $wpdb): these not only pollute the global context and are not easy to track where they originate from, but also, if they get renamed, the error will be produced on runtime. As a solution, we can use a Dependency Injection Container to obtain an instance of the required object.

Dealing With Errors/Exceptions

We can create an architecture of Exception objects, so that the application can react appropriately according to each particular problem, to either recover from it whenever possible or show a helpful error message to the user whenever not, and in general to log the error for the admin to fix the problem. And always protect your users from the white screen of death: All uncaught Errors and Exceptions can be intercepted through function set_exception_handler to print a non-scary error message on screen.

Adopt Build Tools

Build tools can save a lot of time by automating tasks which are very tedious to execute manually. WordPress doesn’t offer integration with any specific build tool, so the task of incorporating these to the project will fall entirely on the developer.

There are different tools for accomplishing different purposes. For instance, there are build tools to execute tasks for compressing and resizing images, minifying JS and CSS files, and copying files to a directory for producing a release, such as Webpack, Grunt and Gulp; other tools help create the scaffolding of a project, which is helpful for producing the folder structure for our themes or plugins, such as Yeoman. Indeed, with so many tools around, browsing articles comparing the different available tools will help find the most suitable one for our needs.

In some cases, though, there are no build tools that can achieve exactly what our project needs, so we may need to code our own build tool as an extension to the project itself. For instance, I have done this to generate the service-worker.js file to add support for Service Workers in WordPress.

Conclusion

Due to its strong emphasis on keeping backwards compatibility, extended even up to PHP 5.2.4, WordPress has not been able to benefit from the latest features added to PHP, and this fact has made WordPress become a not-very-exciting platform to code for among many developers.

Fortunately, these gloomy days may soon be over, and WordPress may become a shiny and exciting platform to code for once again: The requirement of PHP 7.0+ starting in December 2019 will make plenty of PHP features available, enabling developers to produce more powerful and more performant software. In this article, we reviewed the most important newly-available PHP features and how to make the most out of them.

The recent release of Gutenberg could be a sign of the good times to come: even if Gutenberg itself has not been fully accepted by the community, at least it demonstrates a willingness to incorporate the latest technologies (such as React and Webpack) into the core. This turn of events makes me wonder: If the frontend can get such a makeover, why not extend it to the backend? Once WordPress requires at least PHP 7.0, the upgrade to modern tools and methodologies can accelerate: As much as npm became the JavaScript package manager of choice, why not making Composer become the official PHP dependency manager? If blocks are the new unit for building sites in the frontend, why not use PHP components as the unit for incorporating functionalities into the backend? And finally, if Gutenberg treats WordPress as a swappable backend CMS, why not already recognize that WordPress is a site dependency and not the site itself? I’ll leave these open questions for you to reflect upon and ponder about.

Further Reading

Smashing Editorial (rb, ra, il, mrn)