WordPress
Article

Getting Started with WordPress MVC

By Wern Ancheta

In WordPress we often have to stick with plain PHP when creating plugins. This leads to PHP and HTML code getting mixed up in a single page, which isn’t a good practice. Separation of concerns is a design principle that states that we should split up a program into different parts, namely the logic and the presentation. In this tutorial, we will be taking a look at one solution to this problem: WordPress MVC. It’s a plugin that adds MVC support to WordPress so we can write our plugins the MVC way.

What Is MVC?

Before we move on, it’s important that we are all on the same page. If you already know what MVC is, feel free to skip to the next section.

Ok so what is MVC? MVC stands for Model View Controller. The Model represents the data that our application utilizes. It’s the part that does the talking to the database. The View deals with the presentation. It’s where we put in HTML code and basic presentation logic. Lastly there’s the Controller whose main job is to tie those two together. Examples include validating and sanitizing user input. It’s basically responsible for controlling the overall flow of the application.

Installation and Setup

WP MVC is a plugin which we need to install in order to make MVC work with WordPress. You can download the plugin here and install it like you would usually install a plugin in WordPress. Once that’s done, login to WordPress and activate it from your plugins page.

Building a Plugin the MVC Way

Before we move on, I’ll give you a brief overview of what we will be building in this tutorial. We will build a plugin that will list out all the anime that is created in the admin side of the website. Pretty much like what this site does. In the admin side, we will have an interface where we can add, list, edit or delete anime shows. In the public side, we will have them presented in a grid view in a specific page.

Now we’re ready to build a new plugin. You can do that by navigating to the path where you installed the WP MVC plugin.

cd path/to/plugins/wp-mvc

Then add execution permissions to the wpmvc file. This is the file that we will be using to generate a new plugin.

chmod +x wpmvc

Next, we can now generate a new plugin. Execute the following command to do that.

./wpmvc generate plugin AnimeList

This will create a new plugin under the wp-content/plugins directory of your WordPress installation. For me, it created an anime-list directory. Open that directory and then open the anime_list_loader.php file. This file contains the functions that will get executed upon activating or deactivating the plugin. As we will need to save a lot of custom data, we will have to create a custom table instead of using the options API to store things in the database. To do that, we have to add the code that will create the new table upon plugin activation. Add the following code inside the activate method.

global $wpdb;

$sql = '
CREATE TABLE '. $wpdb->prefix . 'animelists (
  id int(11) NOT NULL auto_increment,
  title varchar(255) NOT NULL,
  poster varchar(255) NOT NULL,
  plot TEXT NOT NULL,
  genres TEXT default NULL,
  PRIMARY KEY  (id)
)';

dbDelta($sql);

In the code above, we are creating a table which has the id, title, poster, plot and genres field using a raw SQL query. The dbDelta function is then used to execute the SQL query.

Next, under the deactivate method, we have to clean up our mess. The code below removes the table from the WordPress database.

require_once ABSPATH.'wp-admin/includes/upgrade.php';

global $wpdb;

$sql = 'DROP TABLE ' . $wpdb->prefix . 'anime_lists';
$wpdb->query($sql);

Normally this isn’t the way you would like to do it. Some users might still need the data that your plugin has acquired over time. Even if they deactivate your plugin. But to keep things simple, we won’t handle that here.

Now is a good time to activate your new plugin from the WordPress admin page. If everything worked correctly, that should have created a wp_anime_lists table in your WordPress database.

Next, execute the following command:

./wpmvc generate scaffold AnimeList AnimeList

The command above generates the views, controllers and model for the model that you specify. The first AnimeList is the name of the plugin and the second one is the name of the model. Note that wpmvc uses a naming convention here. A model should always be in singular form and the table is plural form. And every capital letter in the name of the model means that it should be separated with underscores. The name of the model should be based on the name of the table. So using the rules above, if the name of the table is anime_lists, the model should be named AnimeList. Underscores are turned into CamelCasing and plural is converted to singular form.

Next, open up the add.php file and edit.php under the app/views/admin/anime_lists/ and add the following code:

<h2>Add Anime List</h2>

<?php echo $this->form->create($model->name); ?>
<?php echo $this->form->input('title'); ?>
<?php echo $this->form->input('poster'); ?>
<?php echo $this->form->input('plot'); ?>
<?php echo $this->form->input('genres'); ?>
<?php echo $this->form->input('producer'); ?>
<?php echo $this->form->end('Add'); ?>

On the edit.php file:

<h2>Edit Anime List</h2>

<?php echo $this->form->create($model->name); ?>
<?php echo $this->form->input('title'); ?>
<?php echo $this->form->input('poster'); ?>
<?php echo $this->form->input('plot'); ?>
<?php echo $this->form->input('genres'); ?>
<?php echo $this->form->input('producer'); ?>
<?php echo $this->form->end('Update'); ?>

