Implementing the MVC Pattern in WordPress Plugins

Follow along at:
http://iandunn.name/wp-mvc

Intended Audience

  • Mid-level plugin developers.
  • Many principles/techniques can be used in theme development too.

Interaction

  • Ask questions if anything's unclear
  • Jump in with comments, tips, etc

Overview

  • Intro to MVC
  • Implementing it in WordPress plugins
  • Browsing WordPress Plugin Skeleton
  • Closing
  • Q&A at the end of each section

MVC: Distilled

This is bad:

<html>
<head><style type="text/css">
body { background-color: #E8E8E8; font-family: Arial; }
</style></head>
<body>
<h1>Dolor Sit Ipsum Lorem Amet</h1>
<?php
$x = get_some_data( $y );
$z = mysql_query( "SELECT * FROM foo WHERE z = ". $z );

echo '<div>z:'. $z[ 'foo' ] .'</div>';
?>
<script type="text/javascript">
function foo() {
if ( bar ) alert( 'bar' );
else alert( 'foo' );
}
</script>
</body></html>

MVC: Distilled

This is better:

<?php

function controller() {
$x = get_some_data( $y );
$z = model( $x );
require( '/views/foo.php' );
}

function model( $x ) {
$z = mysql_query( "SELECT * FROM foo WHERE z = ". (int) $z );
return $z;
}

?>

MVC: Distilled

This is better:

<html>
<head>
<style type="text/css">
<link rel="stylesheet" type="text/css" href="/css/main.css" />
</style>
</head>

<body>
<h1><?php echo $heading; ?></h1>

<div>z: <?php echo $z[ 'foo' ]; ?></div>

<script type="text/javascript" src="/javascript/main.js"></script>
</body>
</html>

MVC: Why Should I Care?

  • Organization and readability
  • Flexability and code re-use
  • Ease of maintenance
  • Programming is a craft

MVC: What Is It?

  • Stands for Model-View-Controller
  • It's a design pattern
  • Separation of concerns and code re-use
  • Dominant pattern for web applications

MVC: What Does It Look Like?

Separates components into 3 parts
  • Views: The user interface
  • Models: The business logic layer
  • Controllers: The application logic layer

MVC: What Does It Look Like?

Basic MVC request

Image: Understanding Model-View-Controller

MVC: What Does It Look Like?

MVC File Structure

MVC: Views

  • Only the presentation of the data - HTML/CSS
  • Use inline PHP, but sparingly
  • No data manipulation

MVC: Views

/views/class-name/metabox-example.php

<p>Meta box introduction / user instructions</p>

<table>
  <tr class="alternate-row">
    <th>Example Box Field:</th>
    <td>
      <input
        name="example-box-field"
        type="text"
        value="<?php echo esc_attr( $example_box_field ); ?>" />
      </td>
  </tr>
</table>

MVC: Models

  • Data layer
  • Handles business/domain logic
  • Does the heavy lifting

MVC: Models

/models/shopping-cart.php

function calculate_tax( $items, $zip_code ) {
	$tax = 0.0;
	$tax_rate = mysql_fetch_assoc( mysql_query("
		SELECT rate
		FROM tax_rates
		WHERE zip_code = " . (int) $zip_code
	) );
	
	foreach( $items as $item )
		$tax += $item->price * $tax_rate[ 'rate' ];
	
	return $tax;
}

MVC: Controllers

  • Direct traffic within the application
  • Collect data from models, pass to views
  • Doesn't maniuplate the data

MVC: Controllers

/controllers/custom-post-type.php

function markup_meta_boxes( $post, $box ) {
	$example_box_field = get_post_meta(
		$post->ID, 'example-box-field', true
	);
	
	require_once '/views/class-name/metabox-example-box.php';
}

MVC

Fat Models, Skinny Controllers

Good Cake, Bad Cake

Image: MVC - Fat Models and Skinny Controllers

MVC

Any questions / Comments / Tips?

WP + MVC: Introduction

  • Now we can move on to implementing MVC in plugins

WP + MVC: Views

  • Put all HTML/CSS/JavaScript in separate files
  • Use wp_enqueue_script() and wp_enqueue_style()

WP + MVC: Views

  • Include HTML files from a controller function instead of echo'ing
public function markup_meta_box( $post ) {
	$example_box_field = get_post_meta( $post->ID, 'example-box-field' );
	
	require_once( dirname( __DIR__ ) . '/views/class-name/metabox-example-box.php' );
}

WP + MVC: Views

  • Use output buffering to return a view file as a string
public function shortcode_example( $attributes ) {
	ob_start();
	require_once( dirname( __DIR__ ) . '/views/shortcode-example.php' );
	$output = ob_get_clean();
	
	return $output;
}

WP + MVC: Views

  • Combine multiple items into a single controller and view

WP + MVC: Views

public function markup_fields( $field ) {
	switch( $field[ 'label_for' ] ) {
		case 'field-example1':
			$foo = '123';
		break;
		
		case 'field-example2':
			$bar = 'abc';
		break;
	}
	
	require( dirname( __DIR__ ) . '/views/wpps-settings/page-settings-fields.php' );
}

WP + MVC: Views

<?php if( $field[ 'label_for' ] == 'field-example1' ) : ?>

	<input id="field-example1" name="field-example1" class="regular-text" value="<?php esc_attr_e( $this->settings[ 'field-example1' ] ); ?>" />
	<span class="example"> Example value</span>

<?php elseif( $field[ 'label_for' ] == 'field-example2' ) : ?>

	<textarea id="field-example2" name="field-example2" class="large-text" /><?php echo esc_textarea( $this->settings[ 'field-example2' ] ); ?></textarea>
	<p class="description">This is an example of a longer explanation.</p>

<?php endif; ?>

WP + MVC: Models/Controllers

  • Break them into separate classes
  • Or use unofficial @mvc tag in phpDoc to keep them straight

WP + MVC: Models/Controllers

/**
* Validates submitted setting values before they get saved to the database. Invalid data will be overwritten with defaults.
* @mvc Model
* @author Ian Dunn <ian@iandunn.name>
* @param array $newSettings
* @return array
*/
public function validate_settings( $new_settings ) {
	// ...
	
	return $new_settings;
}		

WP + MVC: Models

  • Called by controllers or other models
  • Filter callbacks are usually models
public static function add_custom_cron_intervals( $schedules ) {
	$schedules[ 'ten_minutes' ] = array(
	'interval'	=> 60 * 10,
	'display'	=> 'Every 10 minutes'
	);
	
	return $schedules;
}
add_filter( 'cron_schedules', __CLASS__ . '::add_custom_cron_intervals' );

WP + MVC: Controllers

  • Action callbacks are usually controllers
public static function save_user_fields( $userID ) {
	$userFields = self::validate_user_fields( $userID, $_POST );
	
	update_user_meta( $userID, 'user-example-field1', $userFields[ 'user-example-field1' ] );
	update_user_meta( $userID, 'user-example-field2', $userFields[ 'user-example-field2' ] );
}
add_action( 'save_post', __CLASS__ . '::save_user_fields' );

WP + MVC

Any questions / Comments / Tips?

WP + MVC: Plugin Skeleton

Takeaways: MVC

  • HTML/CSS/JavaScript in separate files from PHP (and each other)
  • Application logic and business logic in separate functions

Takeaways: WP + MVC

  • Action callbacks are usually controllers
  • Filter callbacks are usually models
  • Output buffering to capture views into a string
  • Single callback/view to markup settings, etc

Additional Resources

MVC in General

MVC in WordPress

Criticism / alternatives

Starters / Examples

Q&A

Any questions / Comments / Tips?