Gutenberg Sidebar – The Main Plugin File Class Structure

Gutenberg Sidebar - Class Structure

Our main class, since we’re not doing anything fancy, is declaring a singleton which self-instantiates.

I mentioned a singleton. What is that?

Singletons

Singletons are basically a coding design pattern meant to prevent duplicate calls of instantiation. For example, in your main plugin file, you ideally would start off with a base class that starts the plugin chain reaction of initializing and divvying up all of the tasks elsewhere.

The Singleton can be considered a bad practice or anti-pattern, but they have their uses.

The singleton pattern is probably the most infamous pattern to exist, and is considered an anti-pattern because it creates global variables that can be accessed and changed from anywhere in the code.

Joseph Benharosh

The truth is that using objects in a WordPress plugin (or theme) is challenging. If you’d used functions, you wouldn’t have any of those headaches. Boring old functions are available anywhere and anytime (as long as your plugin is active).

With the singleton pattern, your class looks a lot like those easy to use functions. You have easy access to your plugin object(s) at any time. All you need to do is call “get_instance”.

Carl Alexander

The quotes seem to offer differing views of a Singleton and how a WordPress plugin should be structured. Carl offers a way out of Singletons in a post about using interfaces instead.

However, since our plugin is fairly simple and there’s not really a need to abstract everything in a fancy fashion, we’re going to stick with a good ol’ Singleton for our base class.

Base Class Structure

What I mean by the base class is that it is in the main plugin file and starts the process of initializing and running the plugin.

The class is usually given a unique name to avoid naming conflicts, but since we’re using namespaces, we can call it whatever we want.

Here it is in all its glory, which we’ll dissect in a little bit.

/** * The plugin base class. */ class Landing_Page_Gutenberg { /** * Landing_Page_Gutenberg instance. * * @var Landing_Page_Gutenberg $instance */ private static $instance = null; /** * Return a class instance. */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Class Constructor */ private function __construct() { add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 20 ); add_action( 'init', array( $this, 'init' ) ); } /** * Fired when the init action for WordPress is triggered. */ public function init() { /* Translations can be sent to ronald@mediaron.com - .po and .mo please. */ load_plugin_textdomain( 'landing-page-guttenberg-template', false, dirname( plugin_basename( WPAJAX_LANDING_FILE ) ) . '/languages/' ); } /** * Fired when the plugins for WordPress have finished loading. */ public function plugins_loaded() { } } Landing_Page_Gutenberg::get_instance();
Code language: PHP (php)

Let’s start at the end: Landing_Page_Gutenberg::get_instance();

What exactly is that line doing? Well, we know from the syntax that a class named Landing_Page_Gutenberg exists somewhere and has a static method called get_instance.

Let’s dive into that static method.

/** * Landing_Page_Gutenberg instance. * * @var Landing_Page_Gutenberg $instance */ private static $instance = null; /** * Return a class instance. */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; }
Code language: PHP (php)

There’s a private static class variable called $instance, which is intended to hold a copy of the class object in the event of instantiation. If $instance is null, we set the variable $instance a copy of the class. If the $instance is already set (i.e., has been instantiated earlier), the class returns a copy of itself. This is what a Singleton pretty much does.

We have a private constructor, which is called when the get_instance method returns an instantiation.

/** * Class Constructor */ private function __construct() { add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 20 ); add_action( 'init', array( $this, 'init' ) ); }
Code language: PHP (php)

In the constructor we start the chain reaction by declaring two actions: plugins_loaded and init.

Plugins are loaded by WordPress in alphabetical order, and there is relative safety in executing important code after a plugin has been fully included inside WordPress core, especially if you depend on another plugin to have loaded. Since we have no idea how many other plugins there are, the plugins_loaded action allows us to execute code only after all of the other plugins have had a chance to load in programmatically.

The init action is loaded later on in the action queue and is the main action you’ll come across when looking at other plugins. It’s a convenient hook timed for creating custom post types, taxonomies, or even hooking into later events in a safe manner.

For more information on which important WordPress hooks are loaded during a typical request, please check out the WordPress Action Reference.

First, let’s checkout what we’re doing in our plugins_loaded callback method, conveniently named plugins_loaded as well.

/** * Fired when the plugins for WordPress have finished loading. */ public function plugins_loaded() { }
Code language: PHP (php)

At the moment it does absolutely nothing. It’s just a placeholder for now, but it’s useful for instantiating classes after all of the plugins have finished loading. It’s also a great way to hook into other actions and filters fairly early.

Let’s travel to our init callback method also conveniently named init as well.

/** * Fired when the init action for WordPress is triggered. */ public function init() { /* Translations can be sent to ronald@mediaron.com - .po and .mo please. */ load_plugin_textdomain( 'landing-page-gutenberg-template', false, dirname( plugin_basename( WPAJAX_LANDING_FILE ) ) . '/languages/' ); }
Code language: PHP (php)

We’ll use the init action as an appropriate place to load in our plugin text domain so that others in different locales can potentially translate your plugin.