What we did above was to create the forms for adding new anime shows and editing existing one’s. This utilizes the form helpers which is built-in to wpmvc. To break it down, first we create a new form and then supply the name of the model as its argument. In this case the name of the model is AnimeList.

<?php echo $this->form->create($model->name); ?>

Next, we output each of the columns that we have added in the table using the input method. This method takes up the name of the field as its first argument. By default, wpmvc determines what type of field it would output by checking the data type. So if the data type is varchar, it will output a text input. If the data type is text it will output a textarea and so on.

<?php echo $this->form->input('title'); ?>
<?php echo $this->form->input('poster'); ?>
<?php echo $this->form->input('plot'); ?>
<?php echo $this->form->input('genres'); ?>
<?php echo $this->form->input('producer'); ?>

If you want to use another input type, you can specify an array containing the type of input as a second argument:

<?php echo $this->form->input('is_awesome', array('type' => 'checkbox')); ?>

Finally, we close off the form using the end method. This takes up the label of the button as its argument.

<?php echo $this->form->end('Add'); ?>

At this point we can now add a few anime shows. WP MVC automatically handles adding a new menu on the WordPress dashboard named after the name of the model. In this case the name of the new menu should be ‘Anime Lists’. From there you can start adding new items using the ‘add new’ sub-menu.

Next we need to update the code which lists the existing items. You can find it on the following path:

app/controllers/admin/admin_anime_lists_controller.php

By default, it contains the following code:

<?php

class AdminAnimeListsController extends MvcAdminController {

    var $default_columns = array('id', 'name');

}

?>

This results to an error returned for every row in the table since we don’t have a name field in the anime_lists table. To solve this issue, all we have to do is use the fields that we have on the wp_anime_lists table:

var $default_columns = array('id', 'title', 'genres', 'producer');

Once you’ve updated the file, the result should now look like this:

Anime Lists

Now we can proceed with the public facing side of the website.

Before we move on, it’s important to know that every time we use the command line to generate models, controllers and views. WP MVC also assigns a new page for that model. So for the AnimeLists model, it creates the anime_lists page. Don’t forget to enable mod_rewrite in your Apache configuration, add the WordPress .htaccess file, and set the permalinks settings to use post name.

Permalinks

For your convenience, here’s the .htaccess file that I’m using:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /WordPress/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /WordPress/index.php [L]
</IfModule>

# END WordPress

Once you’re done with that, you can now check if you can access the page. By default, you wouldn’t see anything in there. That’s what were going to work on.

http://localhost/WordPress/anime_lists/

First, open the app/controllers/anime_lists_controller.php file. This is the controller for the anime_lists page. By default, it should contain the following code:

<?php

class AnimeListsController extends MvcPublicController {

}

?>

This is fine if you want to stick with the defaults that are added from the base public controller (MvcPublicController). But if you want to customize things a little bit, you have to override the index method with the following:

public function index() {

    $params = $this->params;
    $params['per_page'] = 6;
    $collection = $this->model->paginate($params);
    $this->set('objects', $collection['objects']);
    $this->set_pagination($collection);

}

What we’re doing above is to get the default parameters supplied in the base controller by using $this->params. We then assign it to a variable so that we can override the default values. By default, the controller selects 10 items from the database per page. If I only want to select 6, I can do that by setting the per_page parameter to 6:

$params['per_page'] = 6;

That’s really all we need to customize. The rest of the code just creates a collection using the paginate method in the model. This collection is then used to pass the ‘objects’ (anime shows), and then set the pagination into the view.

$collection = $this->model->paginate($params);
$this->set('objects', $collection['objects']);
$this->set_pagination($collection);

Now open up the view that’s responsible for rendering the anime_lists page. It’s at app/views/anime_lists/index.php. Once opened, add the following code:

<h2>Anime Lists</h2>

<div id="anime-shows">
    <?php foreach ($objects as $object): ?>

        <?php 
        $this->render_view('_item', array('locals' => array('object' => $object))); 
        ?>

    <?php endforeach; ?>
</div>

<div id="pagination">
    <?php echo $this->pagination(); ?>
</div>

This loops through all the objects that we have passed earlier from the controller. Inside the loop, we render the view which displays the details for each object. The render_view method takes up the name of the view as its first argument, and the data that we want to pass in as the second.

<?php 
$this->render_view('_item', array('locals' => array('object' => $object))); 
?>

Finally, we output the pagination.

<?php echo $this->pagination(); ?>

Next, open up the _item.php file on the same directory and then add the following code:

