Ever wondered how you can create components for classical WordPress themes? Writing modular code that is easy to (re)use is an important skill if you are considering developing WordPress themes or plugins. In the post and upcoming video, I will explain one approach to how you can set up modular components in WordPress.

You will learn about:

  • How to set up a proper file structure for your components in your theme and plugin.
  • How to use Object Oriented Programming in PHP for creating components.
  • How to use (abstract) classes as a template for components.
  • How to create reusable PHP components.
  • How to separate logic from templating code.
  • How to use modern bundlers for your assets

To complete this tutorial, you should be able to write code in PHP and be familiar with JavaScript and CSS stylesheets. And obviously, be familiar with WordPress.

Why you should use components

There are various reasons to use components:

  • It usually results in cleaner code and a more logical structure
  • Components are easy to reuse
  • It separates PHP code from HTML (as much as possible)
  • It is easy to extend or modify components

Common Examples of Components

A component is anything that is also displayed on a WordPress website. Think of a grid of articles, buttons, headings, images, toggle sections, tabs, a list of categories and so forth.

My Approach to Setting up Components

So how can we set up a component in our code in a modular but smart way? Here are a few takeaways:

  • I am going to use one basic class that contains some properties and methods (functionalities) that can be used by all components.
  • Components will extend that basic class.
  • The output of components is separated into templates, and automatically load.
  • Any other files related to components, such as styles or scripts or placed in assets and named accordingly.

And that’s it. We don’t need much more to create reusable components.

Creating a structure for components: step by step

In the below section, I also explain the process step by step. In the example, we are creating a new plugin; but the same practice also applies to creating themes.

The video will also give more explanations about coding decisions.

Set up your development environment

For development, I am using Local to set up a WordPress website running locally, and VSCode as editor. They are both very straightforward to use.

  • Go ahead and use an existing or new development environment
  • Obviously, you can also use a development environment that is running online.

Create a new folder structure in /wp-content/plugins

Reusable components folder structure

The above image displays my file and folder structure. I would recommend following the same structure, which we will complete in the later steps.

The following is worthy to note for this structure:

  • The core file is in the root folder and is responsible for booting our plugin
  • All classes – files used in object-oriented programming – are placed in /src/classes, and follow the so-called PSR-4 naming conventions. In other words, all files and folders in /classes are Capitalized.
  • All un-bundled scripts and styles are placed in /src/. Thus, these still need to be bundled.
  • Anything that is public (bundled), such as CSS stylesheets and JavaScript will be placed in /public

Booting the plugin: /wp-php-components-example.php

The following code makes sure that if we execute a new Class, it is automatically loaded using – you guessed it – autoloading. In this case, we used MyPHPComponents as the namespace, in which all classes are placed. It also boots up the main plugin file, which we create in the next step.

<?php
/**
 * Plugin Name: WP Component Example
 * Description: PHP Component Tutorial
 * Version: 0.0.1
 */
spl_autoload_register( function($class_name) {

  if( strpos($class_name, 'MyPHPComponents') !== 0 ) {
    return;
  }

  $called_class = str_replace( ['\\', 'MyPHPComponents'], ['/', ''], $class_name );
  $class_file   = dirname(__FILE__) . '/src/classes' . $called_class . '.php'; 

  if( file_exists($class_file) ) {
    require_once( $class_file );
  }

} );

/**
 * Load our plugin
 */
add_action( 'plugins_loaded', function() {
  defined('PLUGIN_PATH') or define( 'PLUGIN_PATH', plugin_dir_path( __FILE__ ));
  defined('PLUGIN_URI') or define( 'PLUGIN_URI', plugin_dir_url( __FILE__ ));

  $plugin = MyPHPComponents\Plugin::instance();

} );

Also, a constant is added to save the path and URL of our plugin. If you run this code, it will give an error because the required Plugin file can not be found. Let’s create it!

The main class: /src/classes/Plugin.php

This code is using a famous design pattern (yes, the software can have design patterns too!) called a singleton. This pattern makes sure you can only instantiate a class once – meaning that all methods executed in this class only execute once.

<?php
/**
 * Boots the plugin
 */
namespace MyPHPComponents;

defined( 'ABSPATH' ) or die('Nope...');

class Plugin {

  private static $instance = null;

  private function __construct() {
    $this->enqueue_scripts();
  }

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

    return self::$instance;
  }

  private function enqueue_scripts() {
    add_action('wp_enqueue_scripts', function() {
      wp_enqueue_style('component-styles', PLUGIN_URI . '/public/css/styles.css');
      wp_enqueue_script('component-scripts', PLUGIN_URI . '/public/js/scripts.js', [], false, true);
    });
  }

}

And that makes it perfect for use cases such as booting a plugin. Else, there is not so much written in this code, it just registers the assets and can be modified to your own liking.

The namespace at the top basically indicates the folder a file is in and ensures autoload works correctly.

The component class: /src/classes/Components/Component.php

Our component class is interesting. This is an abstract class, meaning it can not be executed by itself. The abstract class sets the rules for how all child classes (classes that extend this class) should look like.

<?php
/**
 * The abstract class for our components
 */
namespace MyPHPComponents\Components;

defined( 'ABSPATH' ) or die( 'Nope...' );

abstract class Component {

  /**
   * Contains the parameters for a component
   * @access protected
   */ 
  protected $params = [];

  /**
   * Contains the public properties, used in the template
   * @access public
   */  
  public $props = [];

  /**
   * Contains the template
   * @access protected
   */  
  protected $template = '';

