This is a valid article, and is considered technically accurate up to Feb. 21, 2010
When Artisan System was first started, I had a vague idea of what the Model-View Controller pattern (MVC) was. As a result, the initial Artisan System framework had a hacked together inefficient MVC layout. It was burdensome and unintuitive. After researching more as to what MVC really is, I rewrote it from scratch. Twice. Now, the latest version of Artisan System, 0.4, has a complete MVC engine that’s efficient and very easy to use.
Models, Views, and Controllers
Finding an easy to understand idea of what MVC is was hard to do. Most contained the original layout of the idea proposed by Xerox in the 1970’s. The idea has changed since then, especially for web development. The overall idea of MVC is to separate business logic (what is done with the data) with the display logic (how the data is viewed).
Your model is your data. It can come from anywhere: flat files, XML, a database, a key/value store. The view is what determines how the data will be displayed. Views can generally have some logic in them, but it should be entirely confined to displaying data. A simple example would be to display a different greeting depending on a user type.
<?php if ( $user->type == USER_TYPE_BUSINESS ): ?>
<?php echo _('Welcome to the website, Business User.'); ?>
<?php else: ?>
<?php echo _('Welcome to the website, Consumer User.'); ?>
<?php endif; ?>
The Controller determines what to do with the data sent to it, how it affects the Model, and how to render the data in the View. When broken into their individual terms, understanding the MVC pattern becomes quite clear.
Artisan System contains a series of mostly abstract classes to help define each sub-section of MVC. An abstract Artisan_Controller class is meant to be extended for each of a websites controllers. Each controller has an Artisan_View object as a member. This object is what is responsible for rendering one or more views associated with a Controller. Finally, each Controller has an extended Artisan_Model object that defines validation rules for any forms associated with that View. Generally, each view should have a single form, if any, but multiple forms per View are easily supported.
On the filesystem, this is laid out intuitively as well.
application/
Root/
View/
header.phtml
menu.phtml
footer.phtml
Root.php
Index/
Model/
Index.php
View/
about.phtml
index.phtml
Index.php
The main files in each sub directory are the Controller files, the file in the Model directory is the Model validation file, and all of the files in the View directory correspond to a View. Because they are .phtml files, the views themselves can contain PHP code. The alternative PHP syntax should be used for them, as it is a built in templating language and much more suited for integration with HTML.
Routing and Redirection
As of this writing, the Controllers are URL based. This means the URL specifies which Controller and View to load. In a typical Artisan System application, the index.php file is very small and handles this.
<?php
require_once 'Artisan/Controller/Router.php';
$config_artisan_router = new Artisan_Config_Array(array(
'site_root' => 'http://www.example.com',
'site_root_secure' => 'https://www.example.com/',
'root_dir' => 'application',
'layout_dir' => 'layout',
'default_controller' => 'Index',
'default_method' => 'index',
'default_layout' => 'index',
'rewrite' => true
)
);
$artisanRouter = new Artisan_Controller_Router($config_artisan_router);
echo $artisanRouter->dispatch();
exit;
Basic URL’s without using any rewrite’s are in the form: index.php?u=controller/view/arg1/arg2/argN
Thus, if the $_REQUEST variable u is used for anything, the application will not work. In this example, the value passed to u is exploded based on the slashes. The first value is the name of the Controller to load, the next is the name of the method within that controller to load. Any values after the frist two are passed in as arguments to that method.
Thus, the URL index.php?u=profile/update/1 called through a POST request will cause the following actions to occur.
- Profile/Profile.php is loaded
- Profile_Controller::updatePost() is found
- Profile_Controller::updatePost(1) is called (albeit, not statically)
If the request was made by GET, the method updateGet() would be used instead.
If the method is not found, an exception is thrown, which can be handled by the router.
Because each Controller extends the base Artisan_Controller class, they can easily render the view associated with that Controller. By default, the Controller will attempt to render a view of the same name as the Controller, however, its generally best to specify one.
<?php
class Index_Controller extends Artisan_Controller {
public function indexGet($page) {
$page_data = load_page_data($page);
$this->page_data = $page_data;
$this->render('index');
return true;
}
public function updatePost($page) {
$data = $this->getInput('page');
update_page_data($page, $data);
$this->redirect('index');
}
}
The above code is generic in nature, and obviously the methods load_page_data() and update_page_data() need to be defined, but it gives a general layout for a controller.
In future revisions, the routing will remain URL based by default, but can easily be overwritten with a rewrite table.
By default, the controller will render that data directly to the browser. This is useful for simple AJAX requests where one simply wants basic HTML, JSON, or XML returned. However, on non-AJAX requests, the full HTML page needs to be rendered. Artisan System makes use of Layouts and Content Blocks to facilitate this.
Layouts and Content Blocks
Artisan System uses a layout file to handle the final layout of a page. The layout file is mainly intended for designers to be able to re-theme an application easily.
Layout files are .phtml files as well, but live in a directory named layout (by default). Each has a unique name and is referenced by the Controller calling it. Thus, each Controller could have a different layout, although its generally not necessary.
The root Artisan_Controller class defines a protected member variable $_layout. The value of this variable is the name of the layout file, minus the extension, to be loaded. Additionally, a method, public Artisan_Controller::setLayout($layout) is available to set the layout without having to reference the variable directly. By default, no layout is set, and thus each view is rendered directly to the browser. However, the render() method of the Artisan_Controller class takes an optional second parameter, the name of the content block to render to. After the view is rendered to a specific content block, that block can be loaded in the layout (or another view).
<?php
class Index_Controller extends Artisan_Controller {
protected $_layout = 'default';
public function indexGet() {
// $title is now a variable that can be used directly in the view.
$this->title = _('Welcome to my site!');
// Renders the index.phtml view to the 'body' content block.
$this->render('index', 'body');
return true;
}
}
The index.phtml view is found in application/Index/View/ and is very straightforward.
<?php echo $title; ?>
<p>Thank you for taking the time to read my website.</p>
However, because this method renders to a specific content block, body, nothing will be displayed until the appropriate layout is loaded. The loading and rendering of the layout file happens automatically by the Artisan_Controller file in the load() method.
The layout file, default.phtml is located in public/layout and is equally as straightforward.
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Welcome to my Site!</title>
</head>
<body>
<?php echo $this->getBlock('body'); ?>
</body>
</html>
The default.phtml layout file now loads the body content block and the resulting concatenation of both is rendered to the browser.
Conclusion
Separation of content from design from logic was always a difficult design decision to make. However, with Artisan System, this issue is effortless and intuitive.