I love PHP frameworks like CakePHP, but sometimes they seem a little bloated. I’ve seen some “lightweight” frameworks like MicroMVC (which brags about being “less than 150kB”) and TinyMVC (which is still over 30kB zipped). For those of you who want a lighter-than-air, no-nonsense, zero-learning-curve, lightning-fast MVC framework in less than 4/5 of a kilobyte (uncompressed), I present PunyMVC:
<?function v($c,$a,$vars){if(is_file("views/$c/$a.php")){ob_start();include"views/$c/$a.php";$v=ob_get_contents();ob_end_clean();}return isset($v)?$v:";}function p($content,$l){if(isset($l)){include"layouts/$l.php";}else{echo$content;}}include'config/config.php';$b=strlen(dirname($_SERVER['PHP_SELF']));$u=explode('/',substr($_SERVER['REDIRECT_URL'],$b>1?$b+1:1));if(!($c=$u[0])){include'pages/index.php';die();}elseif(!preg_match('/\W/',$c.isset($u[1])?isset($u[1]):'')&&is_file("controllers/$c.php")){include"controllers/$c.php";$d=$c.'Controller';$a=isset($u[1])&&!empty($u[1])?$u[1]:'index';if(class_exists($d)&&in_array($a,get_class_methods($d))){$o=new$d;p(v($c,$a,$o->$a(array_slice($u,2))),isset($o->layout)?$o->layout:null);die();}}header('HTTP/1.1 404 Not Found');include'pages/404.php';
Yep, that’s it. Of course, I had to minify the code in order to squash it down to under a kilobyte; if you scroll to the bottom of this post, you can download a copy of the developer-friendly unminified version (which includes all of the following sample code as well). Anyway, here’s how to use it:
- Step 1: Save the code blob above as index.php and place it in your webroot.
- Step 2: Make an .htaccess file that will tell Apache to filter all URLs through PunyMVC (unless a file exists):
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]
- Step 3: Make some subdirectories:
$ mkdir config controllers layouts models pages views
- Step 4: Place an .htaccess file in each of the subdirectories for security. The files can just contain the line “deny from all”.
- Step 5: Make a default index page:
$ echo “Welcome to my awesome website.” > pages/index.php
- Step 6: Make a “404 not found” page:
$ echo “Page not found.” > pages/404.php
- Step 7: Make a controller. Here’s a sample controller called Posts:
<?php // controllers/posts.php
class PostsController {
public $layout = 'simple';
private $Posts;
public function __construct() {
require_once 'models/posts.php';
$this->Posts = new PostsModel();
}
public function index() {
$posts = $this->Posts->find();
return array('posts' => $posts);
}
public function view($args) {
if (ctype_digit($args[0])) {
$post = $this->Posts->find(array('_id' => intval($args[0])));
}
if (!isset($post) || empty($post)) {
include 'pages/404.php';
die();
}
return array('post' => $post[$args[0]]);
}
}
- Step 8: Make a model. Here’s a sample model (also called Posts). Just for fun, we’ll use MongoDB instead of a traditional RDBMS like MySQL:
<?php // models/posts.php
class PostsModel {
private $mongo;
private $mongoDb;
private $posts;
public function __construct() {
$this->mongo = new Mongo(MONGO_SERVER);
$this->mongoDb = $this->mongo->{MONGO_DATABASE};
$this->posts = $this->mongoDb->posts;
}
public function find($query = array(), $fields = array()) {
# Very simple example, we just pass the query straight to Mongo
return iterator_to_array($this->posts->find($query, $fields));
}
}
- Step 9: Make some views. Here are two sample views for the actions in our Posts controller:
<?php // views/posts/index.php ?>
<?php $dir = dirname($_SERVER['PHP_SELF']); ?>
Here are the posts:<br /><br />
<?php foreach ($vars['posts'] as $post) : ?>
<?php $post_uri = sprintf('%s/posts/view/%u', $dir, $post['_id']) ?>
<a href="<?php echo $post_uri ?>"><?php echo $post['title'] ?></a>
by <?php echo $post['author'] ?><br />
<?php endforeach ?>
<?php // views/posts/view.php ?>
This is a single post:<br />
<br />
Title: <?php echo $vars['post']['title'] ?><br />
Author: <?php echo $vars['post']['author'] ?><br />
Content:<br />
<?php echo $vars['post']['content'] ?><br /><br /><br />
- Step 10: Make a layout. Layouts are optional (but a good idea). We specified one in the controller, so we’ll make it:
<?php // layouts/simple.php ?>
<html>
<head>
</head>
<body>
<?php echo $content ?>
</body>
</html>
- Step 11: Make a configuration file. You can store anything you want here (I use it for database settings in the demo app):
<?php // config/config.php
define('MONGO_SERVER', 'mongodb://localhost:27017');
define('MONGO_DATABASE', 'punymvc_demo');
- Step 12: We’re done with the app, but it won’t do much unless we have data. Set up a MongoDB server and dump some posts into a database:
> use punymvc_demo
> db.posts.insert({“_id”:1,”title”:”This is an awesome post”, “author”:”Dave Hensley”, “content”:”This is the first post on my PunyMVC demo blog.”})
> db.posts.insert({“_id”:2,”title”:”This is an even awesomer post”, “author”:”John Smith”, “content”:”Just kidding. Dave’s post was way better than mine.”})
You can download the full demo app here. (Note: The download version is not minified, and includes the license and phpDoc comments — but even with all that bloat, it’s still well under 4K compressed.)