Modern WordPress Plugin Development

WordPres Plugin install screen

Recently I needed to write a WordPress plugin. Having written a handful of plugins previously this time around I wanted to build something that;

  • Took a more modern structured approach (ie. not just throwing a bunch of functions in a single file)
  • Separates classes into "single responsibilities"
  • Uses Composer for loading dependencies and PSR-4 autoloading
  • Is easy to read & extend
  • Separates HTML from PHP

I'm not going to go into too much details of the inner workings of the plugin but concentrate on the elements that make for the more modern approach I was aiming for. This article assumes some understanding of PHP, Composer and WordPress plugin development.

I'm always learning especially when it comes to writing modern and clean PHP so I'm sure there are improvements that could be made. Having said that, I do feel that this plugin is some of my best work to date - though I've still got a long way to go. Yes I'm sure there are better solutions out there for getting something like this up and running but I wanted to challenge myself to see if I could roll out my own solution.

As this was a learning process I tweeted along as I built. You can view the Twitter thread here;

The Plugin's functionality #

The plugin needs to regularly pull event data from an API and store this using WP Transients. Based on the API data an administrator can then create posts (via a custom post type) which displays the events post type on the front end. There are two shortcodes for showing upcoming events and specific events. Additionally event dates are appended to the content.

Using Composer #

With Composer being the go to package manager for PHP the very first step was to composer init in the plugins route.

I wanted to use PSR-4 autoloading this would allow me to add classes with namespaces in the /src directory of the plugin this is enabled by adding the following to your composer.json file.

"autoload": {
"psr-4": {
"Mrd\\": "src/"
}
}

I use the following all the time when developing Laravel apps so these were setup as a dependencies.

  • Symfony VarDumper I use the dd() functionality offered by Symfony all the time so pulled this in as a dev dependency.
  • Laravel Collections This gives you a much nicer set of methods when working array data. To pull this in you can composer require illuminate/collections
  • Laravel Helpers Mainly for its string helpers. You could argue this is overkill for the times they are used with the Plugin but never the less they tend to come in handy composer require laravel/helpers
  • Carbon - More specific to the plugins needs as it deals heavily with dates. Carbon is a great extension of the PHPDate class to giving tons of options for manipulating dates & times.

As the plugin evolved I ended up adding Symfony's Dom Crawler & CSS Selectors. When you get the event data from the API there is no room related to an event only it's related room colour. So when an Event object is created it looks up the list of Rooms/Colours (that we create by crawling a page of HTML to grab a list of rooms and their related background colours which is inlined as a style) – it's dirty but it works 🤷‍♂️

$layout = $this->api->getLayout();

$crawler = new Crawler( $layout );

$rooms = $crawler->filter( '#RoomColorList' )->children();

$roomArray = $rooms->each( function ( $room ) {

$styles = $room->attr( 'style' );
$styleAsKeyValuePairs = array_column( array_chunk( preg_split( "/[:;]\s*/", $styles ), 2 ), 1, 0 );

return [
'name' => $room->text(),
'color' => Str::replace( '!important', '', $styleAsKeyValuePairs['background-color'] )
];
} );

return collect( $roomArray );
$this->room = Container::getInstance()->get( Rooms::class )->findByColour( $this->color )['name'];

Dependency Injection/Dependency Container #

Since starting my journey with Laravel one concept that I've really struggled with is service containers, dependency injection and where and when you should use interfaces. But having done more learning on this recently and feeling like I'm slowly starting to understand it, reaching for something similar with this plugin felt like it made perfect sense. Research pointed in the direction of PHP-DI but how do you get this working as part of a custom WordPress plugin?

My key reason for wanting to get this working is so when we first initialse our plugin we can grab the data from the API/cache and then create a single EventRepository within the container to query/manipulate the data without the need for creating multiple instances of the repository throughout the plugins code base.

From my initial research there isn't a ton of documentation/guides on getting something like this working with WordPress. This article was the biggest help in getting me up and running https://torquemag.io/2017/01/using-automatic-dependency-injection-wordpress-development.

