WordPress Plugin: How do I avoid “tight coupling”?

2019-01-30 00:30发布

问题:

I am working on a WordPress Plugin and am trying to ensure best practices. I have two classes, my plugin class "Jargonaut" which is required and then another class called "Dictionary" which is included with require_once() into my main plugin file.

Most of the code in the Jargonaut class addresses initialization and provides controller-like functionality but much of it is highly dependent upon using the Dictionary object (i.e. tightly coupled from my understanding of the term). I wish to keep the Dictionary class separated as it is acting more like a model (in MVC architecture) and interfaces with my database.

I see a lot of gray area in the tight vs. loose coupling and am having a hard time deciding how much is too much?

回答1:

If your plugin needs the dictionary object, it has to ask for it:

class MyPlugin
{
    /**
     * @var Dictionary
     */
    private $dictionary;
    private function __construct(Dictionary $dictionary)
    {
        $this->dictionary = $dictionary;
    }

You now have loosely coupled your plugin with the Dictionary, the plugin class is not responsible any longer to create the Dictionary for itself, because it's injected. It takes what it needs.

So how would that work? The plugin needs to be created somewhere, so this needs a factory. The factory build method knows what your plugin needs:

class MyPluginFactory
{
    public static function build($pluginName)
    {
        $plugin = NULL;
        switch($pluginName)
        {
            case 'MyPlugin':
                $dictionary = new Dictionary();
                $plugin = new MyPlugin($dictionary);
        }
        return $plugin;
    }
}

As this is wordpress we know that the bootstrapping of the plugin is done by including the plugin file. So at it's beginning, the plugin needs to be created. As includes are done in the global scope we want to preserve the plugin object in memory but without being available as a global variable probably. This does not prevent you from creating more than one plugin instance, but it will ensure that when wordpress initializes your plugin (loads your plugin), it will make only use of that single instance. This can be done by making the plugin factory some additional function:

class MyPluginFactory
{
    ...
    public static $plugins;
    public static function bootstrap($pluginName)
    {
        $plugin  = self::build($pluginName);
        self::$plugins[] = $plugin;
        return $plugin;
    }

Take care here, that the only usage of the static class member variable is only to ensure that the plugin stays in memory. It technically is a global variable we normally want to prevent, however, the instance needs to be stored somewhere, so here it is (I changed this to public because it is a global variable and it shouldn't be shy about it. Having a public can help in some circumstances in which private or protected are too restrictive. Also it shouldn't be a problem. If it is a problem, there is another problem that should be fixed first).

This basically decouples your plugin code from wordpress itself. You might want to also create a class that offers and interface to any wordpress function you're making use of, so you're not bound to these functions directly and your plugin code stays clean and loosely coupled to wordpress itself.

class WordpressSystem
{
    public function registerFilter($name, $plugin, $methodName)
    {
        ... do what this needs with WP, e.g. call the global wordpress function to register a filter.
    }
    ...
}

Then add it as a dependency again if your plugin needs the WordpressSystem to perform tasks (which normally is the case):

class MyPlugin
{
    ...
    public function __construct(WordpressSystem $wp, Dictionary $dictionary)
    ...

So to finally wrap this up, only the plugin php file is needed:

<?php
/*
 * MyPlugin
 * 
 * Copyright 2010 by hakre <hakre.wordpress.com>, some rights reserved.
 *
 * Wordpress Plugin Header:
 * 
 *   Plugin Name:    My Plugin
 *   Plugin URI:     http://hakre.wordpress.com/plugins/my-plugin/
 *   Description:    Yet another wordpress plugin, but this time mine
 *   Version:        1.2-beta-2
 *   Stable tag:     1.1
 *   Min WP Version: 2.9
 *   Author:         hakre
 *   Author URI:     http://hakre.wordpress.com/
 *   Donate link:    http://www.prisonradio.org/donate.htm
 *   Tags:           my
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
Namespace MyPlugin;

# if your file is named 'MyPlugin.php' this will be 'MyPlugin'.
return PluginFactory::bootstrap(basename($plugin, '.php'));

class PluginFactory
{
    private static $plugins;
    public static function bootstrap($pluginName)
    {
        $plugin = self::build($pluginName);
        self::$plugins[] = $plugin;
        return $plugin;
    }
    public static function build($pluginName)
    {
        $plugin = NULL;
        switch($pluginName)
        {
            case 'MyPlugin':
                # Make your plugin work with different Wordpress Implementations.
                $system = new System\Wordpress3();
                $dictionary = new Dictionary();
                $plugin = new Plugin($system, $dictionary);
        }
        return $plugin;
    }
}

class Plugin
{
    /**
     * @var System
     */
    private $system;
    /**
     * @var Dictionary
     */
    private $dictionary;
    private function __construct(System $system, Dictionary $dictionary)
    {
        $this->system = $system;
        $this->dictionary = $dictionary;
    }

...

The bootstrap method can also take care of registering an autoloader or do the requires.

Hope this is useful.