WordPress SUV
SUV is our own architecture for building WordPress themes and plugins at GrottoPress. This package is a scaffold for implementing SUV., (*1)
SUV is short for Setups-Utilities-Views. It emphasises an object oriented approach to writing WordPress plugins and themes, and provides for a cleaner, more organised code base., (*2)
SUV employs object composition extensively, and makes full use of the express power of core WordPress' event-driven architecture., (*3)
Setups: Includes all objects with methods that interact directly with WordPress, usually by means of action and filter hooks., (*4)
Utilities: Utilities are objects with methods that are needed by setups and views to accomplish their goals., (*5)
Views: Views are templates and partials to be loaded by the theme/plugin or WordPress., (*6)
Requirements
Code style
Code should comply with PSR-1, PSR-2 and PSR-4, at least., (*7)
You are strongly encouraged to use strict typing in PHP 7, and specify types for function/method arguments and return values., (*8)
As much as possible:, (*9)
- Aim for immutable objects
- Prefer declarative syntax
- Don't inherit concrete classes
- Avoid static methods
- Do away with fancy design patterns
Usage
Note: From here on, app refers to your theme or plugin., (*10)
Directory Structure
Set up your own app's directory structure as follows:, (*11)
.
โโโ app/
โ โโโ MyApp/
โ โ โโโ Setups/
โ โ โโโ Utilities/
โ โ โโโ Utilities.php
โ โโโ helpers.php
โ โโโ MyApp.php
โโโ assets/
โ โโโ css/
โ โโโ js/
โโโ dist/
โ โโโ css/
โ โโโ js/
โโโ lang/
โโโ node_modules/
โโโ partials/
โโโ templates/
โโโ tests/
โโโ vendor/
โโโ .editorconfig
โโโ .gitignore
โโโ CHANGELOG.md
โโโ codeception.yml
โโโ composer.json
โโโ composer.lock
โโโ <app-bootsrap>.php (functions.php or my-plugin.php)
โโโ LICENSE
โโโ package.json
โโโ package-lock.json
โโโ postcss.config.js
โโโ README.md
โโโ tailwind.config.js
โโโ tsconfig.json
โโโ webpack.mix.js
Not all directories/files may apply in your case. Remove whichever you do not need, and add whatever you require as necessary. Just keep the general concept in mind., (*12)
Autoloading
Your composer.json
autoload config:, (*13)
{
"autoload": {
"psr-4": {
"Vendor\\": "app/"
},
"files": [
"app/helpers.php"
]
}
}
Require SUV
From the root of your app, run:, (*14)
composer require grottopress/wordpress-suv
Sample WordPress plugin
Let's write a sample WordPress plugin using SUV, shall we?, (*15)
// @ wp-content/plugins/my-plugin/app/MyPlugin.php
<?php
declare (strict_types = 1);
namespace Vendor;
use Vendor\MyPlugin\Setups;
use Vendor\MyPlugin\Utilities;
use GrottoPress\WordPress\SUV\AbstractPlugin;
final class MyPlugin extends AbstractPlugin
{
/**
* @var Utilities
*/
private $utilities;
protected function __construct()
{
$this->setups['Footer'] = new Setups\Footer($this);
// ...
}
protected function getUtilities(): Utilities
{
return $this->utilities = $this->utilities ?: new Utilities($this);
}
}
// @ wp-content/plugins/my-plugin/app/MyPlugin/Setups/Footer.php
<?php
declare (strict_types = 1);
namespace Vendor\MyPlugin\Setups;
use GrottoPress\WordPress\SUV\Setups\AbstractSetup;
final class Footer extends AbstractSetup
{
public function run()
{
\add_action('wp_footer', [$this, 'renderText']);
}
/**
* @action wp_footer
*/
public function renderText()
{
echo '<div class="useless-text">'.
$this->app->utilities->text->render().
'</div>';
}
}
You may file utility classes in app/MyPlugin/Utilities/
. Utility classes do not interact directly with WordPress, but contain functionality that setup classes and views can use to accomplish their goal., (*16)
// @ wp-content/plugins/my-plugin/app/MyPlugin/Utilities.php
<?php
declare (strict_types = 1);
namespace Vendor\MyPlugin;
use Vendor\MyPlugin;
use GrottoPress\Getter\GetterTrait;
class Utilities
{
use GetterTrait;
/**
* @var MyPlugin
*/
private $app;
/**
* @var Utilities\Text
*/
private $text;
public function __construct(MyPlugin $plugin)
{
$this->app = $plugin;
}
private function getApp(): MyPlugin
{
return $this->app;
}
private function getText(): Utilities\Text
{
return $this->text = $this->text ?: new Utilities\Text($this);
}
}
// @ wp-content/plugins/my-plugin/app/MyPlugin/Utilities/Text.php
<?php
declare (strict_types = 1);
namespace Vendor\MyPlugin\Utilities;
use Vendor\MyPlugin\Utilities;
use GrottoPress\Getter\GetterTrait;
class Text
{
use GetterTrait;
/**
* @var Utilities
*/
private $utilities;
public function __construct(Utilities $utilities)
{
$this->utilities = $utilities;
}
/**
* This is obviously a very trivial example. We could
* have just printed this directly in the footer setup's
* `renderText()` method.
*
* It is done here only for the purpose of demonstration,
* if you know what I mean.
*/
public function render(): string
{
return \esc_html__('Useless text', 'my-plugin');
}
}
Since our plugin extends
SUV's AbstractPlugin
, it is essentially a singleton. The entire plugin (with all objects) can be retrieved with a call to Vendor\MyPlugin\MyPlugin::getInstance()
, (*17)
Let's create a helper to do this in app/helpers.php
., (*18)
// @ wp-content/plugins/my-plugin/app/helpers.php
<?php
declare (strict_types = 1);
use Vendor\MyPlugin;
function MyPlugin(): MyPlugin
{
return MyPlugin::getInstance();
}
Other plugins and themes now have access to the singleton plugin instance, and can remove an action in our plugin thus:, (*19)
\add_action('init', function () {
\remove_action('wp_footer', [\MyPlugin()->setups['Footer'], 'renderText']);
});
Now, to conclude with our plugin's bootstrap:, (*20)
// @ wp-content/plugins/my-plugin/my-plugin.php
<?php
/**
* @wordpress-plugin
* Plugin Name: My Plugin
* Plugin URI: https://www.grottopress.com
* Description: My awesome plugin
* Version: 0.1.0
* Author: GrottoPress
* Author URI: https://www.grottopress.com
* License: MIT
* License URI: https://opensource.org/licenses/MIT
* Text Domain: my-plugin
* Domain Path: /languages
*/
declare (strict_types = 1);
require __DIR__.'/vendor/autoload.php';
/**
* Run plugin
*/
\add_action('plugins_loaded', function () {
\MyPlugin()->run();
}, 0);
Building a plugin?
We created a WordPress plugin scaffold that uses SUV. It sets up the most common stuff for you to dive straight into code., (*21)
You should check it out ยป, (*22)
Building a theme?
If you're looking to build a theme using SUV, you should check out Jentil., (*23)
Jentil is a framework for rapid WordPress theme development, built using the SUV architecture., (*24)
It comes with numerous features, and includes a loader that loads templates (eg: page.php
, index.php
, single.php
etc) only from the app/templates
directory, and partials (eg: header.php
, footer.php
, sidebar.php
) from the app/partials
directory., (*25)
Check it out ยป, (*26)
Development
Run tests with composer run test
., (*27)
Contributing
- Fork it
- Switch to the
master
branch: git checkout master
- Create your feature branch:
git checkout -b my-new-feature
- Make your changes, updating changelog and documentation as appropriate.
- Commit your changes:
git commit
- Push to the branch:
git push origin my-new-feature
- Submit a new Pull Request against the
GrottoPress:master
branch.