Today we’re going to explore code automation in the context of WordPress plugins. I’m going to share with you how we improve programming efficiency at SaberWP when building large scale WordPress plugins. First let’s define what code automation means in this context. Automation is a generic term like many others in technology, it generally refers to improving speed and replicating an effect while reducing or eliminating the need for human labor. In this case we’re talking about create processes (or routines) in our codebase that can reused and which automatically generate working features. You can think of code automation as being one step above “extendable code”. If extendable code “enables” you to do something (with implementation code), then “code automation” goes one step further and seeks to eliminate most or all of the implementation code.
- OrderModel extends BaseModel
- OrderTitleField extends BaseField uses FieldTypeText
Within our BaseModel we will define an abstract function such as $model->fields() and each model that extends it will need to then define each of it’s fields.
The difference between “extending” and “automating” can be subtle and nuanced. Certainly to automate your codebase you must first have the ability to extend objects. In fact automation tends to come about from having complete coverage in your extension capabilities.
What we are aiming for in our code automation is:
- Reducing the amount of implementation code writing to the minimal amount.
- Being able to use CONFIGURATION (data in results in functionality out) instead of IMPLEMENTATION code.
- Maximizing reuse of stable structural code and minimization of fault points (places where bugs tend to occur or logic fails).
Let’s examine how Saber Commerce plugin automates it’s data structure through 3 main automation processes:
- Model Structure Definition. A model structure definition (aka schema) defines the model by providing a title, a unique key, various configuration settings and most importantly the fields definition. For instance we define a TaxRate Model with a relationship (one-to-many) to a TaxClass Model. TaxRate has fields for location such as country, city and the tax rate. TaxClass has one a couple of fields as it’s mainly a categorization model to organize different tax rates. Throughout the entire Saber Commerce plugin, the fields will never be specifically named. They are always indirectly handled by loading the MSD (Model Structure Definition). For example:
- During plugin activation the MSD is loaded and the model database table is created. The fields in the database are automatically created by parsing the $model->fields() array. If a field does not require database storage such a calculated field, a flag “db” is set to false in the definition and it is skipped in the table creation process.
- Automated data loading during read operations (fetch, query). A $model->load() method automatically reads the MSD and does property loading from database row(s). The result is a fully-formed (data loaded) object such as TaxRate, with data available at named properties such as $taxRate->title;.
- Automated save operations. The query for insert or update is handled by the base model parsing the MSD. Usage is provided via $model->save(). Again as with database table creation, only fields flagged for DB storage are included in the query. Of course data sanitization and validation are automatically handled during the process.
- Automated delete or archive operations. Rounding out the CRUD (Create Read Update Delete) operations handling the base model structure also provides delete or archive functionality, including the ability to block deletion based on data integrity verification checks. In models where deletion should be avoided to prevent breaking data integrity (such as vital order data) then archiving can be handled instead (which is technically an Update from the CRUD model).
- REST API Endpoint Automation. Once we have phase 1 of our automation system working we have a solid model structure to build upon. Phase 2 is to automate the creation of WP REST API endpoints. This is simple enough to do because if we build out a REST API for our model manually with implementation code, we’ll find it follows a distinct pattern. In fact REST API’s in general are a great example of how by convention you can create predictable and consistent structures. We’re actually mirroring the REST API approach in both the model automation, and later in our React component automation. What endpoints do most models require? Let’s use the Saber Commerce TaxRate model as an example. First we take the model key (tax_rate) and convert this to an endpoint (tax-rate). This is just a simple swap of the underscored key naming to a hyphenated URL segment. Then using this as our base we will create:
- CreateModel: POST /model-base-segment
- Example: POST /tax-rate (Create a Tax Rate)
- UpdateModel: PUT /model-base-segment/[id]
- Example: PUT /tax-rate/[id] (Update a Tax Rate)
- DeleteModel: DELETE /model-base-segment/[id]
- Example: DELETE /tax-rate/[id] (Update a Tax Rate)
- ReadCollection: GET /model-base-segment
- Example: GET /tax-rate/ Get Collection of Tax Rates)
- ReadOne: GET /model-base-segment/[id]
- Example: GET /tax-rate/[id] Get Single Tax Rate)
- CreateModel: POST /model-base-segment
- React Model UX Automation (RMX). So we’ve built the foundation of our codebase automation structure. Whether we have 5 models or 50 models or more, we now have structured CRUD operations plus a REST API to manage those operations remotely or within our plugin. What we don’t have is an interface. React is our choice for front-end interface, and while it supports our codebase automation beautifully, you could easily adapt this to work with another front-end JS framework such as Vue. What’s important in React is building components that absorb the MSD (Model Structure Definition) and build out the UX based on the fields contained within it. These are “state-managed” higher-order component classes, for those who understand React lingo and structure. In other words we use only component classes (not functional components). The flow of data follows the React convention of only flowing down. Events on the other hand, or callbacks, bubble up the React tree. Key components include:
- ModelEditor Component. This component sits at the top of the RMX (React Model UX Automation) tree. It handles all API requests, manages the state for the main CRUD operations, handles visibility of the child components in the tree.
- ModelTable Component. This is a simple table component for rendering object models. It receives (as React props) the list of data objects plus the MSD (Model Structure Definition). The table headers are output from the field list, and the columns within each data row again are created by looping over the field definitions. ModelTable provides slots to fit child components, specifically the row action buttons. These are passed as prop components, which means the table component does not handle the events for these button instead they initiated in the ModelEditor where their events are controlled.
- ModelForm Component. This component works very similarly to the table component. It generates the editing form using the fields definition it is passed from the MSD. As with the table component we avoid doing handling in the component instead callback defer processing to the ModelEditor component. The form is state managed and is used for both create and edit operations. The parent ModelEditor maintains a cache of the model objects and when the user clicks the edit button on an object the form receives the object as a prop allowing it to display the values and switch into edit mode.