   /**
   * Set up our parameters and component
   * 
   * @param array     $params     The parameters for our component
   * @param boolean   $format     Query and format by default
   */
  final public function __construct( array $params = [], bool $format = true ) {

    $this->register();

    $file = strtolower( (new \ReflectionClass($this))->getShortName() );
    $this->template = PLUGIN_PATH . '/src/templates/components/' . $file . '.php';
    $this->params   = wp_parse_args( $params, $this->params );

    if( $format ) {
      $this->format();
    }

  }

  /**
   * This function registers the default parameters for the component
   * It should be used to set $this->params with the default parameters.
   */  
  abstract protected function register();

  /**
   * This function is used for formatting or querying of data based on parameters
   */
  abstract protected function format();

  /**
   * Renders a component
   * 
   * @param bool $echo Whether a template should be displayed or returned as a string
   */
  public final function render( bool $echo = true ) {

    if( ! $this->props || ! file_exists($this->template) ) {
      return;
    }

    foreach( $this->props as $key => $value ) {
      ${$key} = $value;
    }

    if( ! $echo ) {
      ob_start();
    }

    require( $this->template );

    if( ! $echo ) {
      return ob_get_clean();
    }

  }

}

The method has the following:

  • The ‘props’ property which holds all properties that are used in templates.
  • A default constructor, which parses existing with default properties and executes the major functions
  • An abstract register function, that every component needs to use in order to register properties.
  • A render function that can automatically load the correct template based on the class name. This function will take all properties and map them to their variables, so $this->props[‘text’] becomes $text. And that’s the variable we can use in the template.

An example component: /src/classes/Components/Example.php

The example component class extends the abstract class, which as I explained functions as a class ‘template’. As a property, I have added the text key in ‘props’ which allows adding some text. And that’s it! The rest is already defined by our abstract component class.

<?php
/**
 * The abstract class for our components
 */
namespace MyPHPComponents\Components;

defined( 'ABSPATH' ) or die( 'Nope...' );

class ExampleComponent extends Component {

  /**
   * Register our default parameters
   */
  protected function register() {
    $this->params = [
      'link'  => '#',
      'size'  => 'regular', 
      'text'  => ''
    ];
  }

  /**
   * Format our properties
   */
  protected function format() {

    foreach($this->params as $key => $value) {
      $this->props[$key] = esc_html($value);
    }
    
  }  

}

Obviously, for more elaborate components the format and register functions would contain a lot more content!

An example template: /src/templates/components/example.php

In the example template, we take the properties that are automatically inserted by our Example component class. In this case, that is $text, $link and $size.

<?php
/**
 * The actual template for the component
 */
?>
<a class="btn btn-<?php echo $size; ?>" href="<?php echo $link; ?>">
  <?php echo $text; ?>
</a>

Adding assets: /src/assets/

If your component requires any assets, such as styles and scripts, a good approach would be to place them in /src/assets. This step is only needed if you are going to use assets.

For this project, I am using ESBuild which is a modern and fast bundler for assets. It’s outside the scope of the article to discuss this, but ESBuild is worth checking out.

After adding your assets, run the following commands (this requires NodeJS to be installed on your machine):

npm init

Follow the wizard, and you end up with a package.json. Subsequently, run the following command to install some development dependencies

npm i -D autoprefixer esbuild esbuild-sass-plugin postcss

In the package.json that is generated, also add the following command under scripts: ‘”bundle”: “node esbuild.config.js”‘. You’ll end up with something like the following:

{
  "name": "wp-php-components-example",
  "version": "1.0.0",
  "description": "WordPress Example for Modular Components",
  "main": "public/assets/js/scripts",
  "scripts": {
    "bundle": "node esbuild.config.js"
  },
  "devDependencies": {
    "autoprefixer": "^10.4.2",
    "esbuild": "^0.14.23",
    "esbuild-sass-plugin": "^2.2.4",
    "postcss": "^8.4.7"
  }
}

At last, determine your configurations for ESBuild in esbuild.config.js. This is my approach:

const esbuild = require("esbuild");
const { sassPlugin } = require("esbuild-sass-plugin");
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');

esbuild.build({
  entryPoints: ["src/assets/css/styles.scss", "src/assets/js/scripts.ts"],
  outdir: "public",
  bundle: true,
  metafile: true,
  plugins: [
      sassPlugin({
          async transform(source) {
              const { css } = await postcss([autoprefixer]).process(source);
              return css;
          },
      }),
  ],
  watch: true
})
.then(() => console.log("⚡ Complete! ⚡"))
.catch( () => process.exit(1));

The above explains that any file in /src/assets/css/styles.scss and /src/assets/js/scripts.ts will be output to the public folder.

However, to run this configuration, you should run the command `npm run bundle` in your command line.

Don’t forget to enqueue your public scripts and styles if you use them.

Implementing a component

But how do you implement a component? Let’s look at a code example (in any PHP code):

<?php
add_filter('the_content', function($content) {

  $component = new MyPHPComponents\Components\ExampleComponent([
    'link'  => 'https://example.com',
    'text'  => __('Text'),
    'size'  => 'large'
  ]);

  $component_string = $component->render(false);

  return $content . $component_string;

});

In this simple example, I implement the component after all single posts and pages – any place where ‘the_content’ is called. In this case, it renders ‘Some Text’. The great thing is that we can use the ‘render’ method from the inherited class to also output a string.

Building Reusable Components

I hope you liked building your first reusable component – and it gave you a hunger to create more components. You can find the complete code for this post on Github.

In this post, we focused on building reusable components with PHP, which is the classic way how WordPress themes were built. In an upcoming post, we will focus on building a custom component in Gutenberg (using JavaScript and React).

I also created a complete library for common WordPress components.