Namespaces and Objects

Markdown editing for WordPress.

Parsedown Party is a new WordPress Plugin I wrote with PHP namespaces, Composer support, 90% code coverage, and Travis-CI for automatic testing and releases.

While writing this Plugin, I settled on some WordPress coding conventions that I’d like to talk about.

Namespaces

One of the weirder things I read as a WordPress Plugin developer are tips such as “prefix all your function with name_” or “put all your functions in a class and declare them as static.” This advice is to prevent code collisions with the other 53,123+ Plugins available for WordPress. Big number.

On the other hand, Packagist lists 164,796+ PHP Composer packages, does not follow the aforementioned recommendations and has no such code collisions. Why? Because they use namespaces.

PHP Namespaces have been available since 2009. Other programming languages such as Java or Perl have had them for longer. WordPress Core doesn’t encourage PHP Namespaces but the syntax is fully supported.

If my class is a bunch of static methods and nothing else then I am doing it wrong. I should instead write a library of functions. If I’m afraid of function name collisions then I should use Namespaces because they solve that exact problem and they work fine with WordPress.

add_action( 'login_head', '\Kizu514\Coolname\this_works_fine' );
add_filter( 'login_headerurl', '\Kizu514\Coolname\welcome_to_php' );

Objects

Another odd thing I see while browsing OPP (other people’s plugins) are objects with “mostly” static methods.  There is some object-oriented approach to the design but most of the methods are static because they hook into actions and filters.

I think that an object in a WordPress Plugin should have, at most, two static methods. Every other method in a class should be public, protected or private.

There are very few cases where static methods are necessary. Most of the time public methods can be used. At worst, namespaced functions can invoke objects as needed and these concerns should be separate.

Here’s an example of what I mean (the code must also have PHPDoc comments  but for brevity, I have omitted these):

namespace Kizu514\CoolClass;

class Plugin {
	private static $instance = null;
	private $obj1;
	private $obj2;
	private $obj3;

	static public function init() {
		if ( is_null( self::$instance ) ) {
			$obj1 = new \Obj1();
			$obj2 = new \Obj2();
			$obj3 = new \Obj4( new \Obj3() );
			self::$instance = new self( $obj1, $obj2, $obj3 );
			self::hooks( self::$instance );
		}
		return self::$instance;
	}

	static public function hooks( Plugin $obj ) {
		add_action( 'save_post', [ $obj, 'doSomething' ] );
		add_filter( 'the_content', [ $obj, 'doSomethingElse' ] );
	}

	public function __construct( $obj1, $obj2, $obj3 ) {
		$this->obj1 = $obj1;
		$this->obj2 = $obj2;
		$this->obj3 = $obj3;
	}

	public function doSomething( $post_id ) { /**/ }

	public function doSomethingElse( $content) { /**/ }
}

Some time passes….

add_action( 'after_setup_theme', [ '\Kizu514\CoolClass\Plugin', 'init' ] );

The first static method, init, is hooked into one of many available launch points like plugins_loadedafter_setup_theme, or init. Because these actions happen before everything else a second static method, hooks, can do the rest of the work from inside the object itself.

Admittedly, even if I am down to just two static methods, they are gross. Glaring problems include “Singletons are evil” and not using Inversion of Control.

I’m very happy with this code style and I will be using it moving forward.

WordPress REST API Quickstart

The WordPress REST API has been available since 4.7.  It’s robust, consistent, and nifty to work with. Why? Backend and mobile developers can use other frameworks while still keeping WordPress around for their customers. Frontend developers can build sites using JavaScript without having to touch PHP. Up is down, left is right, dogs and cats living together… Let’s get started!

Recommended Tools

Troubleshooting

  • JSON Formatter: CTRL/CMD+Click a triangle to collapse/expand nodes at the same level.
  • YARC: When testing with Basic Authentication, make sure you are logged out of WordPress first.

Getting Started

WP API supports all HTTP Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS.

WP API respects permissions but the developer must setup authentication separately.

Schema

WP API is self-documenting. Send an OPTIONS request to any endpoint and get back JSON Schema compatible info on how to use it:

OPTIONS

