WordPress Plugin Development using Docker

In this tutorial we will create a docker environment for dev and then go through how to get started building a plugin.  I want the ability to let my readers submit article ideas and even vote on each other’s ideas so I have some good starting points for what you want to read about.  Unfortunately such a plugin exists, but only for older versions of WordPress (and for security purposes we should all be upgrading constantly, right?!). Let’s get started!

Dev Environment

Personally, I don’t like mixing projects or installing PHP directly on my dev system.  It’s just too costly in terms of the time wasted getting and keeping everything set up properly in the dev environment.  Brew on an Apple makes it the easiest to do that, so feel free…  but I’m going to show you how I actually do this in real life: Docker.

While I used Vagrant a lot in the past because of its ability to run any VM I want, I have found that Docker setup is a lot easier.  With Docker Compose I am able to make a simple file (similar to Vagrantfile) for generating my Docker environment.  The biggest plus is that Docker runs the operating system inside of my own host instead of requiring a dedicated VM environment.  In short, that means I can be running 100 docker servers on my machine and jump around in them (assuming it’s just me accessing them), whereas with Vagrant, I would have to turn them on and off because of the memory requirements alone.

If you don’t have docker, it’s pretty easy to get.  After a simple installation it is ready to go. Head over to store.docker.com and click on Docker CE (Community Edition) to download it for your platform.  You will need to create an account to download it, but they haven’t spammed me as of yet, and I don’t expect them to start in the future.  Follow their instructions to install for your platform.

I’m going to name my project InspireMe, so I’ll create a directory in my dev folder called inspire-me (the name really doesn’t matter).  Inside that folder we should create a docker-compose.yml file to keep the docker setup in.  This file will make it a breeze to start or stop our server environment using the Docker Compose tool.

A WordPress site needs two different servers to work – a MySQL server and a Web server, so we will need two docker containers (both defined inside this yaml file):

version: '3'


    image: mysql:5.7
    container_name: inspire-me-db
    restart: always
    - 33061:3306

    image: wordpress
    container_name: inspire-me-wp
    - ./:/var/www/html
    - 8081:80
    - inspire-me-db:mysql

In this file we are defining two services (or containers).  The first one comes from the official mysql release tagged for version 5.7.  So that everything looks nice we give it a container name, then make a port mapping so we can access it directly.  Outside the docker container, in my mac environment port 33061 will map to the port 3306 (mysql port) inside the container.  Lastly, we tell MySQL what the root password should be (you can make it anything you want – this is just a dev environment).

The second service is being made from the official WordPress release in the latest version. This container we want to map to our local filesystem, so we use the volumes configuration to map ./ in our filesystem (the directory the yaml file is in) to /var/www/html inside the container.  When mapping, it is important to know how the container image is set up, but most linux installations default to /var/www/html for service web pages with apache (like in this container).  I’ve mapped the internal port 80 to my local port 8081, so I will be able to access this WordPress installation at http://localhost:8081. Any local posts can be used – whatever is most convenient for you.

At this point, the docker-compose.yml file is the only one in the directory.  However, once we load the system setup it will put all the files from /var/www/html in the WordPress environment into this directory.  The first run will take a little while as your system downloads all the files, but once it’s been loaded the first time, subsequent loads will be super fast (since all the files and settings are already local).  Now it’s as simple as running docker-compose up -d in the console from the directory.  The -d flag tells docker compose to detach and run the containers in the background, giving you back your terminal.

Unloading this system is as simple as typing docker-compose down.  This is another thing I love about docker.  Vagrant always bugged me with their inconsistent use of terminology with loading and unloading… I love that it’s up and down in docker.

One other thing that can be super helpful and not always easy to find is how to get your logs.  Again, docker compose to the rescue.  See your logs as they currently stand (for all services in the yaml file) by running docker-compose logs.  If you add the -f flag, the the logs will stream to your terminal window as you go (you can exit by pressing control-c).  This can be extremely helpful when developing an app, and again, much easier than my ex-love, Vagrant!

