Article
Modernizing WordPress Development with Sage 9: Part 3
In Part 1 and Part 2 of this series, we’ve covered Controller in Sage 9. According to MVC pattern (Model-View-Controller), in the ideal circumstance, Views and Controller will be completely separated which enables us to reuse Views more conveniently.
Imagine you are tailoring a website. There are parts that share the same HTML markup but use data from 3 different sources (let’s say, one from a Post, one from an API, and one from a custom database).
The most common way to achieve this is to create a partial template, then have condition checking implemented, where the partial template is displayed and data is pulled from the source accordingly. Another approach would be to create 3 partial templates using the same markup but change the data source. Either way, both approaches come with their own headaches.
Controller: The Knight in the Shining Armor
As we mentioned above, in MVC, Views, and Controllers are completely separated. Views don’t need to “know” where the data comes from. They simply wait for the Controller to feed them data to display.
So, to solve the aforementioned problem, consider creating a template echoing some variable. Then create a Controller to expose the data to the template.
Creating Our Controller
While we were writing this entry of the series, Sage 9.0.2 has released. It included a major change upgrading the package
soberwp/controller
from version 9.0.0-beta4 in previous release to current 2.1.0. Thus, this entry got updated to coversoberwp/controller
2.1.0
First, navigate to the directory ./app/Controllers
where there will be two Controllers existing in the directory already, which are App.php
and FrontPage.php
App.php
is the global Controller. Every template in our theme will be able to access data/method in this Controller. Other controllers will be named after the template file name but converted to CamelCase. For example:
FrontPage.php
is corresponding tofront-page.blade.php
Single.php
is corresponding tosingle.blade.php
SinglePostType.php
is corresponding tosingle-post-type.blade.php
You can learn more about template file names via WPHierarchy.
Inside the controller there are some requirement, listed below:
namespace
must beAppControllers
- import (
use
)SoberControllerController
- The class name must be the same as the file name (CamelCase of template file name)
- The class must be extended from
Controller
For example:
<?php // app/Controller/Single.php namespace AppControllers; use SoberControllerController; class Single extends Controller { // ... methods go here }
Inside the class, There are 3 types of methods we are going to use:
public function MethodName()
Public method will be used to expose data back to View, whereMethodName()
will be converted to a snake_case variable as$method_name
. From here, we can use it inside the template.public static function MethodName()
Static method won’t be converted to a variable. You can access them directly by runningClassName::MethodName()
. Notice that this is a static method call. That means you can also receive/pass arguments to the method if needed.protected function MethodName()
Protected method won’t be exposed to the template. That means you can’t access it from outside the class. This is generally used for data processing inside an object. (Dependencies injection is also available in__construct()
)
Here is the example of exposing data from Controller to view
<?php // app/Contollers/Single.php namespace AppControllers; use SoberControllerController; class Single extends Controller { public function PostTitle() { return $something; // get_the_title(); } }
{{-- resources/views/single.blade.php --}} <h3 class="entry__title"> {{ $post_title }} </h3>
Create Components with Trait
In a more complex theme, we will usually run into some circumstances of using Partial Templates for reused elements, like headers and footers. And if these partial templates are using data from the Controller, we might end up repeating every method in each Controller. We can avoid this situation by using Trait.
Trait
is a PHP built-in feature. It will let you write a set of method and reuse them in classes (it’s metaphorically same as file inclusion).
For example, we have a page header as a partial template like this:
Let’s say the title and description on the header can be changed conditionally. So, we use Controller to expose data back to the template file, avoiding the template being spaghetti from using a lot of if ... else
.
In this case, instead we have to write the same method in every Controller, we create our trait and use
it in controllers (we suggest keeping the file name and directory structure the same between templates and controllers/traits to make them easily recognizable. For example, trait file for views/partials/page-header.blade.php
will be app/Controllers/Partials/PageHeader.php
)
To create a trait, just declare the trait name and write methods inside the trait block as usual:
<?php // app/Controllers/Partials/PageHeader.php namespace AppControllersPartials; trait PageHeader { public function PageTitle() { return $something; } public function PageDescription() { return $something; } }
And in the Controller file, we call Trait with the use
keyword inside a class block:
<?php // app/Controllers/Single.php namespace AppControllers; use SoberControllerController; class Single extends Controller { use AppControllersPartialsPageHeader; // call Trait PageHeader }
Inherit Methods between controller using Tree interface
Let’s take a minute and look back at WordPress template hierarchy (here), You will notice that WordPress will look for less and less specific template files if it can’t find the more specific template. For example, if WordPress can’t find single-post-type.blade.php
, then it will look for single.blade.php
(and it will end up at index.blade.php
, eventually).
Following the hierarchy, Controller has a Tree
interface to let it inherit methods from a less specific template. For example, we have method PageTitle()
in Controller Index.php.
If we create the Controller Single.php
without implementing Tree
, we won’t be able to use PageTitle()
from Index.php
. Then, we’ll have to write the same method in Single.php
The Tree
interface will solve this issue. All you need to do is use
the module SoberControllerModuleTree
and implement it to our Controller:
<?php // app/Controllers/Index.php namespace AppControllers; use SoberControllerController; class Index extends Controller { public function PageTitle() { return get_the_title(); } public function PageDate() { return get_time(); } }
<?php // app/Controllers/Single.php namespace AppControllers; use SoberControllerController; use SoberControllerModuleTree; // call Tree Module class Single extends Controller implements Tree { // implementing Tree interface }
As shown in the example above, you can use $page_title
and $page_date
in the template single.blade.php
, which actually was declared in Controller Index.php
Using the Tree
interface is a better solution than using Trait
in some circumstances, because you can safely redeclare the same method name in the Controller. If you want to change something in PageTitle()
you can just redeclare it like this:
<?php // app/Controllers/Single.php namespace AppControllers; use SoberControllerController; use SoberControllerModuleTree; class Single extends Controller implements Tree { public function PageTitle() { return "Page: " . get_the_title(); } }
And in single.blade.php
you’ll be able to call $page_date
which came from Index.php
, while $page_title
will now using data from Single.php
Note: only
public
method can be inherited via the interface.
A Final Note: Template Override
There is ONE edge case where you can’t just declare a class name for the template. That case is 404.php
since you can’t name the class using the number. To solve it, you can still specify which template this Controller is for by declare property $template
<?php namespace AppControllers; use SoberControllerController; class PageNotFound extends Controller { protected $template = "404"; }