Additionally on digging around I discovered that the WP migrate plugin from Delicious Brains makes uses of PHP-DI so a dive into their code base gave me further insights. Additionally there is also a demo over on PHP-DI's GitHub https://github.com/PHP-DI/demo/tree/master/app.

After a few hours of tinkering I had something up and running that works. I'm very much still in the early days of my learning with this so I'm sure there are improvements that could be made - who knows I could even being doing it all wrong – but it's all part of the learning process right?

Here's the Container.php class that builds up a container. Once created we can get/put items into the container to the be used throughout our codebase - pretty cool huh?

<?php

namespace Mrd;

use DI\ContainerBuilder;

final class Container {

protected $container;
protected static $instance;

protected function __construct() {
$builder = new ContainerBuilder();
$this->container = $builder->build();
}

public static function getInstance() {
if ( null == static::$instance ) {
static::$instance = new static();
}

return ( static::$instance )->container;
}

}

Separating HTML from PHP in WordPress Plugin #

One of my biggest frustrations with "standard" plugin and theme development is the mixing of PHP with HTML. So I pulled together the following View helper class that can be used anywhere in our plugin where we want to include a HTML view.

src/View/View.php

<?php

namespace Mrd\View;

class View {

private $output;
private $viewPath;
private $data;

public function __construct( $location, $data = null ) {

$this->viewPath = MRD_PATH. "views/{$location}.php";
$this->data = $data;

if ( ! file_exists( $this->viewPath ) ) {
return "<p>View <strong>'{$this->viewPath}'</strong> not found</p>";
}

if ( ! empty( $this->data ) ) {
extract( $this->data );
}

ob_start();
include( $this->viewPath );

$this->output = ob_get_clean();

return $this;

}

public static function render( $location, $data = null ) {

return new self( $location, $data );

}

public function echo() {
echo $this->output;
}

public function fetchOutput() {
return $this->output;
}


}

The following would echo "Richard Bell" wrapped within a h1 tag – assuming name.php exists in the views folder. If you wanted to return the view as a string you could call fetchOutput() instead of echo().

View::render('name',["Name"=>"Richard Bell"])->echo();

views/name.php

<h1><?php echo $name; ?></h1>

Git setup & deployment via DeployHQ #

At this stage I don't plan to make the plugin public so I don't need to worry about SVN etc. That being said I wanted a way to build the plugin locally and then deploy a "cleaned" version to live. The development version will include my composer files and dev dependencies and at some point further down the line I'll want to add tests. There is no need for these to be pushed to the live server.

I did look into GitHub actions but I didn't fancy learning something new after all the learning that went into pulling together the new plugin so opted to use DeployHQ which made the process a breeze! It's a pretty simple; setup a server, exclude composer.json/composer.lock we setup composer install --no-progress as part of the build pipeline. I then develop on a development branch and merge into a master branch - when this is pushed to, DeployHQ automatically deploys the updated version of the plugin to the live site.

Plugin Structure #

So we end up with the following structure for our plugin once deployed;

  • src – Directory for any plugin classes that we want to autoload
    -- Container.php (see above)
    -- Plugin.php (see below)
    -- Views/View.php (see above)
  • views - Directory to include any view files can be structured in sub folders as required
  • vendor - Including all our dependencies
  • plugin.php (see below)

plugin.php #

<?php
/**
* MRD Plugin
* (See https://developer.wordpress.org/plugins/plugin-basics/header-requirements/ for more details of the header requirements)
*/


use Mrd\Container;
use Mrd\Plugin;

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

define( 'MRD_PATH', plugin_dir_path( __FILE__ ) );

$container = Container::getInstance();
$container->get( Plugin::class );

src/Plugin.php #

<?php

namespace Mrd\Plugin;


class Plugin {

public function __construct() {

add_action( 'init', [ $this, 'registerPostType' ] );

}

public function registerPostType() {
//Register custom post
}
}

Next steps & learning... Adding in tests, static analysis and adding typed properties & return types 🚀

Thank you for reading 🙏 If you have any suggestions/improvements or if you've found this useful I'd love to know. The best place to get me is Twitter @richard_bell. Similarly if you think I can help you build something within in WordPress let me know.