Now that we have a docker environment, we just need to go through the basic installation steps at http://localhost:8081.  It’s as simple as telling WordPress your language and admin login credentials.  Now we are ready to code!

WordPress Plugins

If you are anything like me, as you code you will inadvertently leave off a semicolon or make some other small errors in your script from time to time as you go.  To get WordPress to let us see debugging info we need to put it in Debug mode.  This is done using a constant called WP_DEBUG.  It can be changed in the root directory’s wp-config.php file.  For my local setup after installation, it is found on line 80, near the bottom.  Just change the false to true and you are good to go. Under this line, you will also want to go ahead and tell WordPress to save all the DB queries made so you can easily sort through them.  This is done by adding define('SAVEQUERIES', true); to the script.

WordPress has a very specific folder structure. Plugins are created in the wp-content/plugins folder.  They can go their own folder and have some specific files that help WordPress know what they are (like defining the name of the plugin, etc.).  There is also a convention in WordPress to help end users with their security setup.  That is, there is always a simple three-line index.php file in every folder:

// Silence is golden.

I agree – it doesn’t really do much for security, but I suppose the intent is to help users with misconfigured servers not offer a list of files to users that go snooping around, which is certainly better than not doing it.

Now, in this directory (mine is called inspire-me-to-write) there is a php file bearing the same name (so in my case ./wp-content/plugins/inspire-me-to-write/inspire-me-to-write.php).  This file needs to have some very specific metadata at the top, which is how WordPress knows how to display this plugin to its users. Mine would look like this:

 * @package InspireMeToWrite
Plugin Name: Inspire Me to Write
Plugin URI: https://wpinspireme.com/
Description: <strong>Inspire Me to Write</strong> is a tool for web authors to allow their readers to easily ask and vote for future articles.
Version: 1.0.0
Author: John Fansler
Author URI: https://johnfansler.com/
License: GPLv2 or later
Text Domain: inspire-me-to-write

I think the fields here make pretty decent sense.  One thing to note is that you can choose any license you want, but if you wish to host it publicly on wordpress.org then you will need a GPL license – preferably “GPLv2 or later” according to wordpress.org.  Text Domain is a setting used if you plan to include translations with your plugin to make it available in other languages.

You can do anything with this file that you would otherwise do in PHP.  Other scripts can be included, subdirectories made, etc.  However, there are some specific WordPress functions that need to be called in order to do certain things like include links in menus, display widgets, allow the user to change settings, or get WP-specific data about the user, etc.

Going OOP

Object Oriented Programming is often missing in many plugins I have seen for WordPress.  However, I feel like it is of utmost importance especially in a system such as WordPress, which can have tons of plugins installed, to avoid namespace conflicts.  While WordPress does have some standards that help avoid conflicts (such as prefixing every function with your plugin name), entering into a Class with its own namespace makes doing anything substantial in PHP easier in the long run, takes less typing for calling individual functions, and is more maintainable.  Please, spend some time setting up at the beginning and create a system that is utilizing OOP for most stuff.

WordPress itself recommends a couple different boilerplates that can be forked from github.  If I had to choose one I’d go with https://github.com/DevinVinson/WordPress-Plugin-Boilerplate though. Starting there may well be a better way to begin a project if you are going to create many, but setting it up manually is good practice to really get a good understanding of how plugins work in the WordPress architecture.

To get into the habit of OOP, let’s start by making three folders in our plugin folder.  Most plugins will usually have something for the admin pages and something that is public.  Additionally you will usually have some other generic things that need to be accessed by both the admin and public sections.  (You can almost think of the admin and public sections as controllers, and the third section as models or helpers.) Let’s call them admin, public, and includes for consistency with the boilerplate above.  Again, any time you create a folder, add in the index.php file that does nothing (silence is golden, above).

