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?
MVC: What Does It Look Like?
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
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
- Implemented these ideas in WordPress Plugin Skeleton
- Browse code
- Fork on GitHub
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
- Understanding Model-View-Controller from the CakePHP Cookbook
- Understanding Model-View-Controller by Jeff Atwood
- Understanding MVC in PHP by Joe Stump
MVC in WordPress
- MVC in WordPress by Brian Zeligson
- An MVC Inspired Approach to WordPress Plugin Development by Daryl Lozupone
- Easily add a touch of MVC to WordPress Plugins by Daniel Auener
- MVC-based WordPress Plugins by Jesse Hanson
Criticism / alternatives
- MVC: No Silver Bullet by Andy Wardley
- Architecture more suitable for web apps than MVC? at StackOverflow
- Alternative patterns for web development? (non-MVC) at Programmers - Stack Exchange
- MVC Obscures the Mechanics of the Web by Ian Davis
Starters / Examples
- WordPress Plugin Boilerplate by Tom McFarlin
- WP MVC by Tom Benner
- Tagregator by Ian Dunn
- swpMVC by Streetwise Media
- WordPress Plugin Skeleton by Ian Dunn
- Tina MVC by Francis Crossen
- WordPress Plugin Kickstarter by Vasyl Martyniuk
- PW_Archives by Phillip Walton