WordPress Software Development at H1. Part 2: Application Structure

Application loader

Bedrock does give you a project structure, but for your own code, it is still just regular old plugins, mu plugins and themes. We like to place our project core code as an mu plugin, and we put inside a [appname]-application folder. If we come up with reusable functionality, we separate those as plugins and mu plugins.

We start our application up in a php file in mu-plugins directory. That file is called [appname]-loader.php and looks something like this:

namespace MyApplication;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

// Initiate Composer Autoloading.
require( ABSPATH . '../../vendor/autoload.php' );

// Bootstrap other mu plugins.
require( 'posts-to-posts/posts-to-posts.php' );
require( 'cmb/Custom-Meta-Boxes/custom-meta-boxes.php' );

// Load application configuration.
$container = new ContainerBuilder();
$loader = new YamlFileLoader( $container,
	new FileLocator( __DIR__ . '/../../../config' ) );
$loader->load( 'services.yml' );

// Initiate the application.
$container->get('application');

What is happening there? To my taste, there is too much boilerplate code in there that would benefit of being encapsulated somewhere else. But, basically we just initiate the Symfony DependencyInjection container with a configuration file and then get an instance of our application class from it. Because of what we had defined in the configuration file, the application will get every object it will need from the container.

Application confuguration

Let’s look at the configuration file, services.yml. It has two parts. First, the parameters of each constructor is defined:

parameters:
    posttypes:
        - @post
        - @page
        - @person
        - @casestudy
        - @service
    connections:
        - @case_personnel
        - @services_provided
    taxonomies:
        - @casetype
        - @expertise
    queryfilters:
        - @frontpage
        - @cases
        - @persons
    services:
        - @person_api
        - @auto_meta_property

Names starting with @ are references to classes, defined in the later part of the file. The parameters are arrays of classes.

Then, the actual services, which in practise means classes:

services:
    wordpress:
        class:     WPlinth\WordPressFactory
    post_type_manager:
        class:     WPlinth\PostTypeManager
        arguments: ["%posttypes%"]
    queryfiltermanager:
        class:     WPlinth\QueryFilterManager
        arguments: ["%queryfilters%"]
    application:
        class:     ProjectName\Application
        arguments:
            - "@post_type_manager"
            - "%connections%"
            - "%taxonomies%"
            - "@queryfiltermanager"
            - "%services%"
    logging:
        class:     H1\Logging\Logging
    post:
        class:     ProjectName\PostType\Post
    page:
        class:     ProjectName\PostType\Page
    person:
        class:     ProjectName\PostType\Person
        arguments: ["@logging"]
        calls:     [[set_wp, ["@wordpress"]]]
    casestudy:
        class:     ProjectName\PostType\CaseStudy
        calls:     [[set_wp, ["@wordpress"]]]
    service:
        class:     ProjectName\PostType\Service
        calls:     [[set_wp, ["@wordpress"]]]
    case_personnel:
        class:     ProjectName\Connection\CasePersonnel
    services_provided:
        class:     ProjectName\Connection\ServicesProvided
    casetype:
        class:     ProjectName\Taxonomy\CaseType
    person_api:
        class:     ProjectName\Service\PersonAPI
    auto_meta_property:
        class:     WPlinth\AutoMetaProperty
        arguments: ["@post_type_manager"]
    frontpage:
        class:     ProjectName\QueryFilter\FrontPage
    cases:
        class:     ProjectName\QueryFilter\Cases
    persons:
        class:     ProjectName\QueryFilter\Persons

This example file is from a fictitious project. Let me explain it a bit. We have a site that displays reference cases and services that a company has. Personnel and services are connected to reference cases with Posts to Posts. Personnel information is integrated with the company CRM, using PersonAPI class, and we are logging each change to a person post because of that.

Application class

An instance of most classes is passed on to the Application object as a parameter. That class itself can be very simple:

namespace ProjectName;

class Application {
	public function __construct( $posttypemanager, $connections,
		 $taxonomies, $querymanager, $services ) {
	}
}

We could store the constructor parameters as instance variables, if we would like to extend the Application class to do stuff with those. But in normally we don’t have to.

Application folder structure

Our [appname]-application has a specific structure. Here is a typical directory structure:

/Connection
/PostType
/QueryFilter
/Service
/Taxonomy
/Test
Application.php

Each of the folder contains the classes that inherit from a base class in WPlinth. For example PostType folder contains all the post types and they are classes that inherit from WPlinth PostType base class. Service is just a generic folder name for miscellaneous services. You can obviously have different and/or other folders (namespaces) in your application.

In the next post, we’ll look at code inside those classes.