Now we have a couple immediate things that need to be done first.  We need to be able to set up and tear down the data for the plugin (activate and deactivate it).  WordPress offers two hooks that define functions specifically for this: register_activation_hook  and register_deactivation_hook. These functions, like all the other WordPress API functions, are in the global scope and can be called from anywhere.

Depending on how invasive the plugin will be, you might be able to skip them altogether, but chances are there is something that will need to be set up.  If there is a lot to do, I would recommend making a Class in the includes folder for it, but if it is super tiny, a simple function at the top level will do.

Activation and deactivation are special hooks, but in reality there are many, many hooks in WordPress.  Hooks are divided into two categories: Actions and Filters.  Actions are used to run functions at specific points in the WordPress execution, whereas Filters are used to modify an input and return it (like if you wanted to do something like highlight a sentence in an article). At first glance into the code for add_action or add_filter you will notice that the same exact code is used – add_action actually just points the unchanged parameters to add_filter.  I believe this is probably because the same “hook” system is what is being used, but the WordPress developers wanted to help Plugin developers remember that these hooks call the function in very different ways.

There are many different hooks that functions can be attached to. As a plugin is being designed, it’s best to really study the list of hooks published and determine the best place to have your function execute.  There are two lists of hooks: one list for Actions and another for Filters.  Maybe you are going to include some CSS used by your program, which is best tied to wp_head or admin_head.  A simple add action call will do the trick: add_action('admin_head', 'my_function');.  If the function is inside of a Class, just swap ‘my_function’ for an array where the first item is the Class name, and the second is the static function:  add_action('admin_head', ['MyClass', 'my_function']);.  There are a couple more parameters for add_action that can be helpful in specific situations: the priority (defaults to 10 – lower numbers execute earlier than higher numbers), and lastly the number of arguments the function accepts, which defaults to 1.

Now that we know how to get our code to run in various places of WordPress execution, we probably need to have some access to the database.  WordPress provides a global $wpdb that is a Class based on ezSQL by Justin Vincent. This object already has an active connection to the database being used by WordPress, so no additional overhead is incurred if it is used directly.  The ins and outs of this Class could easily be a full article (and you can find many around the web), but here are the basics.

If you are familiar with writing queries directly and find comfort in that, then the get_results method will be your best friend.  Simply call $resultArray = $wpdb->get_results("SELECT ID, post_title, comment_count FROM $wpdb->posts ORDER BY comment_count"); to get an Array of Post objects that have the properties ID, post_title, and comment_count.  $wpdb->posts in the middle of the query references the name of the post table (usually wp_posts), but allows the query to work in environments with custom table prefixes.  You can also create tables and access your own data in the same way. There is also a query method, but it returns the number of rows/affected rows and not the result. It could be useful for creating a table or inserting static data.

In order to keep your code secure, it is recommended that all queries that contain any user-based data use prepared statements.  This can be done with the prepare method and follows a very familiar format: $resultArray = $wpdb->get_results($wpdb->prepare("SELECT post_title FROM $wpdb->posts WHERE ID = %d",5));.  Using the prepare method %d denotes an integer, %f a float, and %s a string.  A literal percent sign (%) should be entered as a double percent (%%), for example when formatting dates.  There is also a special formatter for using LIKE wildcards in a query.  The $wpdb->esc_like method can be used to pass in the full string including single % for wildcards as such: $resultArray = $wpdb->get_results($wpdb->prepare("SELECT ID, post_title FROM $wpdb->posts WHERE post_title LIKE %s",$wpdb->esc_like("%".$searchTerm."%")));.

There are also some helper methods in $wpdb that are there to make simple queries easier, such as the update, insert, and delete methods.  More info on these can be found in the WordPress wpdb class documentation.

At this point you have an excellent starting point for writing a Plugin.  Now I’m going to go write mine and hopefully you will see it on this site soon! Leave your comments and questions below – I love getting feedback!

Leave a Reply

Your email address will not be published. Required fields are marked *