<div class="anime-show">
    <div class="anime-poster-container">
        <a href="<?php echo mvc_public_url(array('controller' => 'anime_lists', 'id' => $object->id)); ?>">
            <img src="<?php echo $object->poster; ?>" class="anime-poster">
        </a>
    </div>
    <div>
        <strong><?php echo $object->title; ?></strong>
    </div>
    <div>
        <?php echo $object->producer; ?>
    </div>
    <div class="genre">
        <small><?php echo $object->genres; ?></small>
    </div>
</div>

This shows the details for each object. As you can see from the above code, we can directly access each of the fields from the $object variable. We’re also using a helper function called the mvc_public_url to generate a URL which points to the individual pages for each object. This method takes up an array which contains the name of the controller and the ID of the object.

<?php 
echo mvc_public_url(array('controller' => 'anime_lists', 'id' => $object->id)); 
?>

This generates a URL similar to the following:

http://localhost/WordPress/anime_lists/2/

Next, we also have to update the individual object page. To do that, open up the show.php file. Still on the same directory.

<p>
    <?php 
    echo $this->html->link('&#8592; All Anime Lists', array('controller' => 'anime_lists')); 
    ?>
</p>
<div id="anime-show">
    <div class="anime-poster-container">
        <img src="<?php echo $object->poster; ?>" class="anime-poster">
    </div>
    <div>
        <strong><?php echo $object->title; ?></strong>
    </div>
    <div>
        <?php echo $object->producer; ?>
    </div>
    <div class="genre">
        <small><?php echo $object->genres; ?></small>
    </div>
    <div class="plot">
        <small><?php echo $object->plot; ?></small>
    </div>
</div>

Not much difference here. It’s basically the same as the output in the previous view. Only this time we also output the plot.

<div class="plot">
    <small><?php echo $object->plot; ?></small>
</div>

We are also adding a link to the main anime_lists page:

<?php 
echo $this->html->link('&#8592; All Anime Lists', array('controller' => 'anime_lists')); 
?>

To make things a little bit pleasing to the eye. Let’s add some css for the public facing side of the website. You can add stylesheets on the app/public/css directory. Just name the file anime-lists.css and add the following code:

#anime-shows {
    overflow: auto;
    padding: 20px;
}

.anime-show {
    float: left;
    width: 227px;
    margin: 10px;
    height: 470px;
    font-size: 15px;
}

.anime-poster-container {
    height: 332px;
    overflow: hidden;
}

#anime-show {
    padding: 20px;
    width: 500px;
}

.plot {
    margin-top: 10px;
    font-size: 16px;
}

.genre {
    color: gray;
}

#pagination {
    padding: 20px;
}

#pagination .page-numbers {
    padding: 0 10px;
}

In order to use the stylesheet that we’ve just created. Create a bootstrap.php file under the app/config directory of your plugin. Then we add the following code:

<?php
add_action( 'wp_enqueue_scripts', 'animelists_enqueue_scripts' );

function animelists_enqueue_scripts($options) {
    wp_register_style('animelists_style', mvc_css_url('anime-list', 'anime-lists.css'));
    wp_enqueue_style('animelists_style');
}

The code above should look familiar. It’s how we usually add custom scripts and styles to WordPress. Only this time we’re using another helper function called mvc_css_url. This function takes up the machine-friendly name of the plugin (Hint: copy the folder name of your plugin) and the file name of the stylesheet.

Once you’re done with that, and you’ve added some items in the admin side. The final output should look like this:

Public

You can check out the code used in this plugin on this GitHub Repo.

Conclusion

That’s it! In this tutorial you have learned about how to implement MVC in WordPress by creating a plugin which utilizes it. We have only scratched the surface in this tutorial. Be sure to check out the WP MVC documentation to learn more. How about you? Do you know or use any other MVC solutions for WordPress? Let us know in the comments.

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

  • http://wpviz.com/ Hammad Afzal

    nice introduction about the wordpress mvc. today I learn something new.

    Regards

  • Mike Ritter

    You lost me at “Plugin”. From here I argue my client is better served if I begin with a robust framework or stretch WP’s built-in functions and refactor my code for extensibility as WP matures. Like CPT plugins, this adds a crutch that threatens the viability of my project.

    • Prince Oluwasegun Abisagbo

      i agree… doesnt make sense to use a plugin

  • http://akemi-mokoto.me/ Akemi Mokoto

    I approve!

  • http://Art-iDesenvolvimento.com.br/?utm_source=checking_whether_you_reach_me_through_disqus&utm_medium=disqus&utm_campaign=checking_whether_you_reach_me_through_disqus Luis Eduardo Braschi

    The table created should be named like this:

    $wpdb->prefix . ‘anime_lists

    Note the underscore.

    • http://Art-iDesenvolvimento.com.br/?utm_source=checking_whether_you_reach_me_through_disqus&utm_medium=disqus&utm_campaign=checking_whether_you_reach_me_through_disqus Luis Eduardo Braschi

      And you are missing the field “producer.”

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in WordPress, once a week, for free.