Taking Control
by
, 11-09-2012 at 02:22 AM (27131 Views)
Last week, I wrote about taking a "PHP-first" approach to programming - program first, output last. But what should go first in your program?
M-V-C
You may have heard about something called "MVC" (Model-View-Controller). If not, here's your crash course:
Model
Your Model is all of the information needed/used by your program. [Hopefully], the Model is well-organized. Think of it in terms of note-taking: notes are easier to read when they're organized by subject, sub-subject, etc. than when they're just scribbled haphazardly all over the margins of your textbook.
(Yes, I know that some people - and yes, I'm one of them - actually function better with a huge glob of cryptic post-its ...or we seem to prefer things that way, at least... but we do not think like computers. Computers don't like post-its. Trust me.)
View
Your View is how all that information is supposed to be displayed. In our domain - Web Design - it's actually very easy to figure out what belongs in the View: output! If there's HTML, then it belongs in the View. Likewise, CSS, JavaScript, even HTTP Headers, etc., belong here.
Learning "PHP-first" helps by separating all of the output from the rest of the program. However, consider that the View is not the output itself, but all of the stuff that is going to be output. An example, where we show a comment on a page:
ControllerPHP Code:
<?php
// Model: the information we need
list( $message,$author,$publish_date ) = get_comment( $commentID );
// (`$commentID` is some identifier for the comment we want (e.g., a DB record id))
// View: not output *yet*, but it *will be*!
$comment = <<< HTML
<article class="comment">
<p>$message</p>
<p class="author">Published by $author on $publish_date.</p>
</article>
HTML
;
// View? no... than what?
print $comment;
The Controller is the missing part of the plan. The Controller controls everything. It's the parts of your script that check conditions, make decisions, and order things to be done. In the above example, the last line is part of the controller: it tells the script what to do (i.e., print something). There is probably some other Controller code [not shown in the example] that decided to build the comment in the first place - some sort ofif( this ){ then; }else{ that; }
.
So, any given PHP script probably has two types of Controller code:
- code that makes decisions, and
- instructions for carrying out decisions.
With a Controller, our example might look something like this:
You'll notice that there's not a lot of code inside eachPHP Code:
<?php
// controller logic (decide what to do):
// IF the user asked for a comment,
if( !empty( $_GET['comment_id'] ) ){
// procedure (instructions on what to do):
// get the comment they want.
$model = getComment( $_GET['id'] );
$view = prepareComment( $model );
// controller logic:
// OTHERWISE,
}else{
// procedure:
// show a random comment.
$model = getComment( NULL );
$view = prepareComment( $model );
}
// procedure:
print $view;if{}
andelse{}
block - just a few calls to functions. This is done on purpose. While a coder's first thought might be to put all of the instructions right inside the block where they're being used, you'll end up with lots of nested blocks that way - it quickly becomes difficult to keep track of your Controller's logic, and mistakes start showing up. Just like we're separating the PHP from its output, we'll separate the Model and View from their Controller. This has the side-effect of making the code reusable (instead of writing all the code twice, just call the function again).
This pattern repeats throughout the program. The functions used in the Controller (to create the Model and the View) contain their own Controllers:
PHP Code:
<?php
function getComment( $commentID=NULL ){
// controller: two possibilities...
// 1) we want a random comment (specified by making $commentID=NULL).
if( is_null( $commentID ) ){
/* get a random comment */
// we might have gotten this info from a database, or somewhere else -
// doesn't really matter for this example.
// What's important is, if we get the info, we return it in an array:
return array( $message,$author,$publish_date );
// 2) we want a specific comment (specified by a non-NULL $commentID).
}else{
/* get the comment with commentID = $commentID */
// again, if you find the info, return it.
return array( $message,$author,$publish_date );
}
// HA! you say. A third possibility!
// what if we wanted $commentID, but we couldn't find it?
// well, if we found it, then this function has already returned (finished).
// if not, then this last bit of code will be executed:
return FALSE;
// ...and will indicate that we didn't get what we wanted.
}
function prepareComment( $model=FALSE ){
// controller: two possibilities...
// 1) $model is an array, returned from getComment().
if( is_array( $model ) ){
// get the info from the Model
list( $message,$author,$publish_date ) = $model;
// use it to create the view
$comment = <<< HTML
<article class="comment">
<p>$message</p>
<p class="author">Published by $author on $publish_date.</p>
</article>
HTML
;
// and return the view (for the controller to print later).
return $view;
}
// 2) $model is not an array (should be FALSE, indicating the comment wasn't found)
// return an error message.
return <<< HTML
<p class="error">I'm sorry, the monkeys couldn't find that comment.</p>
HTML
;
}
Hint, Hint, Hint...
You might see some possible complications in the code above: there may be some cases where errors would creep in and break things. But error handling is a topic for another day...
PAQ (Please Ask Questions),
- Adrian