To get the entire API schema in a single query, add context=help at the index. (Ie. http://site/wp-json?context=help )

Features

WP API items have a _links node based on HAL (Hypertext Application Language):

_links

To reduce the number of HTTP requests use the _embed parameter to tell the API that the response should include embeddable resources.

_embed

WP API exposes pagination info in the response header.

Pagination

PHP to JSON

WP API renders JSON in a generic way that does not match the DB columns. Keep calm and RTFM:

if ( ! empty( $schema['properties']['author'] ) ) {
$data['author'] = (int) $post->post_author;
}
if ( ! empty( $schema['properties']['slug'] ) ) {
$data['slug'] = $post->post_name;
}
if ( ! empty( $schema['properties']['content'] ) ) {
$data['content'] = array(
'rendered'  => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ),
'protected' => (bool) $post->post_password,
);
}

{
"author": 1,
"slug": "chapter-1",
"content": {
"rendered": "<p>Hello World!</p>",
"protected": false
}
}

Example

Setup the Basic Authentication Plugin on your development environment.

In YARC, add your credentials:

YARC CredentialsSend an OPTIONS request to a post endpoint. The response will contain, among other information:

{
"methods": [
"POST",
"PUT",
"PATCH"
],
"title": {
"required": false,
"description": "The title for the object.",
"type": "object"
},

Translation: The API client can send a PUT request to change the title.

In YARC, send a PUT request with the following JSON to the endpoint:

{ 
"title": "My changed title!" 
}

Congratulations, you just changed the title. 

…cue the sound of a thousand keyboards furiously hacking.

Write Unit Tests For Your WordPress Plugin Using PhpStorm Code Completion

Git clone the WordPress develop repository somewhere on your hard drive:

git clone git@github.com:WordPress/wordpress-develop.git

Open wordpress-develop/tests/phpunit/includes/phpunit6-compat.php in a text editor.

PHPUnit version 5: Comment out the class_alias() functions in phpunit6-compat.php because these break PhpStorm code completion. (These files aren’t actually used by the testing framework, we only downloaded them so they could be included in the Project Configuration’s Include Path.)

PHPUnit version 6 and up: Do the same thing as PHPUnit 5, the paragraph above, except leave this line uncommented:

class_alias( 'PHPUnitFrameworkTestCase', 'PHPUnit_Framework_TestCase' );

In PhpStorm, go to: Settings -> Languages & Frameworks -> PHP and add wordpress-develop/tests/phpunit/includes to your Include Path.

Use WP-CLI to generate the tests scaffolding.

Write tests that extend WP_UnitTestCase. Look at the code in wordpress-develop/tests/phpunit/tests for examples.

Autocomplete!
Autocomplete!

WordPress as a Development Platform

Many people dislike WordPress code. It’s no secret that for contemporary PHP developers WordPress feels antiquated. The founder of WordPress was even once-upon-a-time vocal about not keeping up to date with the PHP eco-system because reasons.

Times changed. So did PHP. So did WordPress.

To give credit where credit is due, the reasoning behind WordPress’ conservative change management is sound. They don’t want to mess with their insanely huge user base.

WordPress powers 30% of all websites on the internet.

That’s a lot of users. By choosing WordPress as your development platform you get massive traction for free.

But… that code. Ugh!

The good news is that when you develop for WordPress you don’t ever touch WordPress code. Instead you write a Plugin. [1] I put forward that in 2017 nothing is stopping you from writing a good, clean Plugin other than yourself.

Environment

WordPress is PHP 7 compatible. WordPress is also HHVM compatible [2]. Running on either vastly increases performance.

It follows that if your environment is PHP 7 then you get the syntax.

Syntax

WordPress Plugins can have PSR compatible namespaces.

WordPress’ answer to Event Dispatcher (and/or Observer) are the add_action() and add_filter() functions. These functions are compatible with closures.

Meaning you can write code like:

add_action('init', function() use ($v) {
    (new \Acme\Foo\Bar\SomeClass($v))->someMethod();
});

add_action('init', '\Acme\some_function');

add_action('init', ['\Acme\Foo\Bar\SomeOtherClass', 'someStaticMethod']);

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

Or *any* standards compliant PHP 7 code you want to write.

With thousands of actions and filters available, pretty much any part of WordPress can be changed.

Tools

PHPStorm supports WordPress Plugin development out-of-the-box.

WP-CLI is a set of command-line tools for managing WordPress installations. It simplifies many developer and deployment related tasks and makes unit testing your plugin possible.

WordPress Plugins have PHP CodeSniffer rules ready to go.

WordPress Plugins can be installed using Composer.

Bedrock and Trellis for the win.

Open

WordPress is licenced under the GPL. This still matters.

Getting Things Done

Is there room for improvement? Of course! Just like PHP, WordPress is always improving with the caveat that just like PHP, WordPress strives to keep backwards compatibly.

Developers rejoice, WordPress is moving forward, kicking and screaming as we drag it into the future.

[1] WordPress also has a REST API if you’re into that kind of thing…
[2] https://core.trac.wordpress.org/ticket/40548

Password Protect WordPress Admin With .htaccess

The wp-admin panel is already password protected in that you are required to login. Sometimes that’s not good enough. This tutorial explains how to add an additional layer of authentication to the login process, essentially blocking wp-login.php requests from annoying bots or other malicious users.

Step 1:

Create a `/path/to/.htpasswd` file. (More info.)

Step 2:

Create a `/path/to/your/site/wp-admin/.htacess` file with the following content:

AuthUserFile /path/to/.htpasswd
AuthType basic
AuthName "Restricted Resource"
require valid-user

# Whitelists

<Files "admin-ajax.php" >
   Order allow,deny
   Allow from all
   Satisfy any
</Files>

<Files "*.css" >
   Order allow,deny
   Allow from all
   Satisfy any
</Files>

<Files ~ "\.(jpg|jpeg|png|gif)$">
   Order deny,allow
   Allow from all
   Satisfy any
</Files>

Change `/path/to/` your files accordingly.

Important! Under Whitelists I have added entries for admin-ajax.php, *.css, and a regular expression for images. This unblocks WordPress’ AJAX functionality used by certain plugins, as well as CSS and image files certain themes may be importing. Without these you risk breaking your site.

Step 3:

Append the following to your existing WordPress .htaccess file one parent folder up (Ie. /path/to/your/site/.htaccess):

<Files wp-login.php>
  AuthUserFile /path/to/.htpasswd
  AuthType basic
  AuthName "Restricted Resource"
  require valid-user
</Files>

Change `/path/to/` your files accordingly.

Profile WordPress from the Command Line Using Blackfire.io and WP-CLI

Last week I needed a way to use Blackfire.io to profile a POST action in the WordPress administration panel.

I thought Blackfire.io would be able to handle POST the way Xdebug does: Generate a different cachegrind file for every PHP invocation; but alas, Blackfire.io currently only does static webpages, command line, or API calls. In theory I could have used “Copy-As-cURL in your Browser(and believe me I tried) but in practice the WordPress admin is stateless (no $_SESSION), uses check_admin_referer() all over, making whatever POST action I was copying as cURL useless.

My solution was the following hack:

cd /path/to/wordpress
blackfire run wp eval-file --url=http://pressbooks.dev/helloworld/ test.php

Where `wp` is WP-CLI, `eval-fileloads and executes a PHP file after loading WordPress, `–url=` is the the current site I want to profile on a WordPress multi-site install, and `test.php` is a script containing only the functionality I want to profile.

The profiler data gave some bogus results (Ie. a lot of WP-CLI bootstrapping gets flagged as slow) but at least this was better than nothing.

In the future, it would be great if Blackfire Companion had some sort of option to profile “the next action,” or to “start profiler on submit,” or something other than reloading the current page… Ping SensioLabs?

WP Memcached Object Cache Leaks Memory?

While working on Pressbooks, a multi-site WordPress based web application, I noticed that some of our customers were getting blank pages in the admin section. Specifically, customers with a lot of Sites (or Books as they are known in Pressbooks).

Checking the error logs I saw that these customers were running out of memory.

PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to allocate 292913 bytes) in /path/to/object-cache.php on line 212.

First, to temporarily stop the out-of-memory problem so I could profile I added the following to wp-config.php:

define( 'WP_MAX_MEMORY_LIMIT', '512M' );

Next, using Blackfire.IO I was able to determine the following:

Before
Before (285 MB)

That is, when a customer was looking at their dashboard, PHP was consuming 285MB of memory. Most of it the Memcached Object Cache plugin.

That’s weird. I’m using the latest version of the plugin, the plugin is developed by core developers, and no one has reported this before? Or so I thought! Browsing the plugin SVN I see the following change committed to trunk:

Changeset 626248

There’s a few more fixes in there as well. After installing the TRUNK version of this plugin Blackfire.IO displayed:

After
After (12.2MB)

That’s a 273MB improvement!

It took me days to figure out this problem. It would have saved me a lot of time had I seen the new code first.

Bonus info:

  • The code in TRUNK has at least 2 bugs. (…just load the file in PHPStorm and the errors will be underlined in red)
  • Redis Object Cache gives better results.

For now, this is good enough.