Introduction
I primarily work with ASP.NET and therefore fell in love with the simplicity of their MVC model. You just click new controller, yes please create the view, and you are presented with a piece of controller code that
couldn't be more simplistic.
But the internet is mostly build out of PHP/MySQL and when you're building a site for the bakery at the corner, including Zend or some other large MVC, might be a bit over the top. I wanted to create a
simple MVC modal in PHP, where the controller class
could be just a simple as the .NET variant.
In .NET you can chose several template engines. In the modal I present you could easily plug-in any template engine you'd like. I went for Smarty. http://www.smarty.net/
I am in now way claiming this to be unique, there are a lot of great frameworks out there that do
exactly the same. Like CodeIgniter. But I thought it would be fun to see how you could roll your own.
The code
First things first. Because we would like a MVC modal, we need al the request to be routed through a single source. This is done by changing the
.htaccess file.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(\.css|\.js|\.jpg|\.jpeg|\.png|\.gif|\.flv|\.swf)$
RewriteRule ^.*$ index.php
Note the exception list. You wouldn't want to route CSS through a controller. Although that might be the place where you could transform your
.sass or .less files in to .css.
The same goes for minified JavaScript files or optimised images.
Bootstrapping
I've set up an ini file. That contains some valuable settings. The first thing
index.php should do is parse this file.
[settings]
controller_path="controllers/"
view_path="views/"
baseURL="/subdir"
baseUrl
could be empty. If this was installed at the root of the site
index.php is then going to include smarty and do some basic stuff. And then do the most important part of parsing the incoming request URL.
If the URL is home, then the homeController.php should contain that controller. So if the
URL is products/details/10, productsController.php should be loaded.
<?php
require_once('libs/Smarty.class.php');
$config = parse_ini_file("config.ini", TRUE);
if (isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD'])) {
$requestData = '';
$method = $_SERVER['REQUEST_METHOD'];
if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 0) {
$httpContent = fopen('php://input', 'r');
while ($data = fread($httpContent, 1024)) {
$requestData .= $data;
}
fclose($httpContent);
}
$urlString = substr($_SERVER['REQUEST_URI'], strlen($config['settings']['baseURL']));
$urlParts = explode('/', $urlString);
if (isset($urlParts[0]) && $urlParts[0] == '') {
array_shift($urlParts);
}
if (!isset($urlParts[0]) || $urlParts[0] == '') {
$mainViewPath="home/";
$currentController = "HomeController";
$controllerFile = "homeController.php";
}
else {
$mainViewPath=strtolower($urlParts[0]);
$currentController = ucfirst($urlParts[0]) . "Controller";
$controllerFile = $urlParts[0] . "Controller.php";
array_shift($urlParts);
}
}
else {
header($_SERVER["SERVER_PROTOCOL"] . " 400 Bad request");
}
?>
Abstract class Controller
So we now know which controller class we would like to start. If someone gave us the request
URL/products, we'd want to load productsContoller.php and create an instance of the ProductsController.
But first we need to focus on creating an abstract class Controller. In order to get our controllers to have such a simple structure,
we'll need a super class that will do al the heavy lifting.
All controllers will inherit from this class. The Controller class needs to do a few things. It needs to look at what
is left of the request URL and determine which action within the controller should be started.
So if we get /products, there is no action defined and we'll start the index action. If we get /products/details we wil start the details action.
The second thing the Controller class needs to do is create the viewBag. This is a 'bag' of data that the implementing
controller can add data to. But next to that I like to also assume that POST data is JSON and that all
POST data is added to the viewBag before starting the controller action. This will allow setting template variables from
JavaScript. (But that might be better for a next time).
The third thing he does is determine the view.html file.
The last thing the Controller class does is implement the View() function, which applies the viewBag to the template.
The constructor
public function __construct($controllerName, $urlParts, $data)
{
$this->fullActionPath=implode("/", $urlParts);
$this->method=$_SERVER['REQUEST_METHOD'];
if($data=='') {
$data='{}';
}
$this->viewBag=json_decode($data);
$action=$urlParts[0];
if (count($urlParts) > 1 && $urlParts[1] != '') {
array_shift($urlParts);
foreach ($urlParts as $uid) {
if ($uid != '') {
$this->uid[] = $uid;
}
}
}
if(!isset($action) || $action=='') {
$action="index";
}
$this->viewHtml=strtolower($action) . ".html";
try {
$reflector = new ReflectionClass($this);
$method=$reflector->getMethod($action);
call_user_func($controllerName . "::" . $action, $this->uid);
}
catch(Exception $exc) {
call_user_func($controllerName . "::index");
}
}
View function
public function View()
{
$viewPath=$GLOBALS['config']['settings']['view_path'] . $this->viewHtml;
if (file_exists($viewPath)) {
$this->smarty = new Smarty();
foreach($this->viewBag as $key => $value) {
$this->smarty->assign($key, $value);
}
$this->smarty->display($viewPath);
}
else {
header('HTTP/1.0 404 Not Found');
echo "404 Not Found";
echo "The page that you have requested could not be found.";
}
}
We'll need to add require_once('classes/Controller.php')
to our
index.php.
Home view and controller
Now we're ready to create views and controllers, but first I'll remind you of the
.ini file which has controller_path
and view_path
. This
determines the file structure below:
- baseURL
- classes
- Controller.php
- Smarty.class.php
- controllers
- views
- .htaccess
- config.ini
- index.php
HomeController
<?php
class HomeController extends Controller
{
public function index()
{
$this->viewBag->hellomessage="Hello world!";
return $this->View();
}
}
?>
home/index.html
<html>
<head>
<title>Index</title>
</head>
<body>
<h1>{$hellomessage}</h1>
</body>
</html>
That is basically it.
Conclusion
Though this is going to need al lot more detail to be used in a real live situation I believe it shows you can create a simple MVC with PHP. For small sites this should almost be enough.
In the zip files I've worked the example out to some more detail. It contains a worked out example of the bakery webshop. It also works with a shared template, so that we can wrap
html around our views. One of the most important parts of MVC is missing though. The modal. I'll get to that in
the next article.
This being my first article and me not really being a core PHP coder, any comments will be welcome.