A Model, View, Controller Pattern for WordPress Plugins

The Model, View, Control (MVC) design pattern is very useful for Web development. When you follow this pattern, your applications will have 3 distinct pieces. Models will handle your program’s interactions with data storage. This is often means running a query against a database. Views present your program’s User Interface. Usually, this means rendering HTML. Finally, Controllers will act as brokers passing data between Models and Controllers. The benefit to structuring your programs this way is that it is easier to swap out User Interfaces or change databases. In this post, we’re going to look at how this pattern can be applied to WordPress Plugin development.

We’ll illustrate development with an example. Let’s build a simple Plugin to log a user in to WordPress. The goal is to give the Admin to a WordPress blog a short code that he can imbed in any page or post that will show the user a login screen if they are not logged into WordPress.

We’ll call our plugin ‘Lightbox Login’ and set up our files accordingly. We need to name our folder ‘lightbox-login/’ and our main plugin file ‘lightbox-login/lightbox-login.php’. We’ll also create an `app` folder with the sub-folders `model`, `view`, and `controller`. Our MVC class files will live here. Finally, we have a `lib` folder with a `Mixer` class in it. We’ll explain how `Mixer` is used in a bit.

lightbox-login/
              |-lightbox-login.php
              |-lightbox-login.css
              |-lightbox-login.js
              |-app/
                   |-model/
                          |-debug.model.php
                   |-view/
                         |-login.view.php
                   |-controller/
                               |-login.controller.php
              |-lib/
                   |-Mixer.php  

Let’s take a look at `lightbox-login/lightbox-login.php` first:

<?php
/*
 * Plugin Name: Lightbox Login
 * Plugin URI: http://atomicbroadcast.net/lightbox-login/
 * Description: A simple shortcode that will require a user to login to view a
 *              page.
 * Author: Andrew Dixon
 * Author URI: http://atomicbroadcast.net
 * Version: 0.1
 */
/*****************************************************************************
 * constants and globals                                                     *
 *****************************************************************************/
if (!defined( 'LIGHTBOX_URL' )) {
  define( 'LIGHTBOX_URL', plugin_dir_url( __FILE__ ) );
}
if (!defined( 'LIGHTBOX_DIR' )) {
  define( 'LIGHTBOX_DIR', dirname( __FILE__ ) );
}
/*****************************************************************************
 * include files                                                             *
 *****************************************************************************/
require_once( LIGHTBOX_DIR . '/lib/Mixer.php' );
require_once( LIGHTBOX_DIR . '/app/controller/login.controller.php' );
/*****************************************************************************
 * instantiate loginController                                               *
 *****************************************************************************/
$loginController = new LoginController();
?>

All that we’re doing here is defining a few constants, including a few files and instantiating our LoginController. So, let’s move on and take a look at the controller:

<?php
require_once( LIGHTBOX_DIR . '/app/model/debug.model.php' );
include_once( LIGHTBOX_DIR . '/app/view/login.view.php' );
/*****************************************************************************
 * class LoginController                                                     *
 *****************************************************************************/
class LoginController extends Mixer
{
  /***************************************************************************
   * __construct()                                                           *
   ***************************************************************************/
  public function __construct()
  {
    $this->addMixin( new DebugModel() );
    $this->addMixin( new LoginView() );
    add_action( 'template_redirect',
     array( $this, 'check_login' ) );
    add_shortcode( 'lightbox_login',
      array( $this, 'lightbox_login_shortcode' ) );
    add_action( 'wp_enqueue_scripts',
      array( $this, 'load_scripts' ) );
  }
 /***************************************************************************
  * check_login()                                                           *
  ***************************************************************************/
  public function check_login()
  {
     if (isset( $_POST['username'] ) && (isset( $_POST['password'] ))){
       $credentials = array(
         'user_login' => $_POST['username'],
         'user_password' => $_POST['password']
       );
       wp_signon( $credentials );
     }
  }
  /***************************************************************************
   * lightbox_login_shortcode( $atts, $content = null )                      *
   ***************************************************************************/
  public function lightbox_login_shortcode( $atts, $content = null )
  {
    $this->debug( 'lightbox_login_shortcode()' );
    if (!is_user_logged_in()){
      $this->debug( 'show login lightbox' );
      $this->render_login( $this );
    } else {
      $this->debug( 'do nothing because user is logged in' );
    }
  }
 /****************************************************************************
  * load_scripts()                                                           *
  ****************************************************************************/
  public function load_scripts()
  {
    $this->debug( 'load_scripts()' );
    wp_enqueue_script( 'jquery' );
    wp_enqueue_script( 'jquery-ui' );
    wp_enqueue_script( 'jquery-ui-dialog' );
    wp_enqueue_script( 'lightbox-login', LIGHTBOX_URL . 'lightbox-login.js' );
    wp_register_style( 'lightbox-login', LIGHTBOX_URL . 'lightbox-login.css' );
    wp_enqueue_style( 'lightbox-login' );
  }
}
?>

