Using proper Object
Oriented
Programming
in your Plugins

A presentation by @felixarntz

Why OOP

  • Code Organization (e.g. classes)
  • Abstraction (e.g. class inheritance)
  • Encapsulation (e.g. private properties)

Benefits of proper OOP

  • Modularity
  • Reusability
  • Maintainability
  • Extensibility
  • Portability
  • ...

But: Classes and Objects ≠ OOP

To truly leverage OOP, you need to learn about design principles, design patterns, standards.

What is a design principle?

→ Set of guidelines that help avoiding a bad software design.

Example: Single Responsibility Principle

Bad:

interface Car {
	public function set_price( float $price );

	public function sell();
}

Better:

interface Car {
	public function set_price( float $price );
}

interface Car_Dealership {
	public function sell_car( Car $car );
}

SRP is part of SOLID, the first five object-oriented design principles made popular by Robert C. Martin.

What is a design pattern?

→ Formalized best practices to solve common software problems.

Example: The Singleton Pattern

class My_Plugin {
	private static $instance = null;

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

		return self::$instance;
	}

	private function __construct() {
		My_Plugin_Settings_Controller::instance()->initialize();
	}
}

This is an anti-pattern!

Singleton Pattern Alternative

class My_Plugin {
	private $settings;

	public function __construct( My_Plugin_Settings_Controller $settings ) {
		$this->settings = $settings;

		$this->settings->initialize();
	}
}

Oh, another pattern: Dependency Injection!

Three Categories

  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns

What is a standard?

→ Improves interoperability of otherwise independent projects.

Example: WordPress Coding Standard

class My_Plugin {

	/**
	 * The settings controller instance.
	 *
	 * @since 1.0.0
	 * @var My_Plugin_Settings_Controller
	 */
	private $settings;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param My_Plugin_Settings_Controller Settings controller instance to use.
	 */
	public function __construct( My_Plugin_Settings_Controller $settings ) {
		$this->settings = $settings;

		$this->settings->initialize();
	}
}

Example: PSR-2 Standard

class MyPlugin
{

	/** @var MyPluginSettingsController The settings controller instance. */
	private $settings;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param My_Plugin_Settings_Controller Settings controller instance to use.
	 */
	public function __construct(MyPluginSettingsController $settings)
	{
		$this->settings = $settings;

		$this->settings->initialize();
	}
}

PSR-2 is a standard established by the PHP-FIG,
a group whose goal is to define standards for PHP projects.

Further Learning

General

More targeted towards WordPress

Let's Get Started With Code!

This project implements a reusable abstraction layer around the WordPress Admin Menu API. We will go through it one by one.

https://github.com/felixarntz/oop-admin-pages

Project Setup

cd my-wordpress-site
cd wp-content/plugins
git clone git@github.com:felixarntz/oop-admin-pages.git oop-admin-pages
cd oop-admin-pages
composer install
cd ../../..
wp plugin activate oop-admin-pages
cd wp-content/plugins/oop-admin-pages

The generic Admin_Page Interface

git checkout 8f4f979ee4318570557d0e855fe06977ce57f9ec

The Abstract_Admin_Page Class

git checkout f6ef1da22f4ab20a79192f5b3b88970aba561a6a

The Admin_Page_Factory Interface

git checkout 255b4d4af50d9f0bc753cd2f8e8cd53f3bc0fec5

The Admin_Page_Collection Interface

git checkout 84b6d689821b820fbd6cfc93123ef6db1f9a7cd4

The Base_Admin_Page_Collection Class

git checkout e7e6a21f32f1305bee47cf127707d1ef599065cc

The WordPress_Admin_Page Class

git checkout 3f73ca49e15d6c11338f3812afb55d014b092b9d

The WordPress_Admin_Menu_Page Class

git checkout 76281736965958e01446abcdf1031b277530a4e6

The WordPress_Admin_Submenu_Page Class

git checkout fc6019694dc7c73d03f9bfa67bf5badd21f82858

The WordPress_Admin_Page_Factory Class

git checkout 758c3f1aac6aa67a13f4935d28543f8545a32a80

Examples Using The Abstraction Layer

git checkout 76d65113b43aa780049ddbf52ce6e7d3181f9bf4

😳

Okay, we just turned two simple functions add_menu_page() and add_submenu_page() plus the admin_menu hook into 3 interfaces and 8 classes. WTF?
  • Applying OOP practices mostly helps in the long run, as projects get more complex or requirements change.
  • You only need to instantiate a Base_Admin_Page_Collection instance and pass in an array instead of looking up the parameter order of the WordPress functions multiple times.
  • The base implementation is system-agnostic and can be used in other environments as well. All you'd need to add is simple Drupal_Admin_Page and Drupal_Admin_Page_Factory implementations for example.
  • Keep in mind that in a real-world scenario, this would actually be a library you would reuse throughout all your plugins that need it.

Enhancing Our Project

In WordPress Multisite there is not only a site admin panel,
but also network and user administration panels.

How can we enhance our project so that
admin pages for these panels can also be registered?

First (Naive) Approach

git checkout 2e08d5bb50734f594c011eb43727f8bbb5761d42

Second (Better) Approach

git checkout 6326675a7b408992eb8af8de514dcb0f905a2bd6

Adjusted Examples

git checkout 857892236e62ce932715919f213aa47b81ffd29c

Thank you!

Felix Arntz

Plugin Developer / Core Committer / Freelancer