We use the WordPress function load_plugin_textdomain to tell WordPress that we have a unique text domain that is needed to load any translation files.

The load_plugin_textdomain takes the following arguments:

/** * Loads a plugin's translated strings. * * If the path is not given then it will be the root of the plugin directory. * * The .mo file should be named based on the text domain with a dash, and then the locale exactly. * * @since 1.5.0 * @since 4.6.0 The function now tries to load the .mo file from the languages directory first. * * @param string $domain Unique identifier for retrieving translated strings * @param string|false $deprecated Optional. Deprecated. Use the $plugin_rel_path parameter instead. * Default false. * @param string|false $plugin_rel_path Optional. Relative path to WP_PLUGIN_DIR where the .mo file resides. * Default false. * @return bool True when textdomain is successfully loaded, false otherwise. */ function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false )
Code language: PHP (php)
  • $domain – The Plugin text domain.
  • $deprecated – Not used.
  • $plugin_rel_path – Location of your languages folder relative to the plugin.

In this plugin’s case, the languages or translations folder is at the root of the plugin and is named languages. I’ve seen people name their folders lang, translations, and other variations of the word languages.

The way WordPress loads the translations is that it tries to determine if a WordPress plugin has a language pack present (these are in the wp-content/languages/plugins folder). If your plugin has a WordPress-loaded language pack, then a typical translation file would look like: landing-page-gutenberg-template-es_ES.mo.

If you’re releasing on .org, it is very important that your plugin slug matches your text domain. This is how WordPress knows which language packs belong to which plugin.

However, if you do plan on rolling your own translations, if WordPress doesn’t find a language pack, it’ll attempt to find the file in your plugin’s languages folder.

To further complicate things, there’s an entirely different method for translating strings in your JavaScript. This requires a language pack in JSON format. I’ll go into that in a different series on translations. For now, let’s just assume we’ll host all of our translations inside our plugin to make things a bit less complicated.

Another thing to note is that your text domain cannot be assigned to a variable.

For example, this won’t work very well:

define( 'MY_TEXT_DOMAIN', 'landing-page-gutenberg-template' ); load_plugin_textdomain( MY_TEXT_DOMAIN, ... );
Code language: PHP (php)

The issue is that tools that sniff your code for translatable strings will not really know what text domain to look for when interpreting your code for translation. You can find more about this issue in the WordPress Developer Handbook on Internationalization.

Conclusion

We’ll leave here with this file structure:

. └── landing-page-gutenberg-template/ ├── .babelrc ├── autoloader.php ├── landing-page-gutenberg-template.php ├── package.json ├── webpack.config.js ├── dist/ │ ├── sidebar.js (compiled JS) │ └── style.css (compiled SCSS) ├── includes (Include files go here) ├── languages (.mo, .po, .json files go here) └── src/ ├── js/ │ └── index.js └── scss/ ├── style.scss └── common.scss
Code language: AsciiDoc (asciidoc)

Here is the first draft of our main plugin file that we’ve been working on putting together.

<?php namespace WPAndAjax; /** * Plugin Name: Landing Page for Gutenberg * Plugin URI: https://github.com/wpajax/landing-page-gutenberg-template * Description: Creates a blank template for creating landing pages with Gutenberg and the theme Twenty Twenty One * Version: 1.0.0 * Requires at least: 5.5 * Requires PHP: 7.2 * Author: Ronald Huereca * Author URI: https://wpandajax.com * License: GPL v2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Text Domain: landing-page-gutenberg-template * Domain Path: /languages */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'WPAJAX_LANDING_VERSION', '1.0.0' ); define( 'WPAJAX_LANDING_DIR', plugin_dir_path( __FILE__ ) ); define( 'WPAJAX_LANDING_URL', plugins_url( '/', __FILE__ ) ); define( 'WPAJAX_LANDING_SLUG', plugin_basename( __FILE__ ) ); define( 'WPAJAX_LANDING_FILE', __FILE__ ); define( 'WPAJAX_LANDING_PLUGIN_URI', 'https://github.com/wpajax/landing-page-gutenberg-template' ); // Setup the plugin auto loader. require_once 'autoloader.php'; /** * The plugin base class. */ class Landing_Page_Gutenberg { /** * Landing_Page_Gutenberg instance. * * @var Landing_Page_Gutenberg $instance */ private static $instance = null; /** * Return a class instance. */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Class Constructor */ private function __construct() { add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 20 ); add_action( 'init', array( $this, 'init' ) ); } /** * Fired when the init action for WordPress is triggered. */ public function init() { /* Translations can be sent to ronald@mediaron.com - .po and .mo please. */ load_plugin_textdomain( 'landing-page-gutenberg-template', false, dirname( plugin_basename( WPAJAX_LANDING_FILE ) ) . '/languages/' ); } /** * Fired when the plugins for WordPress have finished loading. */ public function plugins_loaded() { } } Landing_Page_Gutenberg::get_instance();
Code language: PHP (php)

I’ll see in you in the next post in this series where we will begin working on hooking our generated JavaScript file into the block editor.