OK. So, there’s a lot more going on here. The first thing you’ll notice is that LoginController extends the Mixer Class (code below). Mixer is an implementation of the Mixin patter. It is used to “mix in” the function from on Class into another. It’s a good solution for our purposes because it doesn’t require us to use a newer version of php (most WordPress installs use older versions) and it gives us most of the functionality of Multiple Inheritance.

Let’s take a look at LoginController’s `__construct()` function. Notice the first line:

$this->addMixin( new DebugModel() );

Here, we’re telling the interpreter that, when it instantiates LoginController, it should look for the DebugModel class and pull all functions from DebugModel into the new instance of LoginController. Let’s take a look at DebugModel to see what new functionality we’ll have access to:

<?php
// Don't redeclare the Class if it already exists.
if (!class_exists( 'DebugModel' ))
{
  /***************************************************************************
   * class DebugModel                                                        *
   ***************************************************************************/
  class DebugModel{
   /**************************************************************************
    * debug( $message )                                                      *
    **************************************************************************/
    public function debug( $message ){
      if (WP_DEBUG_LOG === true) {
        if (is_array($message) || is_object($message)) {
          error_log( print_r( $message, true ) );
        } else {
          error_log( $message );
        }
      }
    }
  }
}
?>

So, we get a nice little function to write a debug message to the log file if WP_DEBUG_LOG is true. Notice that the debug() function actually writes to disk. Because it accesses permanent storage, it belongs in a Model according to the MVC pattern. The benefit here is that we’ve separated the debug() function from the logic in our controller. If, in the future, we decide that writing to disk isn’t good enough and we also want to email the debug message to an administrator, we only need to update the DebugModel and the rest of our plugin remains unchanged.

The rest of the __construct() function “mixes in” the LoginView and then hooks the newly created LoginController object into the WordPress core. The add_shortcode() function tells WordPress to “listen” for a [lightbox-login] shortcode. When it sees this code, the lightbox_login_shortcode() function will be executed.

Inside lightbox_login_shortcode() we’ll check to see if the user is NOT logged in.

if (!is_user_logged_in()){

if not, we want to render our login page:

      $this->debug( 'show login lightbox' );
      $this->render_login( $this );

If the user is logged in, we don’t do anything and just let them see the page:

    } else {
      $this->debug( 'do nothing because user is logged in' );
    }

Remember that we only want to display anything to the user in a View. So, the render_login() function needs to live in the LoginView Class:

<?php
/*****************************************************************************
 * class LoginView                                                           *
 *****************************************************************************/
class LoginView
{
 /****************************************************************************
  * render_login( $controller )                                              *
  ****************************************************************************/
  public function render_login( $controller )
  {
    $controller->debug( 'render_login( $controller )' );
    ob_start();
    ?>
      <div class='dialog app'
           title='<h1>Login</h1><h2>Please log in</h2>'>
        <div class='panel'>
          <form name='lightbox-login-form'
                id='lightbox-login-form'
                method='POST'>
            <article>
              <div class='username'>
                <label for='username'>User Name:</label>
                <input id='username'
                       name='username'
                       type='text'
                       value=''
                       placeholder='User Name'>
                <label for='password'>Password:</label>
                <input id='password'
                       name='password'
                       type='password'
                       value=''
                       placeholder='Password'>
              </div>
            </article>
            <footer>
              <button id='lightbox-login-submit'
                      class='blue submit'>
                <span>Login</span>
              </button>
            </footer>
          </form>
        </div>
      </div>
    <?php
    echo ob_get_clean();
  }
}
?>

Now, with a little javascript magic, and the jquery-ui-dialog plugin, we’re going to tell the browser to render a “dialog” class as a lightbox. WordPress comes with jquery, jquery-ui, and jquery-ui-dialog but we do have to load them in the LoginController. Once that is done, we can include a customer javascript to configure jquery-ui-dialog:

jQuery(document).ready(function() {
  var dialogOpts = {
    autoOpen: true,
    minHeight: 'auto',
    modal: true
  }
  jQuery('.dialog').dialog(dialogOpts);
  jQuery('#lightbox-login-form').submit(function(event) {
    jQuery('.dialog').dialog('close');
  });
});

And we have a login form that pops up in a lightbox if the user is not logged in. The last thing to do is log the user in when the form is submitted. For this we use the wp_signon() function. This presents one last problem as we can’t use this function in the lightbox_login_shortcode() function because wp_signon() needs to be run before we output any headers. The solution is to add a “template_redirect” to the WordPress core that checks to see if our POST vars are set (i.e. it checks to see if our login form was submitted).

  public function check_login()
  {
     if (isset( $_POST['username'] ) && (isset( $_POST['password'] ))){
       $credentials = array(
         'user_login' => $_POST['username'],
         'user_password' => $_POST['password']
       );
       wp_signon( $credentials );
     }
  }

