DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Data Engineering
  3. Databases
  4. Another MVC approach

Another MVC approach

Daniel Oosterhuis user avatar by
Daniel Oosterhuis
·
Aug. 04, 08 · Interview
Like (0)
Save
Tweet
Share
24.17K Views

Join the DZone community and get the full member experience.

Join For Free

So, after having read and heard much about the Model/View/Controller pattern, I decided to apply it to a new web application at the office. After much trial and error, redesign, refactoring, and more reading, I've come up with a simple architecture that actually works quite well. If you're not at all familiar with MVC, I suggest you read the wiki page for a brief introduction. Ready?

My application can be broken down into roughly 5 objects: the FrontOffice object, Controllers, Actions, ActionResults and Views.

FrontOffice
The FrontOffice is where the request comes in. Since a request could come in through any kind of protocol, I've defined FrontOffice as an abstract class, extending on it for each protocol. In this case, we'll use the FrontOffice_URL object, to extract the request from the URL. The FrontOffice simply translates passes the request on to the Controller that's responsible for handling the requested Action. This is called a single point of entry.

Controller
I´ve decided to cut the controller in three pieces to allow for more abstraction. The Controller only takes care of the mapping from an action alias to an action class, providing the model, and executing the result.

Action
Action objects deal with authorizing an action (business rules), and executing the action. This is actually an implementation of the Command Pattern. Depending on the outcome of the Action::execute() method, the Action returns a ActionResult object. This determines the routing of the application. For example: if a record is successfully added to the DB, redirect the client to the overview page. Else, show the Add Record page again, and display the error.

ActionResults
ActionResult objects are returned by an Action object to the Controller. They implement a simple interface, that just says that every ActionResult object must have an execute() method. Examples of ActionResult objects are ActionResult_View (present HTML), ActionResult_Redirect (redirect client) and ActionResult_Download (present a file download).

Views
The Views are simple php files that define the html for a page and take some simple data to combine with it.

The diagram below shows how a request is processed by the system.

Model View Controller

The Model
So, after seeing this, I can hear you say: where's the model? Good one. THis may come as a surprise, but there's no Model classes in my model. In my opinion, the model should handle basic validation and sanitation rules of your data To achieve this, I use a powerful DB engine, written by my friend Arnold Daniels. It's called QDB and it's available for free on his website. For each of my database tables I have a .ini configuration file (you can use yaml or whatever you prefer) that defines datatype and validation rules (required yes/no, maxlength, validate email etc). So when I need the model in my controller, I just call the QDB library for a record/table/recordset and it gives me the data. You don't have to bother with mysql statements or whatever, it's all taken care of under the hood. So basically, the Controller is ignorant of the type of database you're running on. Check the code examples for the simplicity of QDB.

Now, let's take a look at a simple example.


Example
We'll start with a simple example of a MVC application based on this architecture. Let's say our application holds a table with Employees, and we want to update a record (with id=3) in this table. I use the mod_rewrite in Apache for clean urls. The actual URL for this action would be http://mywebsite.com/index.php?__controller=Employee&__action=update&id=3 but this is rewritten to http://mywebsite.com/Employee/update?id=3.

 # initialize the FrontOffice 
$fo = FrontOffice::factory('URL');

# process the request
$fo->dispatch();

This could be all the PHP code in the webroot of your application. As a matter of fact, this is my index.php (safe some initialization code and user authentication, like include 'config.inc';). Looks like magic, doesn't it?

So what happens?
First, we initialize the FrontOffice. I use a factory method to do it, and this returns a FrontOffice_URL object. Here's the constructor of the FrontOffice_URL class:

class FrontOffice_URL extends FrontOffice { 

public $output = "Html";

/**
* Initialize the FrontOffice - Read from URL the controller and the action
*
* @return FrontOffice object
*/
public function __construct() {

$this->_requestVars = new Data();

# capture get
if ($_GET['__controller']) $this->_controller = urldecode($_GET['__controller']);
if ($_GET['__action']) $this->_action = urldecode($_GET['__action']);

foreach ($_GET as $k=>$v) {
$k = urldecode($k);
$this->_requestVars->{$k} = urldecode($v);
}

# capture post
foreach ($_POST as $k=>$v) $this->_requestVars->{$k} = $v;

}

}

Quite simple, right? The controller and the action are distilled from the URL, and the rest of the request vars are stored in a generic Data object, which is just a void class (in PHP you can set properties on the fly). Note that the output format is set to "Html". In the future, you might want your application to respond in an XML format. Depending on the $output setting, you can include the proper views. At the end of the article this is shown in the directory structure.


So then we go to the second statement:

# load controller 
$cclass = self::getControllerClass($this->_controller);
if (!class_exists($cclass)) throw new Controller_Unknown_Exception("Unknown Controller object `".$this->_controller."` requested");

$controller = new $cclass($this);

# controller must extend Controller class
if (!($controller instanceof Controller)) throw new Controller_Illegal_Exception("Controller `$cclass` is not a valid Controller object");

