分类:
2009-04-01 08:59:55
In the first part of this series, I covered the and created most of the base classes. In Part 2, I will be covering the controller and the presentation layer. (Parts three and four, and are also available.)
Quite simply, the controller handles incoming requests. It uses input, in this case from the URI, to load a module and refresh/render the presentation layer. The controller for the aptly named Framework 1.0 uses a few GET
arguments to figure out which module to load.
Before I get to the code, consider a possible request that the controller is going to have to be able to handle.
That looks simple enough. However, seeing that the code now runs inside a framework, things are not as simple as they may seem. Here is a simple list of arguments that the controller understands and what they do.
module
defines the basic module that the user is requesting. For instance, you may choose to create a module named users
that stores your code for logging in, logging out, and registering.
class
defines the actual class the controller will load. For instance, in your users
module you might have classes named login
, logout
, and register
. If you don't specify a class, the controller will attempt to load one with the same name as the module provided.
event
defines which method to run after the controller has authenticated the user. By default, the controller will look for and run the __default()
method of your class. A more complex example of what the controller might handle is:
This tells the controller to locate the module users
, load the class login
, and, because there is no event defined, run login::__default()
.
* @copyright Joe Stump
* @license
* @package Framework
*/
require_once('config.php');
// {{{ __autoload($class)
/**
* __autoload
*
* Autoload is ran by PHP when it can't find a class it is trying to load.
* By naming our classes intelligently we should be able to load most classes
* dynamically.
*
* @author Joe Stump
* @param string $class Class name we're trying to load
* @return void
* @package Framework
*/
function __autoload($class)
{
$file = str_replace('_','/',substr($class,2)).'.php';
require_once(FR_BASE_PATH.'/includes/'.$file);
}
// }}}
if (isset($_GET['module'])) {
$module = $_GET['module'];
if (isset($_GET['event'])) {
$event = $_GET['event'];
} else {
$event = '__default';
}
if (isset($_GET['class'])) {
$class = $_GET['class'];
} else {
$class = $module;
}
$classFile = FR_BASE_PATH.'/modules/'.$module.'/'.$class.'.php';
if (file_exists($classFile)) {
require_once($classFile);
if (class_exists($class)) {
try {
$instance = new $class();
if (!FR_Module::isValid($instance)) {
die("Requested module is not a valid framework module!");
}
$instance->moduleName = $module;
if ($instance->authenticate()) {
try {
$result = $instance->$event();
if (!PEAR::isError($result)) {
$presenter = FR_Presenter::factory(
$instance->presenter,$instance
);
if (!PEAR::isError($presenter)) {
$presenter->display();
} else {
die($presenter->getMessage());
}
}
} catch (Exception $error) {
die($error->getMessage());
}
} else {
die("You do not have access to the requested page!");
}
} catch (Exception $error) {
die($error->getMessage());
}
} else {
die("An valid module for your request was not found");
}
} else {
die("Could not find: $classFile");
}
} else {
die("A valid module was not specified");
}
?>
After looking over the code, you might notice a few things. Here's a walk through the second example URI from above to thoroughly explain things.
config.php
__autoload()
function. Because all the classes are named in a structured manner, this function can easily include them when necessary. The __autoload()
function is new to PHP 5 and provides a method to load classes dynamically. This eliminates the need to have the controller require every single library needed to function.
$module
, $event
, and $class
are defined, the controller can move on to loading the module.
authenticate()
, which should return a Boolean value. If the function returns true
, then the controller continues running the module's event handler; if false
, then the controller exits out. This would be a good place for the controller to redirect to a login form.
login::__default()
. The event handler can either throw an exception or return a PEAR_Error
, which tells the controller that something deadly has happened.
FR_Presenter_smarty
presentation layer. You may not like Smarty, which is fine. Just create a new presentation class and use that instead. If there isn't anything wrong with the requested presentation layer, the controller finally runs the display()
method, which does the heavy lifting of rendering the module. Notice how I pass the instance of the module class to the presentation layer. For being under 100 lines of code, the controller sure does a lot! The beauty of MVC programming is that the controller is dumb to what is going on. Its sole purpose is to do simple validation on the request, load up the request, and hand off everything to the presentation layer to render the request.
Working within this structure can seem somewhat rigid at first, but once you realize how much more quickly you are able to create large applications and leverage the existing code, you can easily work around the perceived rigidity.
One of the biggest advantages of MVC programming I have found is that once my foundation is rock solid, I rarely have to worry about bugs related to the basics of my site. This allows me to focus more on the application I'm programming, rather than whether my database connection is working or the user is being properly authenticated. In my own framework, I've found that there are files and classes that I haven't touched since first coding them (three years and counting). In addition to reducing the time you spend debugging on mundane features (such as authentication), working within a framework allows you to add features that propagate downstream and fix bugs in centralized locations.
Do the URLs in my example make you cringe? They make me cringe too, which is why mod_rewrite
exists. Below is an example of some rewrite rules I created to work with the framework.
RewriteEngine On
# Change the URI here to whatever you want your homepage to be
RewriteRule ^/$ /index.php?module=welcome [L,QSA]
# Changes /index.php?module=welcome to /welcome
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/([^/]*)$ /index.php?module=$1 [L,QSA]
# Changes /index.php?module=users&class=login to /users/login
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/([^/]*)/([^/]*)$ /index.php?module=$1&class=$2 [L,QSA]
# Changes /index.php?module=users&class=login&event=foo
# to /users/login/foo.html
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/([^/]*)/([^/]*)/([^/]*).html$ \
/index.php?module=$1&class=$2&event=$3 [L,QSA]
Now those are some pretty URLs! One item to note is that you don't need to worry about GET
arguments with mod_rewrite
. It ignores GET
arguments and passes them on to the real script.
The nice thing about having a controller is that you can easily make changes in a single place to add features that will then be instantly available to all of your applications. You could easily add several features to the controller to enhance your applications.
GET
and POST
input. This would include escaping variables, possibly removing malicious HTML code, and so on. The only drawback could be that you sanitize data that isn't truly harmful (such as data from an admin form).
?view=pdf
, and switch your module class's presentation layer to the PDF presenter instead of the default presenter.
Whatever you decide to do with your controller, make sure that the changes are ambiguous enough to be useful for all applications, regardless of what they might be used for. For instance, stripping all HTML from POST
input might not be such a good idea, as many administrative forms might need to add/edit/update data in the database that contains HTML.
In the next part of this series, I will cover the presentation layer (the view). By using the factory method, I can easily switch the presentation of my applications to the users.
is the Lead Architect for Digg where he spends his time partitioning data, creating internal services, and ensuring the code frameworks are in working order.