And we hook the function check_login() into the WordPress core in the LoginController __construct() function:

add_action( 'template_redirect',
     array( $this, 'check_login' ) );

And that’s it! We’ve not got a working shortcode that will propt a user to login to any page or post that we want. Of course, this plugin isn’t very robust. The first thing to note is that we’re sending usernames and passwords in plain text over the Internet. That’s just bad. The second thing is that the user can just close the form instead of logging in. So, please, only use this plugin as an example and don’t put it on a live site :)

If you want to see this plugin in action. Check out this page.

You can log in with the username: “test” and password: “test”

And, you can download the source here.

Mixer Class magic. This post is already too long so I won’t go into how the Mixer Class works in this post but maybe I’ll write about it in a future post.

<?php
// Don't redeclare the Class if it already exists.
if (!class_exists( 'Mixer' ))
{
abstract class Mixer {

  protected $methods = array();
  protected $mixins = array();
  protected $priorities = array();

  /**
   ** @description By adding a mixin, the extending class will automatically adopt all of a mixin's methods.
   ** @param object $mixin - The instantiated object that's methods should be adopted by the extending class.
   ** @security You are expected to know if there will be a conflict in which two mixins share the same method name. If
   **      this conflict occurs, use the <a href="#setPriorities">setPriorities()</a> method to specify which method
   **      should take precedence. Mixer will not handle the conflict automatically on its own.
   **/
  public function addMixin($mixin){
    if (!is_object($mixin)){
      throw new Exception("The mixin is not valid because it is not an object.");
    }
    $name = get_class($mixin);
    $this->mixins[$name] = $mixin;
    $methods = get_class_methods($name);
    $this->methods[$name] = $methods;
  }

  /**
   ** @description Allows multiple mixins to be added at once. By adding a mixin, the extending class will
   **      automatically adopt all of a mixin's methods.
   ** @param array $mixin - An array of instantiated objects whose methods should be adopted by the extending class.
   ** @security You are expected to know if there will be a conflict in which two mixins share the same method name. If
   **      this conflict occurs, use the <a href="#setPriorities">setPriorities()</a> method to specify which method
   **      should take precedence. Mixer will not handle the conflict automatically on its own.
   **/
  public function addMixins(array $mixins){
    foreach ($mixins as $mixin){
      $this->addMixin($mixin);
    }
  }

  /**
   ** @description Gets the class's current mixins by name
   ** @return An array of mixin class names.
   **/
  public function getMixins(){
    return array_keys($this->methods);
  }

  /**
   ** @description Manages conflicts for the mixins.
   ** @param array $priorities - The method name as the key, the class name that has priority in a conflict as
   **      the value.
   ** @note Once a method has been assigned to a class, it cannot be reassigned to a different class at a later point.
   **      This is done to minimize potential bugs due to dynamic prioritization.
   **/
  public function setPriorities(array $priorities){
    $classNames = array_keys($this->methods);
    $setPriorities = array_keys($this->priorities);
    foreach ($priorities as $method=>$class){
      if (!in_array($class, $classNames)){
        throw new Exception("$class is not a valid mixin. To make $class a mixin, use the addMixin method.");
      }
      if (!in_array($method, $this->methods[$class])){
        throw new Exception("$class does not have a method named '$method'.");
      }
      if (in_array($method, $setPriorities)){
        $assigned = $this->priorities[$method];
        throw new Exception("$method has already been assigned to $assigned and cannot be reassigned.");
      }
    }
  $this->priorities = $priorities;
  }

  /**
   ** @description A magic method that calls the mixin methods automatically. This method should not be
   **      called directly.
   ** @param string $methodName - The name of the mixin method
   ** @param array $arguments - The arguments for the method
   ** @return The return value will vary depending on the function called.
   **/
  public function __call($methodName, array $arguments){
    foreach ($this->methods as $className=>$methods){
      if (in_array($methodName, $methods)){
        if (
           (in_array($methodName, array_keys($this->priorities))) &&
           ($className == $this->priorities[$methodName])
        ){
          return call_user_func_array(array($className, $methodName), $arguments);
        } else if (!in_array($methodName, array_keys($this->priorities))){
          return call_user_func_array(array($className, $methodName), $arguments);
        }
      }
    }
    $mixins = (sizeof($this->methods) > 0) ? implode(', ', array_keys($this->methods)) : 'No mixins are listed.';
    throw new Exception("$methodName is not a method. Your current mixins are: $mixins");
  }
}
}
?>
Posted in Uncategorized
2 comments on “A Model, View, Controller Pattern for WordPress Plugins
  1. test says:

    Does this force everyone else to login twice?

  2. laxman says:

    this tutorial is very help full,for beginners.
    thanking you,..

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>