# execute the action
return $controller->execute($this->_action, $this->_requestVars);

Now, we've moved the request from the FrontOffice to a Controller object. Aren't you excited?! From here, we're actually gonna do something :)

The controller in this case is the Employee controller. We'll focus on the execute() method that's being called. As I said, it performs a simple mapping from action names to action classes. I prefer not to do this straight from the URL for several reasons. The execute() method looks up and loads the class for the requested Action, and then call the Action::execute() method. This method in turn returns an ActionResult object. The ActionResult::execute() does whatever the action requires (present a view, offer a file download, redirect the client etc).

class Controller_Employee extends Controller { 

public function execute($action, $data)
{
$data->mode = $action;

switch (strtolower($action))
{
case "list":
$action = new Action_Overview('Employee', $data);
break;
case "update":
case "show":
case "add":
$action = new Action_Detail('Employee', $data);
break;
case "delete":
$action = new Action_Delete('Employee', $data);
break;
default :
$action = new Action_Overview('Employee', $data);
break;
}
$result = $action->execute();
if (!$result instanceof ActionResult) throw new ActionResult_Illegal_Exception('$result is not a valid ActionResult');

return $result->execute();

}

}

That's the code for the execute() method.

In the constructor of Action_Detail we have:

public function __construct($table, $data) 
{
if (!$data->id && $data->mode !== 'add') throw new Action_Arguments_Exception('Param id required, not given');
$this->data = $data;
$this->record = QDB::i()->table($table)->load($data->id);

if (!$this->record) throw new Action_Exception('Record not found');
}

You might understand my appreciation for the QDB library after seeing this. It's totally void of MySQL statements and has a very intuitive API.

Back to the Controller_Employee::execute(). Two things happen inside of $result = $action->execute().

  1. The Action's auth() method is called, which would check stuff like: if the current user is not the employee's boss, the user can't change the employee record. In this example, I won't check for anything, so I can just use the generic Action_Detail object (it's auth() method always returns true). Otherwise you would create an object Action_Detail_Employee_Update extends Action_Detail and override the auth() method.
  2. If the Action::auth() returns true, we can go on executing the action.

Now that we're authorized to update this employee, we want to get to the part where we create a form. For this I use a proprietary form library that compares to PEAR's HTML_QuickForm (but is actually better :) ). So just for argument's sake, don't bother with trying to understand the $form object. The code for actually performing the action looks something like this:
In Action_Detail::execute()

$form = HTML_Form::createFromData('form', $this->record); 

# if the form is submitted and validated, redirect to overview
if ($form->isSubmitted() && $form->validate()) {
$this->record->save(); // record updaten
return new ActionResult_Redirect('/'.FrontOffice::currentController().'/list');
}

# create form
if ($this->data->mode === 'show')
return new ActionResult_View('View/Show.php', $this->record);
else
return new ActionResult_View('View/Update.php', $this->record);

The ActionResult is returned to the Controller, where we saw the return $result->execute(); code. In this case, we get the ActionResult_View object returned to us, with a view and some data to present in the view.

Almost there, just the ActionResult_View::execute() to wrap it up.

function execute()  
{
# Make $data available as a global in the View
$data = $this->data;

# determine output format (HTML/XML etc)
$type = ucfirst(strtolower(FrontOffice::i()->output));

# locate View file
$view = preg_replace('/View/', 'View/'.$type, $this->view);

try {
$success = @include($view);
if (!$success) throw new View_Exception("View does not exist");
} catch (View_Exception $e) {
print $e->getMessage();
}
}

The View file that's included here could look something like this:

# initialize stuff like menu/layout etc 
initLayout();

if ($data && $data instanceof QDB_Record)
{
$form = HTML_Form::createFromData('form', $data);
$form->validate();

$form->display();
}

And that's it. We include the PHP file and it outputs some HTML to the client.

To close, here's the directory structure I use:

Action
  \Delete.php
  \Detail.php
  \Overview.php
  \Static.php
ActionResult
  \Download.php
  \NotAllowed.php
  \Redirect.php
  \View.php
MyApplication
  \Action
  \Employee
     \Update.php
  \Controller
     \Employee.php
     \...
  \View
     \...

View
  \Html
     \Overview.php
     \Start.php
     \...
   \Xml
     \Overview.php
     \Start.php
     \...

Action.php
ActionResult.php
Controller.php
Data.php
FrontOffice.php

That's it. We're currently using this architecture in a big application and it's proving to be very flexible and easy to use. It may take a little time to get used to the structure, but the high reusability and small pieces of code really pay off in the long run.

Note that I've simplified some stuff, and I haven't gone into detail about some of the interfaces/abstract classes I use. I intended to get my idea across, and look forward to any questions/suggestions and comments.

 

Object (computer science) Database Web application

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Rust vs Go: Which Is Better?
  • Using GPT-3 in Our Applications
  • Choosing the Right Framework for Your Project
  • Create a REST API in C# Using ChatGPT

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: