
One Way to Turn WordPress Plugin into MVC Architect and developing with OOP 转换wordpress插件为MVC架构并用面向对象开发的一种方法 ワードプレスプラグインをMVCアーキテクチャに変換し、オブジェクト指向開発を使用する方法
Basically speaking, I hate developing on wordpress as it is not modern enough or say not using modern techniques like composer, oop.
Fortunately after studying a few modern themes and plugin examples, I learnt the way.
To be short and clear, will show the directory structure and sample code directly. You should have some basic knowledge of wordpress plugin to understand what I’ll write below.
MVC directory structure for wordpress plugin:
├── my-plugin.php
├── assets
│ ├── myscript.js
│ ├── mystyle.css
├── composer.json
├── gulpfile.js
├── inc
│ ├── Api
│ │ ├── Callbacks
│ │ │ ├── ManagerCallbacks.php
│ │ │ ├── TaxonomyCallbacks.php
│ │ │ └── TestimonialCallbacks.php
│ │ ├── SettingsApi.php
│ │ └── Widgets
│ │ └── MediaWidget.php
│ ├── Base
│ │ ├── Activate.php
│ │ ├── BaseController.php
│ │ ├── CustomPostTypeController.php
│ │ ├── CustomTaxonomyController.php
│ │ ├── Deactivate.php
│ │ ├── Enqueue.php
│ │ ├── TemplateController.php
│ │ ├── TestimonialController.php
│ │ └── WidgetController.php
│ ├── Init.php
│ └── Pages
│ └── Dashboard.php
├── index.php
├── package.json
├── page-templates
│ ├── front-page.php
│ └── two-columns-tpl.php
├── src
│ ├── js
│ │ ├── auth.js
│ │ ├── form.js
│ │ ├── myscript.js
│ │ └── slider.js
│ └── scss
│ ├── auth.scss
│ ├── form.scss
│ ├── modules
│ │ ├── checkbox.scss
│ │ ├── form.scss
│ │ ├── table.scss
│ │ └── tabs.scss
│ ├── mystyle.scss
│ └── slider.scss
├── templates
│ ├── taxonomy.php
│ ├── testimonial.php
│ └── widget.php
└── uninstall.php
Think of my-plugin.php
as the entry file for the plugin. And init the composer’s autoload and also init services( as if registering and booting services in a container. Think of it as an IoC pattern.)
//my-plugin.php
// Require once the Composer Autoload
if ( file_exists( dirname( __FILE__ ) . '/vendor/autoload.php' ) ) {
require_once dirname( __FILE__ ) . '/vendor/autoload.php';
}
/**
* Initialize all the core classes of the plugin
*/
if ( class_exists( 'Inc\\Init' ) ) {
Inc\Init::registerServices();
}
// composer.json
"autoload": {
"psr-4": {"Inc\\": "./inc"}
}
// Inc/init.php
namespace Inc;
final class Init
{
/**
* Store all the classes inside an array
* @return array Full list of classes
*/
public static function getServices()
{
return [
Pages\Dashboard::class,
Base\Enqueue::class,
Base\CustomPostTypeController::class,
Base\CustomTaxonomyController::class,
Base\WidgetController::class,
Base\TestimonialController::class,
Base\TemplateController::class,
];
}
/**
* Loop through the classes, initialize them,
* and call the register() method if it exists
* @return
*/
public static function registerServices()
{
foreach (self::getServices() as $class) {
$service = self::instantiate($class);
if (method_exists($service, 'register')) {
$service->register();
}
}
}
/**
* Initialize the class
* @param class $class class from the services array
* @return class instance new instance of the class
*/
private static function instantiate($class)
{
$service = new $class();
return $service;
}
}
Up to now we’ve setup the IoC pattern. The next step is to define models, views(templates) and controllers and register them accordingly.
// a sample controller
namespace Inc\Base;
use Inc\Base\BaseController;
class TemplateController extends BaseController
{
public $templates;
public function register()
{
if ( ! $this->activated( 'templates_manager' ) ) return;
$this->templates = array(
'page-templates/two-columns-tpl.php' => 'Two Columns Layout'
);
add_filter( 'theme_page_templates', array( $this, 'custom_template' ) );
add_filter( 'template_include', array( $this, 'load_template' ) );
}
public function custom_template( $templates )
{
$templates = array_merge( $templates, $this->templates );
return $templates;
}
public function load_template( $template )
{
global $post;
if ( ! $post ) {
return $template;
}
// If is the front page, load a custom template
if ( is_front_page() ) {
$file = $this->plugin_path . 'page-templates/front-page.php';
if ( file_exists( $file ) ) {
return $file;
}
}
$template_name = get_post_meta( $post->ID, '_wp_page_template', true );
if ( ! isset( $this->templates[$template_name] ) ) {
return $template;
}
$file = $this->plugin_path . $template_name;
if ( file_exists( $file ) ) {
return $file;
}
return $template;
}
}
wordpress
enque
// enqueue.php
namespace Inc\Base;
use Inc\Base\BaseController;
class Enqueue extends BaseController
{
public function register() {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
}
function enqueue() {
// enqueue all our scripts
wp_enqueue_script( 'media-upload' );
wp_enqueue_media();
wp_enqueue_style( 'mypluginstyle', $this->plugin_url . 'assets/mystyle.css' );
wp_enqueue_script( 'mypluginscript', $this->plugin_url . 'assets/myscript.js' );
}
}
For the front assets, we may use gulp or webpack to compile and concatenate js/scss.
// // Load Gulp...of course
const { src, dest, task, series, watch, parallel } = require('gulp');
// // CSS related plugins
var sass = require( 'gulp-sass' );
var autoprefixer = require( 'gulp-autoprefixer' );
// // JS related plugins
var uglify = require( 'gulp-uglify' );
var babelify = require( 'babelify' );
var browserify = require( 'browserify' );
var source = require( 'vinyl-source-stream' );
var buffer = require( 'vinyl-buffer' );
var stripDebug = require( 'gulp-strip-debug' );
// // Utility plugins
var rename = require( 'gulp-rename' );
var sourcemaps = require( 'gulp-sourcemaps' );
var notify = require( 'gulp-notify' );
var options = require( 'gulp-options' );
var gulpif = require( 'gulp-if' );
// // Browers related plugins
var browserSync = require( 'browser-sync' ).create();
// // Project related variables
var projectURL = 'https://wp.dev';
var styleSRC = 'src/scss/mystyle.scss';
var styleForm = 'src/scss/form.scss';
var styleSlider = 'src/scss/slider.scss';
var styleAuth = 'src/scss/auth.scss';
var styleURL = './assets/';
var mapURL = './';
var jsSRC = 'src/js/';
var jsAdmin = 'myscript.js';
var jsForm = 'form.js';
var jsSlider = 'slider.js';
var jsAuth = 'auth.js';
var jsFiles = [jsAdmin, jsForm, jsSlider, jsAuth];
var jsURL = './assets/';
var styleWatch = 'src/scss/**/*.scss';
var jsWatch = 'src/js/**/*.js';
var phpWatch = './**/*.php';
function css(done) {
src([styleSRC, styleForm, styleSlider, styleAuth])
.pipe( sourcemaps.init() )
.pipe( sass({
errLogToConsole: true,
outputStyle: 'compressed'
}) )
.on( 'error', console.error.bind( console ) )
.pipe( autoprefixer({ browsers: [ 'last 2 versions', '> 5%', 'Firefox ESR' ] }) )
.pipe( sourcemaps.write( mapURL ) )
.pipe( dest( styleURL ) )
.pipe( browserSync.stream() );
done();
}
function js(done) {
jsFiles.map(function (entry) {
return browserify({
entries: [jsSRC + entry]
})
.transform( babelify, { presets: [ '@babel/preset-env' ] } )
.bundle()
.pipe( source( entry ) )
.pipe( buffer() )
.pipe( gulpif( options.has( 'production' ), stripDebug() ) )
.pipe( sourcemaps.init({ loadMaps: true }) )
.pipe( uglify() )
.pipe( sourcemaps.write( '.' ) )
.pipe( dest( jsURL ) )
.pipe( browserSync.stream() );
});
done();
}
function reload(done) {
browserSync.reload();
done();
}
function browser_sync() {
browserSync.init({
proxy: projectURL,
https: {
key: '/home/alecaddd/.valet/Certificates/wp.dev.key',
cert: '/home/alecaddd/.valet/Certificates/wp.dev.crt'
},
injectChanges: true,
open: false
});
}
function watch_files() {
watch( phpWatch, reload );
watch( styleWatch, css );
watch( jsWatch, series( js, reload ) );
src( jsURL + 'myscript.js' )
.pipe( notify({ message: 'Gulp is Watching, Happy Coding!' }) );
};
task("css", css);
task("js", js);
task("default", series(css, js));
task("watch", parallel(browser_sync, watch_files));
This article is written based on understanding of Alecddd plugin tutorial. Many thanks for Alecddd.